diff options
1783 files changed, 48666 insertions, 65710 deletions
diff --git a/Android.bp b/Android.bp index d64951c3d98f..cc754f21902a 100644 --- a/Android.bp +++ b/Android.bp @@ -62,12 +62,8 @@ license { "SPDX-license-identifier-Apache-2.0", "SPDX-license-identifier-BSD", "SPDX-license-identifier-CC-BY", - "SPDX-license-identifier-CPL-1.0", - "SPDX-license-identifier-GPL", - "SPDX-license-identifier-GPL-2.0", "SPDX-license-identifier-MIT", "SPDX-license-identifier-Unicode-DFS", - "SPDX-license-identifier-W3C", "legacy_unencumbered", ], license_text: [ @@ -81,6 +77,7 @@ filegroup { // Java/AIDL sources under frameworks/base ":framework-annotations", ":framework-blobstore-sources", + ":framework-bluetooth-sources", // TODO(b/214988855) : Remove once framework-bluetooth jar is ready ":framework-connectivity-tiramisu-sources", ":framework-core-sources", ":framework-drm-sources", @@ -93,7 +90,7 @@ filegroup { ":framework-mca-effect-sources", ":framework-mca-filterfw-sources", ":framework-mca-filterpacks-sources", - ":framework-media-sources", + ":framework-media-non-updatable-sources", ":framework-mms-sources", ":framework-omapi-sources", ":framework-opengl-sources", @@ -155,38 +152,6 @@ filegroup { } java_library_with_nonpublic_deps { - name: "framework-updatable-stubs-module_libs_api", - static_libs: [ - "android.net.ipsec.ike.stubs.module_lib", - "framework-appsearch.stubs.module_lib", - "framework-connectivity.stubs.module_lib", - "framework-connectivity-tiramisu.stubs.module_lib", - "framework-graphics.stubs.module_lib", - "framework-media.stubs.module_lib", - "framework-mediaprovider.stubs.module_lib", - "framework-permission.stubs.module_lib", - "framework-permission-s.stubs.module_lib", - "framework-scheduling.stubs.module_lib", - "framework-sdkextensions.stubs.module_lib", - "framework-statsd.stubs.module_lib", - "framework-supplementalprocess.stubs.module_lib", - "framework-tethering.stubs.module_lib", - "framework-uwb.stubs.module_lib", - "framework-nearby.stubs.module_lib", - "framework-wifi.stubs.module_lib", - ], - soong_config_variables: { - include_nonpublic_framework_api: { - static_libs: [ - "framework-supplementalapi.stubs.module_lib", - ], - }, - }, - sdk_version: "module_current", - visibility: ["//visibility:private"], -} - -java_library_with_nonpublic_deps { name: "framework-all", installable: false, static_libs: [ @@ -212,7 +177,7 @@ java_library_with_nonpublic_deps { soong_config_variables: { include_nonpublic_framework_api: { static_libs: [ - "framework-supplementalapi.stubs.module_lib", + "framework-supplementalapi.impl", ], }, }, @@ -466,7 +431,6 @@ filegroup { name: "framework-ike-shared-srcs", visibility: ["//packages/modules/IPsec"], srcs: [ - "core/java/android/net/annotations/PolicyDirection.java", "core/java/com/android/internal/util/HexDump.java", "core/java/com/android/internal/util/WakeupMessage.java", "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java", diff --git a/ApiDocs.bp b/ApiDocs.bp index feb43d1068b9..4aecc8fe4504 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -60,9 +60,9 @@ stubs_defaults { defaults: ["android-non-updatable-stubs-defaults"], srcs: [ // No longer part of the stubs, but are included in the docs. - "test-base/src/**/*.java", - "test-mock/src/**/*.java", - "test-runner/src/**/*.java", + ":android-test-base-sources", + ":android-test-mock-sources", + ":android-test-runner-sources", ], libs: framework_docs_only_libs, create_doc_stubs: true, @@ -74,11 +74,6 @@ stubs_defaults { srcs: [ ":android-non-updatable-stub-sources", - // Module sources - ":art.module.public.api{.public.stubs.source}", - ":conscrypt.module.public.api{.public.stubs.source}", - ":i18n.module.public.api{.public.stubs.source}", - // No longer part of the stubs, but are included in the docs. ":android-test-base-sources", ":android-test-mock-sources", @@ -116,6 +111,10 @@ stubs_defaults { name: "framework-doc-stubs-sources-default", defaults: ["framework-doc-stubs-default"], srcs: [ + ":art.module.public.api{.public.stubs.source}", + ":conscrypt.module.public.api{.public.stubs.source}", + ":i18n.module.public.api{.public.stubs.source}", + ":framework-appsearch-sources", ":framework-connectivity-sources", ":framework-connectivity-tiramisu-updatable-sources", @@ -160,26 +159,8 @@ droidstubs { droidstubs { name: "framework-doc-stubs", defaults: ["framework-doc-stubs-default"], + srcs: [":all-modules-public-stubs-source"], args: metalava_framework_docs_args, - srcs: [ - ":android.net.ipsec.ike{.public.stubs.source}", - ":framework-appsearch{.public.stubs.source}", - ":framework-connectivity{.public.stubs.source}", - ":framework-connectivity-tiramisu{.public.stubs.source}", - ":framework-graphics{.public.stubs.source}", - ":framework-media{.public.stubs.source}", - ":framework-mediaprovider{.public.stubs.source}", - ":framework-nearby{.public.stubs.source}", - ":framework-permission{.public.stubs.source}", - ":framework-permission-s{.public.stubs.source}", - ":framework-scheduling{.public.stubs.source}", - ":framework-sdkextensions{.public.stubs.source}", - ":framework-statsd{.public.stubs.source}", - ":framework-supplementalprocess{.public.stubs.source}", - ":framework-tethering{.public.stubs.source}", - ":framework-uwb{.public.stubs.source}", - ":framework-wifi{.public.stubs.source}", - ], aidl: { local_include_dirs: [ "apex/media/aidl/stable", @@ -190,46 +171,6 @@ droidstubs { }, } -// This produces the same annotations.zip as framework-doc-stubs, but by using -// outputs from individual modules instead of all the source code. -genrule { - name: "sdk-annotations.zip", - srcs: [ - ":android-non-updatable-doc-stubs{.annotations.zip}", - - // Conscrypt and i18n currently do not enable annotations - // ":conscrypt.module.public.api{.public.annotations.zip}", - // ":i18n.module.public.api{.public.annotations.zip}", - - // Modules that enable annotations below - ":android.net.ipsec.ike{.public.annotations.zip}", - ":art.module.public.api{.public.annotations.zip}", - ":framework-appsearch{.public.annotations.zip}", - ":framework-connectivity{.public.annotations.zip}", - ":framework-connectivity-tiramisu{.public.annotations.zip}", - ":framework-graphics{.public.annotations.zip}", - ":framework-media{.public.annotations.zip}", - ":framework-mediaprovider{.public.annotations.zip}", - ":framework-nearby{.public.annotations.zip}", - ":framework-permission{.public.annotations.zip}", - ":framework-permission-s{.public.annotations.zip}", - ":framework-scheduling{.public.annotations.zip}", - ":framework-sdkextensions{.public.annotations.zip}", - ":framework-statsd{.public.annotations.zip}", - ":framework-supplementalprocess{.public.annotations.zip}", - ":framework-tethering{.public.annotations.zip}", - ":framework-uwb{.public.annotations.zip}", - ":framework-wifi{.public.annotations.zip}", - ], - out: ["annotations.zip"], - tools: [ - "merge_annotation_zips", - "soong_zip", - ], - cmd: "$(location merge_annotation_zips) $(genDir)/out $(in) && " + - "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out", -} - ///////////////////////////////////////////////////////////////////// // API docs are created from the generated stub source files // using droiddoc @@ -1,4 +1,3 @@ third_party { - # would be NOTICE save for libs/usb/tests/accessorytest/f_accessory.h - license_type: RESTRICTED + license_type: RECIPROCAL } diff --git a/StubLibraries.bp b/StubLibraries.bp index 1e16f9431f82..77b26f80ab97 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -185,6 +185,7 @@ droidstubs { last_released: { api_file: ":android-non-updatable.api.module-lib.latest", removed_api_file: ":android-non-updatable-removed.api.module-lib.latest", + baseline_file: ":android-non-updatable-incompatibilities.api.module-lib.latest", }, api_lint: { enabled: true, diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp index fc70219d2aeb..81cec9111b88 100644 --- a/apct-tests/perftests/packagemanager/Android.bp +++ b/apct-tests/perftests/packagemanager/Android.bp @@ -10,7 +10,10 @@ package { android_test { name: "PackageManagerPerfTests", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], static_libs: [ "platform-compat-test-rules", @@ -21,6 +24,7 @@ android_test { "apct-perftests-utils", "collector-device-lib-platform", "cts-install-lib-java", + "services.core", ], libs: ["android.test.base"], diff --git a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt index f9ddf9a9a59a..e873514f11a0 100644 --- a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -19,9 +19,6 @@ package android.os import android.content.pm.PackageParser import android.content.pm.PackageParserCacheHelper.ReadHelper import android.content.pm.PackageParserCacheHelper.WriteHelper -import android.content.pm.parsing.ParsingPackageImpl -import android.content.pm.parsing.ParsingPackageRead -import android.content.pm.parsing.ParsingPackageUtils import android.content.pm.parsing.result.ParseInput import android.content.pm.parsing.result.ParseTypeImpl import android.content.res.TypedArray @@ -29,6 +26,8 @@ import android.perftests.utils.BenchmarkState import android.perftests.utils.PerfStatusReporter import androidx.test.filters.LargeTest import com.android.internal.util.ConcurrentUtils +import com.android.server.pm.pkg.parsing.ParsingPackageImpl +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import libcore.io.IoUtils import org.junit.Rule import org.junit.Test @@ -42,7 +41,7 @@ import java.util.concurrent.TimeUnit @LargeTest @RunWith(Parameterized::class) -class PackageParsingPerfTest { +public class PackageParsingPerfTest { companion object { private const val PARALLEL_QUEUE_CAPACITY = 10 @@ -196,8 +195,12 @@ class PackageParsingPerfTest { // For testing, just disable enforcement to avoid hooking up to compat framework ParseTypeImpl(ParseInput.Callback { _, _, _ -> false }) } - val parser = ParsingPackageUtils(false, null, null, emptyList(), - object : ParsingPackageUtils.Callback { + val parser = ParsingPackageUtils(false, + null, + null, + emptyList(), + object : + ParsingPackageUtils.Callback { override fun hasFeature(feature: String) = true override fun startParsingPackage( @@ -206,7 +209,12 @@ class PackageParsingPerfTest { path: String, manifestArray: TypedArray, isCoreApp: Boolean - ) = ParsingPackageImpl(packageName, baseApkPath, path, manifestArray) + ) = ParsingPackageImpl( + packageName, + baseApkPath, + path, + manifestArray + ) }) override fun parseImpl(file: File) = @@ -268,6 +276,7 @@ class PackageParsingPerfTest { * Re-implementation of the server side PackageCacher, as it's inaccessible here. */ class PackageCacher2(cacheDir: File) : PackageCacher<ParsingPackageImpl>(cacheDir) { - override fun fromParcel(parcel: Parcel) = ParsingPackageImpl(parcel) + override fun fromParcel(parcel: Parcel) = + ParsingPackageImpl(parcel) } } diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index a1a46afcffe6..9fb12277fa5e 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -235,7 +235,6 @@ public class PowerExemptionManager { public static final int REASON_LOCKED_BOOT_COMPLETED = 202; /** * All Bluetooth broadcasts. - * @hide */ public static final int REASON_BLUETOOTH_BROADCAST = 203; /** @@ -259,6 +258,12 @@ public class PowerExemptionManager { * @hide */ public static final int REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207; + /** + * Broadcast {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES}. + * @hide + */ + public static final int REASON_ACTION_REFRESH_SAFETY_SOURCES = 208; + /* Reason code range 300-399 are reserved for other internal reasons */ /** * Device idle system allow list, including EXCEPT-IDLE @@ -399,6 +404,7 @@ public class PowerExemptionManager { REASON_TIME_CHANGED, REASON_LOCALE_CHANGED, REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, + REASON_ACTION_REFRESH_SAFETY_SOURCES, REASON_SYSTEM_ALLOW_LISTED, REASON_ALARM_MANAGER_ALARM_CLOCK, REASON_ALARM_MANAGER_WHILE_IDLE, @@ -682,6 +688,8 @@ public class PowerExemptionManager { return "LOCALE_CHANGED"; case REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED: return "REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED"; + case REASON_ACTION_REFRESH_SAFETY_SOURCES: + return "REASON_ACTION_REFRESH_SAFETY_SOURCES"; case REASON_SYSTEM_ALLOW_LISTED: return "SYSTEM_ALLOW_LISTED"; case REASON_ALARM_MANAGER_ALARM_CLOCK: diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 86d7a5a5a62e..12a8654c61f7 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -54,6 +54,8 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.net.Uri; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; @@ -250,8 +252,6 @@ public class JobSchedulerService extends com.android.server.SystemService */ private final List<RestrictingController> mRestrictiveControllers; /** Need direct access to this for testing. */ - private final BatteryController mBatteryController; - /** Need direct access to this for testing. */ private final StorageController mStorageController; /** Need directly for sending uid state changes */ private final DeviceIdleJobsController mDeviceIdleJobsController; @@ -268,6 +268,9 @@ public class JobSchedulerService extends com.android.server.SystemService */ private final List<JobRestriction> mJobRestrictions; + @GuardedBy("mLock") + private final BatteryStateTracker mBatteryStateTracker; + @NonNull private final String mSystemGalleryPackage; @@ -1553,7 +1556,7 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "UID " + uid + " bias changed from " + prevBias + " to " + newBias); } for (int c = 0; c < mControllers.size(); ++c) { - mControllers.get(c).onUidBiasChangedLocked(uid, newBias); + mControllers.get(c).onUidBiasChangedLocked(uid, prevBias, newBias); } } } @@ -1697,6 +1700,9 @@ public class JobSchedulerService extends com.android.server.SystemService // Initialize the job store and set up any persisted jobs mJobs = JobStore.initAndGet(this); + mBatteryStateTracker = new BatteryStateTracker(); + mBatteryStateTracker.startTracking(); + // Create the controllers. mControllers = new ArrayList<StateController>(); final ConnectivityController connectivityController = new ConnectivityController(this); @@ -1704,8 +1710,8 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(new TimeController(this)); final IdleController idleController = new IdleController(this); mControllers.add(idleController); - mBatteryController = new BatteryController(this); - mControllers.add(mBatteryController); + final BatteryController batteryController = new BatteryController(this); + mControllers.add(batteryController); mStorageController = new StorageController(this); mControllers.add(mStorageController); final BackgroundJobsController backgroundJobsController = @@ -1725,7 +1731,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(mTareController); mRestrictiveControllers = new ArrayList<>(); - mRestrictiveControllers.add(mBatteryController); + mRestrictiveControllers.add(batteryController); mRestrictiveControllers.add(connectivityController); mRestrictiveControllers.add(idleController); @@ -2818,6 +2824,129 @@ public class JobSchedulerService extends com.android.server.SystemService return adjustJobBias(bias, job); } + private final class BatteryStateTracker extends BroadcastReceiver { + /** + * Track whether we're "charging", where charging means that we're ready to commit to + * doing work. + */ + private boolean mCharging; + /** Keep track of whether the battery is charged enough that we want to do work. */ + private boolean mBatteryNotLow; + /** Sequence number of last broadcast. */ + private int mLastBatterySeq = -1; + + private BroadcastReceiver mMonitor; + + BatteryStateTracker() { + } + + public void startTracking() { + IntentFilter filter = new IntentFilter(); + + // Battery health. + filter.addAction(Intent.ACTION_BATTERY_LOW); + filter.addAction(Intent.ACTION_BATTERY_OKAY); + // Charging/not charging. + filter.addAction(BatteryManager.ACTION_CHARGING); + filter.addAction(BatteryManager.ACTION_DISCHARGING); + getTestableContext().registerReceiver(this, filter); + + // Initialise tracker state. + BatteryManagerInternal batteryManagerInternal = + LocalServices.getService(BatteryManagerInternal.class); + mBatteryNotLow = !batteryManagerInternal.getBatteryLevelLow(); + mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); + } + + public void setMonitorBatteryLocked(boolean enabled) { + if (enabled) { + if (mMonitor == null) { + mMonitor = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onReceiveInternal(intent); + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + getTestableContext().registerReceiver(mMonitor, filter); + } + } else if (mMonitor != null) { + getTestableContext().unregisterReceiver(mMonitor); + mMonitor = null; + } + } + + public boolean isCharging() { + return mCharging; + } + + public boolean isBatteryNotLow() { + return mBatteryNotLow; + } + + public boolean isMonitoring() { + return mMonitor != null; + } + + public int getSeq() { + return mLastBatterySeq; + } + + @Override + public void onReceive(Context context, Intent intent) { + onReceiveInternal(intent); + } + + @VisibleForTesting + public void onReceiveInternal(Intent intent) { + synchronized (mLock) { + final String action = intent.getAction(); + boolean changed = false; + if (Intent.ACTION_BATTERY_LOW.equals(action)) { + if (DEBUG) { + Slog.d(TAG, "Battery life too low @ " + sElapsedRealtimeClock.millis()); + } + if (mBatteryNotLow) { + mBatteryNotLow = false; + changed = true; + } + } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { + if (DEBUG) { + Slog.d(TAG, "Battery high enough @ " + sElapsedRealtimeClock.millis()); + } + if (!mBatteryNotLow) { + mBatteryNotLow = true; + changed = true; + } + } else if (BatteryManager.ACTION_CHARGING.equals(action)) { + if (DEBUG) { + Slog.d(TAG, "Battery charging @ " + sElapsedRealtimeClock.millis()); + } + if (!mCharging) { + mCharging = true; + changed = true; + } + } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { + if (DEBUG) { + Slog.d(TAG, "Disconnected from power @ " + sElapsedRealtimeClock.millis()); + } + if (mCharging) { + mCharging = false; + changed = true; + } + } + mLastBatterySeq = + intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, mLastBatterySeq); + if (changed) { + for (int c = mControllers.size() - 1; c >= 0; --c) { + mControllers.get(c).onBatteryStateChangedLocked(); + } + } + } + } + } + final class LocalService implements JobSchedulerInternal { /** @@ -3417,29 +3546,27 @@ public class JobSchedulerService extends com.android.server.SystemService void setMonitorBattery(boolean enabled) { synchronized (mLock) { - if (mBatteryController != null) { - mBatteryController.getTracker().setMonitorBatteryLocked(enabled); - } + mBatteryStateTracker.setMonitorBatteryLocked(enabled); } } int getBatterySeq() { synchronized (mLock) { - return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1; + return mBatteryStateTracker.getSeq(); } } - boolean getBatteryCharging() { + /** Return {@code true} if the device is currently charging. */ + public boolean isBatteryCharging() { synchronized (mLock) { - return mBatteryController != null - ? mBatteryController.getTracker().isOnStablePower() : false; + return mBatteryStateTracker.isCharging(); } } - boolean getBatteryNotLow() { + /** Return {@code true} if the battery is not low. */ + public boolean isBatteryNotLow() { synchronized (mLock) { - return mBatteryController != null - ? mBatteryController.getTracker().isBatteryNotLow() : false; + return mBatteryStateTracker.isBatteryNotLow(); } } @@ -3614,6 +3741,16 @@ public class JobSchedulerService extends com.android.server.SystemService mQuotaTracker.dump(pw); pw.println(); + pw.print("Battery charging: "); + pw.println(mBatteryStateTracker.isCharging()); + pw.print("Battery not low: "); + pw.println(mBatteryStateTracker.isBatteryNotLow()); + if (mBatteryStateTracker.isMonitoring()) { + pw.print("MONITORING: seq="); + pw.println(mBatteryStateTracker.getSeq()); + } + pw.println(); + pw.println("Started users: " + Arrays.toString(mStartedUsers)); pw.print("Registered "); pw.print(mJobs.size()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index cc202130ab07..27268d267001 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -293,13 +293,13 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { } private int getBatteryCharging(PrintWriter pw) { - boolean val = mInternal.getBatteryCharging(); + boolean val = mInternal.isBatteryCharging(); pw.println(val); return 0; } private int getBatteryNotLow(PrintWriter pw) { - boolean val = mInternal.getBatteryNotLow(); + boolean val = mInternal.isBatteryNotLow(); pw.println(val); return 0; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java index 657f4700bd5c..63781ae334d2 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java @@ -18,6 +18,8 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.annotation.NonNull; +import android.app.job.JobInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -31,7 +33,8 @@ import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.GuardedBy; +import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; @@ -48,18 +51,26 @@ public final class BatteryController extends RestrictingController { private static final boolean DEBUG = JobSchedulerService.DEBUG || Log.isLoggable(TAG, Log.DEBUG); + @GuardedBy("mLock") private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>(); - private ChargingTracker mChargeTracker; + /** + * List of jobs that started while the UID was in the TOP state. + */ + @GuardedBy("mLock") + private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>(); - @VisibleForTesting - public ChargingTracker getTracker() { - return mChargeTracker; - } + private final PowerTracker mPowerTracker; + + /** + * Helper set to avoid too much GC churn from frequent calls to + * {@link #maybeReportNewChargingStateLocked()}. + */ + private final ArraySet<JobStatus> mChangedJobs = new ArraySet<>(); public BatteryController(JobSchedulerService service) { super(service); - mChargeTracker = new ChargingTracker(); - mChargeTracker.startTracking(); + mPowerTracker = new PowerTracker(); + mPowerTracker.startTracking(); } @Override @@ -68,9 +79,16 @@ public final class BatteryController extends RestrictingController { final long nowElapsed = sElapsedRealtimeClock.millis(); mTrackedTasks.add(taskStatus); taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY); - taskStatus.setChargingConstraintSatisfied(nowElapsed, mChargeTracker.isOnStablePower()); - taskStatus.setBatteryNotLowConstraintSatisfied( - nowElapsed, mChargeTracker.isBatteryNotLow()); + if (taskStatus.hasChargingConstraint()) { + if (hasTopExemptionLocked(taskStatus)) { + taskStatus.setChargingConstraintSatisfied(nowElapsed, + mPowerTracker.isPowerConnected()); + } else { + taskStatus.setChargingConstraintSatisfied(nowElapsed, + mService.isBatteryCharging() && mService.isBatteryNotLow()); + } + } + taskStatus.setBatteryNotLowConstraintSatisfied(nowElapsed, mService.isBatteryNotLow()); } } @@ -80,9 +98,32 @@ public final class BatteryController extends RestrictingController { } @Override + @GuardedBy("mLock") + public void prepareForExecutionLocked(JobStatus jobStatus) { + if (DEBUG) { + Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); + } + + final int uid = jobStatus.getSourceUid(); + if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) { + if (DEBUG) { + Slog.d(TAG, jobStatus.toShortString() + " is top started job"); + } + mTopStartedJobs.add(jobStatus); + } + } + + @Override + @GuardedBy("mLock") + public void unprepareFromExecutionLocked(JobStatus jobStatus) { + mTopStartedJobs.remove(jobStatus); + } + + @Override public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) { mTrackedTasks.remove(taskStatus); + mTopStartedJobs.remove(taskStatus); } } @@ -93,141 +134,127 @@ public final class BatteryController extends RestrictingController { } } + @Override + @GuardedBy("mLock") + public void onBatteryStateChangedLocked() { + // Update job bookkeeping out of band. + JobSchedulerBackgroundThread.getHandler().post(() -> { + synchronized (mLock) { + maybeReportNewChargingStateLocked(); + } + }); + } + + @Override + @GuardedBy("mLock") + public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { + if (prevBias == JobInfo.BIAS_TOP_APP || newBias == JobInfo.BIAS_TOP_APP) { + maybeReportNewChargingStateLocked(); + } + } + + @GuardedBy("mLock") + private boolean hasTopExemptionLocked(@NonNull JobStatus taskStatus) { + return mService.getUidBias(taskStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP + || mTopStartedJobs.contains(taskStatus); + } + + @GuardedBy("mLock") private void maybeReportNewChargingStateLocked() { - final boolean stablePower = mChargeTracker.isOnStablePower(); - final boolean batteryNotLow = mChargeTracker.isBatteryNotLow(); + final boolean powerConnected = mPowerTracker.isPowerConnected(); + final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow(); + final boolean batteryNotLow = mService.isBatteryNotLow(); if (DEBUG) { - Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower); + Slog.d(TAG, "maybeReportNewChargingStateLocked: " + + powerConnected + "/" + stablePower + "/" + batteryNotLow); } final long nowElapsed = sElapsedRealtimeClock.millis(); - boolean reportChange = false; for (int i = mTrackedTasks.size() - 1; i >= 0; i--) { final JobStatus ts = mTrackedTasks.valueAt(i); - reportChange |= ts.setChargingConstraintSatisfied(nowElapsed, stablePower); - reportChange |= ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow); + if (ts.hasChargingConstraint()) { + if (hasTopExemptionLocked(ts) + && ts.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) { + // If the job started while the app was on top or the app is currently on top, + // let the job run as long as there's power connected, even if the device isn't + // officially charging. + // For user requested/initiated jobs, users may be confused when the task stops + // running even though the device is plugged in. + // Low priority jobs don't need to be exempted. + if (ts.setChargingConstraintSatisfied(nowElapsed, powerConnected)) { + mChangedJobs.add(ts); + } + } else if (ts.setChargingConstraintSatisfied(nowElapsed, stablePower)) { + mChangedJobs.add(ts); + } + } + if (ts.hasBatteryNotLowConstraint() + && ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow)) { + mChangedJobs.add(ts); + } } if (stablePower || batteryNotLow) { // If one of our conditions has been satisfied, always schedule any newly ready jobs. mStateChangedListener.onRunJobNow(null); - } else if (reportChange) { + } else if (mChangedJobs.size() > 0) { // Otherwise, just let the job scheduler know the state has changed and take care of it // as it thinks is best. - mStateChangedListener.onControllerStateChanged(mTrackedTasks); + mStateChangedListener.onControllerStateChanged(mChangedJobs); } + mChangedJobs.clear(); } - public final class ChargingTracker extends BroadcastReceiver { + private final class PowerTracker extends BroadcastReceiver { /** - * Track whether we're "charging", where charging means that we're ready to commit to - * doing work. + * Track whether there is power connected. It doesn't mean the device is charging. + * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is + * charging. */ - private boolean mCharging; - /** Keep track of whether the battery is charged enough that we want to do work. */ - private boolean mBatteryHealthy; - /** Sequence number of last broadcast. */ - private int mLastBatterySeq = -1; + private boolean mPowerConnected; - private BroadcastReceiver mMonitor; - - public ChargingTracker() { + PowerTracker() { } - public void startTracking() { + void startTracking() { IntentFilter filter = new IntentFilter(); - // Battery health. - filter.addAction(Intent.ACTION_BATTERY_LOW); - filter.addAction(Intent.ACTION_BATTERY_OKAY); - // Charging/not charging. - filter.addAction(BatteryManager.ACTION_CHARGING); - filter.addAction(BatteryManager.ACTION_DISCHARGING); + filter.addAction(Intent.ACTION_POWER_CONNECTED); + filter.addAction(Intent.ACTION_POWER_DISCONNECTED); mContext.registerReceiver(this, filter); - // Initialise tracker state. + // Initialize tracker state. BatteryManagerInternal batteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); - mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); - mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); + mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); } - public void setMonitorBatteryLocked(boolean enabled) { - if (enabled) { - if (mMonitor == null) { - mMonitor = new BroadcastReceiver() { - @Override public void onReceive(Context context, Intent intent) { - ChargingTracker.this.onReceive(context, intent); - } - }; - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - mContext.registerReceiver(mMonitor, filter); - } - } else { - if (mMonitor != null) { - mContext.unregisterReceiver(mMonitor); - mMonitor = null; - } - } - } - - public boolean isOnStablePower() { - return mCharging && mBatteryHealthy; - } - - public boolean isBatteryNotLow() { - return mBatteryHealthy; - } - - public boolean isMonitoring() { - return mMonitor != null; - } - - public int getSeq() { - return mLastBatterySeq; + boolean isPowerConnected() { + return mPowerConnected; } @Override public void onReceive(Context context, Intent intent) { - onReceiveInternal(intent); - } - - @VisibleForTesting - public void onReceiveInternal(Intent intent) { synchronized (mLock) { final String action = intent.getAction(); - if (Intent.ACTION_BATTERY_LOW.equals(action)) { + + if (Intent.ACTION_POWER_CONNECTED.equals(action)) { if (DEBUG) { - Slog.d(TAG, "Battery life too low to do work. @ " - + sElapsedRealtimeClock.millis()); + Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis()); } - // If we get this action, the battery is discharging => it isn't plugged in so - // there's no work to cancel. We track this variable for the case where it is - // charging, but hasn't been for long enough to be healthy. - mBatteryHealthy = false; - maybeReportNewChargingStateLocked(); - } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { - if (DEBUG) { - Slog.d(TAG, "Battery life healthy enough to do work. @ " - + sElapsedRealtimeClock.millis()); + if (mPowerConnected) { + return; } - mBatteryHealthy = true; - maybeReportNewChargingStateLocked(); - } else if (BatteryManager.ACTION_CHARGING.equals(action)) { + mPowerConnected = true; + } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) { if (DEBUG) { - Slog.d(TAG, "Received charging intent, fired @ " - + sElapsedRealtimeClock.millis()); + Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis()); } - mCharging = true; - maybeReportNewChargingStateLocked(); - } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { - if (DEBUG) { - Slog.d(TAG, "Disconnected from power."); + if (!mPowerConnected) { + return; } - mCharging = false; - maybeReportNewChargingStateLocked(); + mPowerConnected = false; } - mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, - mLastBatterySeq); + + maybeReportNewChargingStateLocked(); } } } @@ -235,14 +262,9 @@ public final class BatteryController extends RestrictingController { @Override public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { - pw.println("Stable power: " + mChargeTracker.isOnStablePower()); - pw.println("Not low: " + mChargeTracker.isBatteryNotLow()); - - if (mChargeTracker.isMonitoring()) { - pw.print("MONITORING: seq="); - pw.println(mChargeTracker.getSeq()); - } - pw.println(); + pw.println("Power connected: " + mPowerTracker.isPowerConnected()); + pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow())); + pw.println("Not low: " + mService.isBatteryNotLow()); for (int i = 0; i < mTrackedTasks.size(); i++) { final JobStatus js = mTrackedTasks.valueAt(i); @@ -264,14 +286,9 @@ public final class BatteryController extends RestrictingController { final long mToken = proto.start(StateControllerProto.BATTERY); proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER, - mChargeTracker.isOnStablePower()); + mService.isBatteryCharging() && mService.isBatteryNotLow()); proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW, - mChargeTracker.isBatteryNotLow()); - - proto.write(StateControllerProto.BatteryController.IS_MONITORING, - mChargeTracker.isMonitoring()); - proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER, - mChargeTracker.getSeq()); + mService.isBatteryNotLow()); for (int i = 0; i < mTrackedTasks.size(); i++) { final JobStatus js = mTrackedTasks.valueAt(i); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 524d68fb72e7..c678755f73da 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -25,18 +25,12 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.JobInfo; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; import android.net.NetworkRequest; -import android.os.BatteryManager; -import android.os.BatteryManagerInternal; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -55,6 +49,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobSchedulerService.Constants; @@ -107,8 +102,6 @@ public final class ConnectivityController extends RestrictingController implemen private final ConnectivityManager mConnManager; private final NetworkPolicyManagerInternal mNetPolicyManagerInternal; - private final ChargingTracker mChargingTracker; - /** List of tracked jobs keyed by source UID. */ @GuardedBy("mLock") private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>(); @@ -237,9 +230,6 @@ public final class ConnectivityController extends RestrictingController implemen // network changes against the active network for each UID with jobs. final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); mConnManager.registerNetworkCallback(request, mNetworkCallback); - - mChargingTracker = new ChargingTracker(); - mChargingTracker.startTracking(); } @GuardedBy("mLock") @@ -527,7 +517,7 @@ public final class ConnectivityController extends RestrictingController implemen @GuardedBy("mLock") @Override - public void onUidBiasChangedLocked(int uid, int newBias) { + public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { UidStats uidStats = mUidStats.get(uid); if (uidStats != null && uidStats.baseBias != newBias) { uidStats.baseBias = newBias; @@ -535,6 +525,17 @@ public final class ConnectivityController extends RestrictingController implemen } } + @Override + @GuardedBy("mLock") + public void onBatteryStateChangedLocked() { + // Update job bookkeeping out of band to avoid blocking broadcast progress. + JobSchedulerBackgroundThread.getHandler().post(() -> { + synchronized (mLock) { + updateTrackedJobsLocked(-1, null); + } + }); + } + private boolean isUsable(NetworkCapabilities capabilities) { return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); @@ -591,7 +592,7 @@ public final class ConnectivityController extends RestrictingController implemen // Minimum chunk size isn't defined. Check using the estimated upload/download sizes. if (capabilities.hasCapability(NET_CAPABILITY_NOT_METERED) - && mChargingTracker.isCharging()) { + && mService.isBatteryCharging()) { // We're charging and on an unmetered network. We don't have to be as conservative about // making sure the job will run within its max execution time. Let's just hope the app // supports interruptible work. @@ -1072,51 +1073,6 @@ public final class ConnectivityController extends RestrictingController implemen } } - private final class ChargingTracker extends BroadcastReceiver { - /** - * Track whether we're "charging", where charging means that we're ready to commit to - * doing work. - */ - private boolean mCharging; - - ChargingTracker() {} - - public void startTracking() { - IntentFilter filter = new IntentFilter(); - filter.addAction(BatteryManager.ACTION_CHARGING); - filter.addAction(BatteryManager.ACTION_DISCHARGING); - mContext.registerReceiver(this, filter); - - // Initialise tracker state. - final BatteryManagerInternal batteryManagerInternal = - LocalServices.getService(BatteryManagerInternal.class); - mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); - } - - public boolean isCharging() { - return mCharging; - } - - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mLock) { - final String action = intent.getAction(); - if (BatteryManager.ACTION_CHARGING.equals(action)) { - if (mCharging) { - return; - } - mCharging = true; - } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { - if (!mCharging) { - return; - } - mCharging = false; - } - updateTrackedJobsLocked(-1, null); - } - } - } - private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override public void onAvailable(Network network) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 9749c8087caf..428f2cbaefc2 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -40,7 +40,6 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArrayMap; -import android.util.SparseBooleanArray; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; @@ -81,9 +80,6 @@ public class PrefetchController extends StateController { */ @GuardedBy("mLock") private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>(); - /** Cached list of UIDs in the TOP state. */ - @GuardedBy("mLock") - private final SparseBooleanArray mTopUids = new SparseBooleanArray(); private final ThresholdAlarmListener mThresholdAlarmListener; /** @@ -186,15 +182,9 @@ public class PrefetchController extends StateController { @GuardedBy("mLock") @Override - public void onUidBiasChangedLocked(int uid, int newBias) { + public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { final boolean isNowTop = newBias == JobInfo.BIAS_TOP_APP; - final boolean wasTop = mTopUids.get(uid); - if (isNowTop) { - mTopUids.put(uid, true); - } else { - // Delete entries of non-top apps so the set doesn't get too large. - mTopUids.delete(uid); - } + final boolean wasTop = prevBias == JobInfo.BIAS_TOP_APP; if (isNowTop != wasTop) { mHandler.obtainMessage(MSG_PROCESS_TOP_STATE_CHANGE, uid, 0).sendToTarget(); } @@ -314,7 +304,8 @@ public class PrefetchController extends StateController { // 3. The app is not open but has an active widget (we can't tell if a widget displays // status/data, so this assumes the prefetch job is to update the data displayed on // the widget). - final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid()); + final boolean appIsOpen = + mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP; final boolean satisfied; if (!appIsOpen) { final int userId = jobStatus.getSourceUserId(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index c147ef83dcf0..65e1d49d1510 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -39,15 +39,11 @@ import android.app.IUidObserver; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.BatteryManager; -import android.os.BatteryManagerInternal; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -319,7 +315,6 @@ public final class QuotaController extends StateController { private final SparseLongArray mTopAppGraceCache = new SparseLongArray(); private final AlarmManager mAlarmManager; - private final ChargingTracker mChargeTracker; private final QcHandler mHandler; private final QcConstants mQcConstants; @@ -542,8 +537,6 @@ public final class QuotaController extends StateController { @NonNull ConnectivityController connectivityController) { super(service); mHandler = new QcHandler(mContext.getMainLooper()); - mChargeTracker = new ChargingTracker(); - mChargeTracker.startTracking(); mAlarmManager = mContext.getSystemService(AlarmManager.class); mQcConstants = new QcConstants(); mBackgroundJobsController = backgroundJobsController; @@ -705,6 +698,11 @@ public final class QuotaController extends StateController { mTopAppTrackers.delete(userId); } + @Override + public void onBatteryStateChangedLocked() { + handleNewChargingStateLocked(); + } + /** Drop all historical stats and stop tracking any active sessions for the specified app. */ public void clearAppStatsLocked(int userId, @NonNull String packageName) { mTrackedJobs.delete(userId, packageName); @@ -766,7 +764,7 @@ public final class QuotaController extends StateController { if (!jobStatus.shouldTreatAsExpeditedJob()) { // If quota is currently "free", then the job can run for the full amount of time, // regardless of bucket (hence using charging instead of isQuotaFreeLocked()). - if (mChargeTracker.isChargingLocked() + if (mService.isBatteryCharging() || mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { @@ -777,7 +775,7 @@ public final class QuotaController extends StateController { } // Expedited job. - if (mChargeTracker.isChargingLocked()) { + if (mService.isBatteryCharging()) { return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; } if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) { @@ -864,7 +862,7 @@ public final class QuotaController extends StateController { @GuardedBy("mLock") private boolean isQuotaFreeLocked(final int standbyBucket) { // Quota constraint is not enforced while charging. - if (mChargeTracker.isChargingLocked()) { + if (mService.isBatteryCharging()) { // Restricted jobs require additional constraints when charging, so don't immediately // mark quota as free when charging. return standbyBucket != RESTRICTED_INDEX; @@ -1538,15 +1536,19 @@ public final class QuotaController extends StateController { private void handleNewChargingStateLocked() { mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(), - mChargeTracker.isChargingLocked()); + mService.isBatteryCharging()); if (DEBUG) { - Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isChargingLocked()); + Slog.d(TAG, "handleNewChargingStateLocked: " + mService.isBatteryCharging()); } // Deal with Timers first. mEJPkgTimers.forEach(mTimerChargingUpdateFunctor); mPkgTimers.forEach(mTimerChargingUpdateFunctor); - // Now update jobs. - maybeUpdateAllConstraintsLocked(); + // Now update jobs out of band so broadcast processing can proceed. + JobSchedulerBackgroundThread.getHandler().post(() -> { + synchronized (mLock) { + maybeUpdateAllConstraintsLocked(); + } + }); } private void maybeUpdateAllConstraintsLocked() { @@ -1811,58 +1813,6 @@ public final class QuotaController extends StateController { return false; } - private final class ChargingTracker extends BroadcastReceiver { - /** - * Track whether we're charging. This has a slightly different definition than that of - * BatteryController. - */ - @GuardedBy("mLock") - private boolean mCharging; - - ChargingTracker() { - } - - public void startTracking() { - IntentFilter filter = new IntentFilter(); - - // Charging/not charging. - filter.addAction(BatteryManager.ACTION_CHARGING); - filter.addAction(BatteryManager.ACTION_DISCHARGING); - mContext.registerReceiver(this, filter); - - // Initialise tracker state. - BatteryManagerInternal batteryManagerInternal = - LocalServices.getService(BatteryManagerInternal.class); - mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); - } - - @GuardedBy("mLock") - public boolean isChargingLocked() { - return mCharging; - } - - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mLock) { - final String action = intent.getAction(); - if (BatteryManager.ACTION_CHARGING.equals(action)) { - if (DEBUG) { - Slog.d(TAG, "Received charging intent, fired @ " - + sElapsedRealtimeClock.millis()); - } - mCharging = true; - handleNewChargingStateLocked(); - } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { - if (DEBUG) { - Slog.d(TAG, "Disconnected from power."); - } - mCharging = false; - handleNewChargingStateLocked(); - } - } - } - } - @VisibleForTesting static final class TimingSession { // Start timestamp in elapsed realtime timebase. @@ -2127,6 +2077,12 @@ public final class QuotaController extends StateController { final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid); final boolean hasTopAppExemption = !mRegularJobTimer && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed); + if (DEBUG) { + Slog.d(TAG, "quotaFree=" + isQuotaFreeLocked(standbyBucket) + + " isFG=" + mForegroundUids.get(mUid) + + " tempEx=" + hasTempAllowlistExemption + + " topEx=" + hasTopAppExemption); + } return !isQuotaFreeLocked(standbyBucket) && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption && !hasTopAppExemption; @@ -3938,7 +3894,6 @@ public final class QuotaController extends StateController { public void dumpControllerStateLocked(final IndentingPrintWriter pw, final Predicate<JobStatus> predicate) { pw.println("Is enabled: " + mIsEnabled); - pw.println("Is charging: " + mChargeTracker.isChargingLocked()); pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis()); pw.println(); @@ -4116,7 +4071,7 @@ public final class QuotaController extends StateController { final long mToken = proto.start(StateControllerProto.QUOTA); proto.write(StateControllerProto.QuotaController.IS_CHARGING, - mChargeTracker.isChargingLocked()); + mService.isBatteryCharging()); proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME, sElapsedRealtimeClock.millis()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java index 5e73f42ff1cb..2a2d602b24bf 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java @@ -133,11 +133,18 @@ public abstract class StateController { } /** + * Called when the battery status changes. + */ + @GuardedBy("mLock") + public void onBatteryStateChangedLocked() { + } + + /** * Called when a UID's base bias has changed. The more positive the bias, the more * important the UID is. */ @GuardedBy("mLock") - public void onUidBiasChangedLocked(int uid, int newBias) { + public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { } protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) { diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 8b175127373a..4de8ec8d1241 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -42,6 +42,7 @@ import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; @@ -49,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.CollectionUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; @@ -80,7 +82,7 @@ public class AppIdleHistory { private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>(); private static final long ONE_MINUTE = 60 * 1000; - private static final int STANDBY_BUCKET_UNKNOWN = -1; + static final int STANDBY_BUCKET_UNKNOWN = -1; /** * The bucket beyond which apps are considered idle. Any apps in this bucket or lower are @@ -88,10 +90,35 @@ public class AppIdleHistory { */ static final int IDLE_BUCKET_CUTOFF = STANDBY_BUCKET_RARE; + /** Initial version of the xml containing the app idle stats ({@link #APP_IDLE_FILENAME}). */ + private static final int XML_VERSION_INITIAL = 0; + /** + * Allowed writing expiry times for any standby bucket instead of only active and working set. + * In previous version, we used to specify expiry times for active and working set as + * attributes: + * <pre> + * <package activeTimeoutTime="..." workingSetTimeoutTime="..." /> + * </pre> + * In this version, it is changed to: + * <pre> + * <package> + * <expiryTimes> + * <item bucket="..." expiry="..." /> + * <item bucket="..." expiry="..." /> + * </expiryTimes> + * </package> + * </pre> + */ + private static final int XML_VERSION_ADD_BUCKET_EXPIRY_TIMES = 1; + /** Current version */ + private static final int XML_VERSION_CURRENT = XML_VERSION_ADD_BUCKET_EXPIRY_TIMES; + @VisibleForTesting static final String APP_IDLE_FILENAME = "app_idle_stats.xml"; private static final String TAG_PACKAGES = "packages"; private static final String TAG_PACKAGE = "package"; + private static final String TAG_BUCKET_EXPIRY_TIMES = "expiryTimes"; + private static final String TAG_ITEM = "item"; private static final String ATTR_NAME = "name"; // Screen on timebase time when app was last used private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; @@ -111,6 +138,10 @@ public class AppIdleHistory { private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime"; // The time when the forced working_set state can be overridden. private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime"; + // The standby bucket value + private static final String ATTR_BUCKET = "bucket"; + // The time when the forced bucket state can be overridde. + private static final String ATTR_EXPIRY_TIME = "expiry"; // Elapsed timebase time when the app was last marked for restriction. private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED = "lastRestrictionAttemptElapsedTime"; @@ -119,6 +150,8 @@ public class AppIdleHistory { "lastRestrictionAttemptReason"; // The next estimated launch time of the app, in ms since epoch. private static final String ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME = "nextEstimatedAppLaunchTime"; + // Version of the xml file. + private static final String ATTR_VERSION = "version"; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration @@ -158,15 +191,10 @@ public class AppIdleHistory { // The estimated time the app will be launched next, in milliseconds since epoch. @CurrentTimeMillisLong long nextEstimatedLaunchTime; - // When should the bucket active state timeout, in elapsed timebase, if greater than - // lastUsedElapsedTime. - // This is used to keep the app in a high bucket regardless of other timeouts and - // predictions. - long bucketActiveTimeoutTime; - // If there's a forced working_set state, this is when it times out. This can be sitting - // under any active state timeout, so that it becomes applicable after the active state - // timeout expires. - long bucketWorkingSetTimeoutTime; + // Contains standby buckets that apps were forced into and the corresponding expiry times + // (in elapsed timebase) for each bucket state. App will stay in the highest bucket until + // it's expiry time is elapsed and will be moved to the next highest bucket. + SparseLongArray bucketExpiryTimesMs; // The last time an agent attempted to put the app into the RESTRICTED bucket. long lastRestrictAttemptElapsedTime; // The last reason the app was marked to be put into the RESTRICTED bucket. @@ -249,21 +277,24 @@ public class AppIdleHistory { } /** - * Mark the app as used and update the bucket if necessary. If there is a timeout specified + * Mark the app as used and update the bucket if necessary. If there is a expiry time specified * that's in the future, then the usage event is temporary and keeps the app in the specified - * bucket at least until the timeout is reached. This can be used to keep the app in an + * bucket at least until the expiry time is reached. This can be used to keep the app in an * elevated bucket for a while until some important task gets to run. + * * @param appUsageHistory the usage record for the app being updated * @param packageName name of the app being updated, for logging purposes * @param newBucket the bucket to set the app to * @param usageReason the sub-reason for usage, one of REASON_SUB_USAGE_* - * @param elapsedRealtime mark as used time if non-zero - * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used - * with bucket values of ACTIVE and WORKING_SET. + * @param nowElapsedRealtimeMs mark as used time if non-zero (in + * {@link SystemClock#elapsedRealtime()} time base) + * @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in + * {@link SystemClock#elapsedRealtime()} time base) * @return {@code appUsageHistory} */ AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId, - int newBucket, int usageReason, long elapsedRealtime, long timeout) { + int newBucket, int usageReason, + long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) { int bucketingReason = REASON_MAIN_USAGE | usageReason; final boolean isUserUsage = isUserUsage(bucketingReason); @@ -274,30 +305,27 @@ public class AppIdleHistory { newBucket = STANDBY_BUCKET_RESTRICTED; bucketingReason = appUsageHistory.bucketingReason; } else { - // Set the timeout if applicable - if (timeout > elapsedRealtime) { + // Set the expiry time if applicable + if (expiryElapsedRealtimeMs > nowElapsedRealtimeMs) { // Convert to elapsed timebase - final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot); - if (newBucket == STANDBY_BUCKET_ACTIVE) { - appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime, - appUsageHistory.bucketActiveTimeoutTime); - } else if (newBucket == STANDBY_BUCKET_WORKING_SET) { - appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime, - appUsageHistory.bucketWorkingSetTimeoutTime); - } else { - throw new IllegalArgumentException("Cannot set a timeout on bucket=" - + newBucket); + final long expiryTimeMs = getElapsedTime(expiryElapsedRealtimeMs); + if (appUsageHistory.bucketExpiryTimesMs == null) { + appUsageHistory.bucketExpiryTimesMs = new SparseLongArray(); } + final long currentExpiryTimeMs = appUsageHistory.bucketExpiryTimesMs.get(newBucket); + appUsageHistory.bucketExpiryTimesMs.put(newBucket, + Math.max(expiryTimeMs, currentExpiryTimeMs)); + removeElapsedExpiryTimes(appUsageHistory, getElapsedTime(nowElapsedRealtimeMs)); } } - if (elapsedRealtime != 0) { + if (nowElapsedRealtimeMs != 0) { appUsageHistory.lastUsedElapsedTime = mElapsedDuration - + (elapsedRealtime - mElapsedSnapshot); + + (nowElapsedRealtimeMs - mElapsedSnapshot); if (isUserUsage) { appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime; } - appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); + appUsageHistory.lastUsedScreenTime = getScreenOnTime(nowElapsedRealtimeMs); } if (appUsageHistory.currentBucket > newBucket) { @@ -309,26 +337,41 @@ public class AppIdleHistory { return appUsageHistory; } + private void removeElapsedExpiryTimes(AppUsageHistory appUsageHistory, long elapsedTimeMs) { + if (appUsageHistory.bucketExpiryTimesMs == null) { + return; + } + for (int i = appUsageHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) { + if (appUsageHistory.bucketExpiryTimesMs.valueAt(i) < elapsedTimeMs) { + appUsageHistory.bucketExpiryTimesMs.removeAt(i); + } + } + } + /** - * Mark the app as used and update the bucket if necessary. If there is a timeout specified + * Mark the app as used and update the bucket if necessary. If there is a expiry time specified * that's in the future, then the usage event is temporary and keeps the app in the specified - * bucket at least until the timeout is reached. This can be used to keep the app in an + * bucket at least until the expiry time is reached. This can be used to keep the app in an * elevated bucket for a while until some important task gets to run. - * @param packageName - * @param userId + * + * @param packageName package name of the app the usage is reported for + * @param userId user that the app is running in * @param newBucket the bucket to set the app to * @param usageReason sub reason for usage - * @param nowElapsed mark as used time if non-zero - * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used - * with bucket values of ACTIVE and WORKING_SET. - * @return + * @param nowElapsedRealtimeMs mark as used time if non-zero (in + * {@link SystemClock#elapsedRealtime()} time base). + * @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in + * {@link SystemClock#elapsedRealtime()} time base). + * @return the {@link AppUsageHistory} corresponding to the {@code packageName} + * and {@code userId}. */ public AppUsageHistory reportUsage(String packageName, int userId, int newBucket, - int usageReason, long nowElapsed, long timeout) { + int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); - AppUsageHistory history = getPackageHistory(userHistory, packageName, nowElapsed, true); - return reportUsage(history, packageName, userId, newBucket, usageReason, nowElapsed, - timeout); + AppUsageHistory history = getPackageHistory(userHistory, packageName, + nowElapsedRealtimeMs, true); + return reportUsage(history, packageName, userId, newBucket, usageReason, + nowElapsedRealtimeMs, expiryElapsedRealtimeMs); } private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) { @@ -383,7 +426,7 @@ public class AppIdleHistory { } public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, - int bucket, int reason, boolean resetTimeout) { + int bucket, int reason, boolean resetExpiryTimes) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, elapsedRealtime, true); @@ -397,9 +440,8 @@ public class AppIdleHistory { appUsageHistory.lastPredictedTime = elapsed; appUsageHistory.lastPredictedBucket = bucket; } - if (resetTimeout) { - appUsageHistory.bucketActiveTimeoutTime = elapsed; - appUsageHistory.bucketWorkingSetTimeoutTime = elapsed; + if (resetExpiryTimes && appUsageHistory.bucketExpiryTimesMs != null) { + appUsageHistory.bucketExpiryTimesMs.clear(); } if (changed) { logAppStandbyBucketChanged(packageName, userId, bucket, reason); @@ -622,6 +664,17 @@ public class AppIdleHistory { } @VisibleForTesting + long getBucketExpiryTimeMs(String packageName, int userId, int bucket, long elapsedRealtimeMs) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtimeMs, true); + if (appUsageHistory.bucketExpiryTimesMs == null) { + return 0; + } + return appUsageHistory.bucketExpiryTimesMs.get(bucket, 0); + } + + @VisibleForTesting File getUserFile(int userId) { return new File(new File(new File(mStorageDir, "users"), Integer.toString(userId)), APP_IDLE_FILENAME); @@ -657,6 +710,7 @@ public class AppIdleHistory { if (!parser.getName().equals(TAG_PACKAGES)) { return; } + final int version = getIntValue(parser, ATTR_VERSION, XML_VERSION_INITIAL); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG) { final String name = parser.getName(); @@ -681,10 +735,6 @@ public class AppIdleHistory { parser.getAttributeValue(null, ATTR_BUCKETING_REASON); appUsageHistory.lastJobRunTime = getLongValue(parser, ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE); - appUsageHistory.bucketActiveTimeoutTime = getLongValue(parser, - ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L); - appUsageHistory.bucketWorkingSetTimeoutTime = getLongValue(parser, - ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L); appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT; if (bucketingReason != null) { try { @@ -710,6 +760,26 @@ public class AppIdleHistory { ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 0); appUsageHistory.lastInformedBucket = -1; userHistory.put(packageName, appUsageHistory); + + if (version >= XML_VERSION_ADD_BUCKET_EXPIRY_TIMES) { + final int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (TAG_BUCKET_EXPIRY_TIMES.equals(parser.getName())) { + readBucketExpiryTimes(parser, appUsageHistory); + } + } + } else { + final long bucketActiveTimeoutTime = getLongValue(parser, + ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L); + final long bucketWorkingSetTimeoutTime = getLongValue(parser, + ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L); + if (bucketActiveTimeoutTime != 0 || bucketWorkingSetTimeoutTime != 0) { + insertBucketExpiryTime(appUsageHistory, + STANDBY_BUCKET_ACTIVE, bucketActiveTimeoutTime); + insertBucketExpiryTime(appUsageHistory, + STANDBY_BUCKET_WORKING_SET, bucketWorkingSetTimeoutTime); + } + } } } } @@ -720,21 +790,53 @@ public class AppIdleHistory { } } + private void readBucketExpiryTimes(XmlPullParser parser, AppUsageHistory appUsageHistory) + throws IOException, XmlPullParserException { + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_ITEM.equals(parser.getName())) { + final int bucket = getIntValue(parser, ATTR_BUCKET, STANDBY_BUCKET_UNKNOWN); + if (bucket == STANDBY_BUCKET_UNKNOWN) { + Slog.e(TAG, "Error reading the buckets expiry times"); + continue; + } + final long expiryTimeMs = getLongValue(parser, ATTR_EXPIRY_TIME, 0 /* default */); + insertBucketExpiryTime(appUsageHistory, bucket, expiryTimeMs); + } + } + } + + private void insertBucketExpiryTime(AppUsageHistory appUsageHistory, + int bucket, long expiryTimeMs) { + if (expiryTimeMs == 0) { + return; + } + if (appUsageHistory.bucketExpiryTimesMs == null) { + appUsageHistory.bucketExpiryTimesMs = new SparseLongArray(); + } + appUsageHistory.bucketExpiryTimesMs.put(bucket, expiryTimeMs); + } + private long getLongValue(XmlPullParser parser, String attrName, long defValue) { String value = parser.getAttributeValue(null, attrName); if (value == null) return defValue; return Long.parseLong(value); } + private int getIntValue(XmlPullParser parser, String attrName, int defValue) { + String value = parser.getAttributeValue(null, attrName); + if (value == null) return defValue; + return Integer.parseInt(value); + } - public void writeAppIdleTimes() { + public void writeAppIdleTimes(long elapsedRealtimeMs) { final int size = mIdleHistory.size(); for (int i = 0; i < size; i++) { - writeAppIdleTimes(mIdleHistory.keyAt(i)); + writeAppIdleTimes(mIdleHistory.keyAt(i), elapsedRealtimeMs); } } - public void writeAppIdleTimes(int userId) { + public void writeAppIdleTimes(int userId, long elapsedRealtimeMs) { FileOutputStream fos = null; AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); try { @@ -747,7 +849,9 @@ public class AppIdleHistory { xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, TAG_PACKAGES); + xml.attribute(null, ATTR_VERSION, String.valueOf(XML_VERSION_CURRENT)); + final long elapsedTimeMs = getElapsedTime(elapsedRealtimeMs); ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId); final int N = userHistory.size(); for (int i = 0; i < N; i++) { @@ -772,14 +876,6 @@ public class AppIdleHistory { Integer.toString(history.currentBucket)); xml.attribute(null, ATTR_BUCKETING_REASON, Integer.toHexString(history.bucketingReason)); - if (history.bucketActiveTimeoutTime > 0) { - xml.attribute(null, ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, Long.toString(history - .bucketActiveTimeoutTime)); - } - if (history.bucketWorkingSetTimeoutTime > 0) { - xml.attribute(null, ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, Long.toString(history - .bucketWorkingSetTimeoutTime)); - } if (history.lastJobRunTime != Long.MIN_VALUE) { xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history .lastJobRunTime)); @@ -794,6 +890,22 @@ public class AppIdleHistory { xml.attribute(null, ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, Long.toString(history.nextEstimatedLaunchTime)); } + if (history.bucketExpiryTimesMs != null) { + xml.startTag(null, TAG_BUCKET_EXPIRY_TIMES); + for (int j = 0; j < history.bucketExpiryTimesMs.size(); ++j) { + final long expiryTimeMs = history.bucketExpiryTimesMs.valueAt(j); + // Skip writing to disk if the expiry time already elapsed. + if (expiryTimeMs < elapsedTimeMs) { + continue; + } + final int bucket = history.bucketExpiryTimesMs.keyAt(j); + xml.startTag(null, TAG_ITEM); + xml.attribute(null, ATTR_BUCKET, String.valueOf(bucket)); + xml.attribute(null, ATTR_EXPIRY_TIME, String.valueOf(expiryTimeMs)); + xml.endTag(null, TAG_ITEM); + } + xml.endTag(null, TAG_BUCKET_EXPIRY_TIMES); + } xml.endTag(null, TAG_PACKAGE); } @@ -846,12 +958,7 @@ public class AppIdleHistory { TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); idpw.print(" lastPred="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw); - idpw.print(" activeLeft="); - TimeUtils.formatDuration(appUsageHistory.bucketActiveTimeoutTime - totalElapsedTime, - idpw); - idpw.print(" wsLeft="); - TimeUtils.formatDuration(appUsageHistory.bucketWorkingSetTimeoutTime - totalElapsedTime, - idpw); + dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime); idpw.print(" lastJob="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) { @@ -877,4 +984,25 @@ public class AppIdleHistory { idpw.println(); idpw.decreaseIndent(); } + + private void dumpBucketExpiryTimes(IndentingPrintWriter idpw, AppUsageHistory appUsageHistory, + long totalElapsedTimeMs) { + idpw.print(" expiryTimes="); + if (appUsageHistory.bucketExpiryTimesMs == null + || appUsageHistory.bucketExpiryTimesMs.size() == 0) { + idpw.print("<none>"); + return; + } + idpw.print("("); + for (int i = 0; i < appUsageHistory.bucketExpiryTimesMs.size(); ++i) { + final int bucket = appUsageHistory.bucketExpiryTimesMs.keyAt(i); + final long expiryTimeMs = appUsageHistory.bucketExpiryTimesMs.valueAt(i); + if (i != 0) { + idpw.print(","); + } + idpw.print(bucket + ":"); + TimeUtils.formatDuration(totalElapsedTimeMs - expiryTimeMs, idpw); + } + idpw.print(")"); + } } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index abbae4e8e43c..ee0995281c29 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -49,10 +49,12 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; +import static android.app.usage.UsageStatsManager.standbyBucketToString; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; +import static com.android.server.usage.AppIdleHistory.STANDBY_BUCKET_UNKNOWN; import android.annotation.CurrentTimeMillisLong; import android.annotation.NonNull; @@ -298,6 +300,11 @@ public class AppStandbyController long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_STRONG_USAGE_TIMEOUT; /** Minimum time a notification seen event should keep the bucket elevated. */ long mNotificationSeenTimeoutMillis = ConstantsObserver.DEFAULT_NOTIFICATION_TIMEOUT; + /** Minimum time a slice pinned event should keep the bucket elevated. */ + long mSlicePinnedTimeoutMillis = ConstantsObserver.DEFAULT_SLICE_PINNED_TIMEOUT; + /** The standby bucket that an app will be promoted on a notification-seen event */ + int mNotificationSeenPromotedBucket = + ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET; /** Minimum time a system update event should keep the buckets elevated. */ long mSystemUpdateUsageTimeoutMillis = ConstantsObserver.DEFAULT_SYSTEM_UPDATE_TIMEOUT; /** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */ @@ -773,7 +780,7 @@ public class AppStandbyController userId); if (DEBUG) { Slog.d(TAG, " Checking idle state for " + packageName - + " minBucket=" + minBucket); + + " minBucket=" + standbyBucketToString(minBucket)); } if (minBucket <= STANDBY_BUCKET_ACTIVE) { // No extra processing needed for ACTIVE or higher since apps can't drop into lower @@ -815,36 +822,34 @@ public class AppStandbyController newBucket = app.lastPredictedBucket; reason = REASON_MAIN_PREDICTED | REASON_SUB_PREDICTED_RESTORED; if (DEBUG) { - Slog.d(TAG, "Restored predicted newBucket = " + newBucket); + Slog.d(TAG, "Restored predicted newBucket = " + + standbyBucketToString(newBucket)); } } else { newBucket = getBucketForLocked(packageName, userId, elapsedRealtime); if (DEBUG) { - Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket); + Slog.d(TAG, "Evaluated AOSP newBucket = " + + standbyBucketToString(newBucket)); } reason = REASON_MAIN_TIMEOUT; } } - // Check if the app is within one of the timeouts for forced bucket elevation + // Check if the app is within one of the expiry times for forced bucket elevation final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); - if (newBucket >= STANDBY_BUCKET_ACTIVE - && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { - newBucket = STANDBY_BUCKET_ACTIVE; - reason = app.bucketingReason; - if (DEBUG) { - Slog.d(TAG, " Keeping at ACTIVE due to min timeout"); + final int bucketWithValidExpiryTime = getMinBucketWithValidExpiryTime(app, + newBucket, elapsedTimeAdjusted); + if (bucketWithValidExpiryTime != STANDBY_BUCKET_UNKNOWN) { + newBucket = bucketWithValidExpiryTime; + if (newBucket == STANDBY_BUCKET_ACTIVE || app.currentBucket == newBucket) { + reason = app.bucketingReason; + } else { + reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; } - } else if (newBucket >= STANDBY_BUCKET_WORKING_SET - && app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) { - newBucket = STANDBY_BUCKET_WORKING_SET; - // If it was already there, keep the reason, else assume timeout to WS - reason = (newBucket == oldBucket) - ? app.bucketingReason - : REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; if (DEBUG) { - Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); + Slog.d(TAG, " Keeping at " + standbyBucketToString(newBucket) + + " due to min timeout"); } } @@ -868,13 +873,14 @@ public class AppStandbyController newBucket = minBucket; // Leave the reason alone. if (DEBUG) { - Slog.d(TAG, "Bringing up from " + newBucket + " to " + minBucket + Slog.d(TAG, "Bringing up from " + standbyBucketToString(newBucket) + + " to " + standbyBucketToString(minBucket) + " due to min bucketing"); } } if (DEBUG) { - Slog.d(TAG, " Old bucket=" + oldBucket - + ", newBucket=" + newBucket); + Slog.d(TAG, " Old bucket=" + standbyBucketToString(oldBucket) + + ", newBucket=" + standbyBucketToString(newBucket)); } if (oldBucket != newBucket || predictionLate) { mAppIdleHistory.setAppStandbyBucket(packageName, userId, @@ -967,6 +973,7 @@ public class AppStandbyController } } + @GuardedBy("mAppIdleLock") private void reportEventLocked(String pkg, int eventType, long elapsedRealtime, int userId) { // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back // about apps that are on some kind of whitelist anyway. @@ -980,13 +987,20 @@ public class AppStandbyController final long nextCheckDelay; final int subReason = usageEventToSubReason(eventType); final int reason = REASON_MAIN_USAGE | subReason; - if (eventType == UsageEvents.Event.NOTIFICATION_SEEN - || eventType == UsageEvents.Event.SLICE_PINNED) { - // Mild usage elevates to WORKING_SET but doesn't change usage time. + if (eventType == UsageEvents.Event.NOTIFICATION_SEEN) { + // Notification-seen elevates to a higher bucket (depending on + // {@link ConstantsObserver#KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET}) but doesn't + // change usage time. mAppIdleHistory.reportUsage(appHistory, pkg, userId, - STANDBY_BUCKET_WORKING_SET, subReason, + mNotificationSeenPromotedBucket, subReason, 0, elapsedRealtime + mNotificationSeenTimeoutMillis); nextCheckDelay = mNotificationSeenTimeoutMillis; + } else if (eventType == UsageEvents.Event.SLICE_PINNED) { + // Mild usage elevates to WORKING_SET but doesn't change usage time. + mAppIdleHistory.reportUsage(appHistory, pkg, userId, + STANDBY_BUCKET_WORKING_SET, subReason, + 0, elapsedRealtime + mSlicePinnedTimeoutMillis); + nextCheckDelay = mSlicePinnedTimeoutMillis; } else if (eventType == UsageEvents.Event.SYSTEM_INTERACTION) { mAppIdleHistory.reportUsage(appHistory, pkg, userId, STANDBY_BUCKET_ACTIVE, subReason, @@ -1022,6 +1036,29 @@ public class AppStandbyController } /** + * Returns the lowest standby bucket that is better than {@code targetBucket} and has an + * valid expiry time (i.e. the expiry time is not yet elapsed). + */ + private int getMinBucketWithValidExpiryTime(AppUsageHistory usageHistory, + int targetBucket, long elapsedTimeMs) { + if (usageHistory.bucketExpiryTimesMs == null) { + return STANDBY_BUCKET_UNKNOWN; + } + final int size = usageHistory.bucketExpiryTimesMs.size(); + for (int i = 0; i < size; ++i) { + final int bucket = usageHistory.bucketExpiryTimesMs.keyAt(i); + if (targetBucket <= bucket) { + break; + } + final long expiryTimeMs = usageHistory.bucketExpiryTimesMs.valueAt(i); + if (expiryTimeMs > elapsedTimeMs) { + return bucket; + } + } + return STANDBY_BUCKET_UNKNOWN; + } + + /** * Note: don't call this with the lock held since it makes calls to other system services. */ private @NonNull List<UserHandle> getCrossProfileTargets(String pkg, int userId) { @@ -1564,23 +1601,18 @@ public class AppStandbyController // ACTIVE or WORKING_SET timeout. mAppIdleHistory.updateLastPrediction(app, elapsedTimeAdjusted, newBucket); - if (newBucket > STANDBY_BUCKET_ACTIVE - && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { - newBucket = STANDBY_BUCKET_ACTIVE; - reason = app.bucketingReason; - if (DEBUG) { - Slog.d(TAG, " Keeping at ACTIVE due to min timeout"); - } - } else if (newBucket > STANDBY_BUCKET_WORKING_SET - && app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) { - newBucket = STANDBY_BUCKET_WORKING_SET; - if (app.currentBucket != newBucket) { - reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; - } else { + final int bucketWithValidExpiryTime = getMinBucketWithValidExpiryTime(app, + newBucket, elapsedTimeAdjusted); + if (bucketWithValidExpiryTime != STANDBY_BUCKET_UNKNOWN) { + newBucket = bucketWithValidExpiryTime; + if (newBucket == STANDBY_BUCKET_ACTIVE || app.currentBucket == newBucket) { reason = app.bucketingReason; + } else { + reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; } if (DEBUG) { - Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); + Slog.d(TAG, " Keeping at " + standbyBucketToString(newBucket) + + " due to min timeout"); } } else if (newBucket == STANDBY_BUCKET_RARE && mAllowRestrictedBucket @@ -1746,7 +1778,7 @@ public class AppStandbyController @Override public void flushToDisk() { synchronized (mAppIdleLock) { - mAppIdleHistory.writeAppIdleTimes(); + mAppIdleHistory.writeAppIdleTimes(mInjector.elapsedRealtime()); mAppIdleHistory.writeAppIdleDurations(); } } @@ -1897,7 +1929,7 @@ public class AppStandbyController } } // Immediately persist defaults to disk - mAppIdleHistory.writeAppIdleTimes(userId); + mAppIdleHistory.writeAppIdleTimes(userId, elapsedRealtime); } } @@ -1964,6 +1996,12 @@ public class AppStandbyController pw.print(" mNotificationSeenTimeoutMillis="); TimeUtils.formatDuration(mNotificationSeenTimeoutMillis, pw); pw.println(); + pw.print(" mNotificationSeenPromotedBucket="); + pw.print(standbyBucketToString(mNotificationSeenPromotedBucket)); + pw.println(); + pw.print(" mSlicePinnedTimeoutMillis="); + TimeUtils.formatDuration(mSlicePinnedTimeoutMillis, pw); + pw.println(); pw.print(" mSyncAdapterTimeoutMillis="); TimeUtils.formatDuration(mSyncAdapterTimeoutMillis, pw); pw.println(); @@ -2386,6 +2424,10 @@ public class AppStandbyController private static final String KEY_STRONG_USAGE_HOLD_DURATION = "strong_usage_duration"; private static final String KEY_NOTIFICATION_SEEN_HOLD_DURATION = "notification_seen_duration"; + private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET = + "notification_seen_promoted_bucket"; + private static final String KEY_SLICE_PINNED_HOLD_DURATION = + "slice_pinned_duration"; private static final String KEY_SYSTEM_UPDATE_HOLD_DURATION = "system_update_usage_duration"; private static final String KEY_PREDICTION_TIMEOUT = "prediction_timeout"; @@ -2428,6 +2470,10 @@ public class AppStandbyController COMPRESS_TIME ? ONE_MINUTE : 1 * ONE_HOUR; public static final long DEFAULT_NOTIFICATION_TIMEOUT = COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR; + public static final long DEFAULT_SLICE_PINNED_TIMEOUT = + COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR; + public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET = + STANDBY_BUCKET_WORKING_SET; public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR; public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT = @@ -2513,6 +2559,16 @@ public class AppStandbyController KEY_NOTIFICATION_SEEN_HOLD_DURATION, DEFAULT_NOTIFICATION_TIMEOUT); break; + case KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET: + mNotificationSeenPromotedBucket = properties.getInt( + KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET, + DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET); + break; + case KEY_SLICE_PINNED_HOLD_DURATION: + mSlicePinnedTimeoutMillis = properties.getLong( + KEY_SLICE_PINNED_HOLD_DURATION, + DEFAULT_SLICE_PINNED_TIMEOUT); + break; case KEY_STRONG_USAGE_HOLD_DURATION: mStrongUsageTimeoutMillis = properties.getLong( KEY_STRONG_USAGE_HOLD_DURATION, DEFAULT_STRONG_USAGE_TIMEOUT); diff --git a/apex/media/OWNERS b/apex/media/OWNERS index bed38954a70c..2c5965c300e3 100644 --- a/apex/media/OWNERS +++ b/apex/media/OWNERS @@ -1,6 +1,5 @@ # Bug component: 1344 hdmoon@google.com -hkuang@google.com jinpark@google.com klhyun@google.com lnilsson@google.com diff --git a/apex/media/framework/jarjar_rules.txt b/apex/media/framework/jarjar_rules.txt index 1bd5b3616693..91489dcee0a1 100644 --- a/apex/media/framework/jarjar_rules.txt +++ b/apex/media/framework/jarjar_rules.txt @@ -1,4 +1,2 @@ rule com.android.modules.** android.media.internal.@1 rule com.google.android.exoplayer2.** android.media.internal.exo.@1 -rule com.google.common.** android.media.internal.guava_common.@1 -rule com.google.thirdparty.** android.media.internal.guava_thirdparty.@1 diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index 7daebd0155a1..b6f85c7eac6f 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -62,8 +62,8 @@ import com.google.android.exoplayer2.extractor.wav.WavExtractor; import com.google.android.exoplayer2.upstream.DataReader; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.ColorInfo; -import com.google.common.base.Ascii; import java.io.EOFException; import java.io.IOException; @@ -978,7 +978,7 @@ public final class MediaParser { @ParserName public static List<String> getParserNames(@NonNull MediaFormat mediaFormat) { String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); - mimeType = mimeType == null ? null : Ascii.toLowerCase(mimeType); + mimeType = mimeType == null ? null : Util.toLowerInvariant(mimeType.trim()); if (TextUtils.isEmpty(mimeType)) { // No MIME type provided. Return all. return Collections.unmodifiableList( @@ -1420,7 +1420,7 @@ public final class MediaParser { int flags = 0; TimestampAdjuster timestampAdjuster = null; if (mIgnoreTimestampOffset) { - timestampAdjuster = new TimestampAdjuster(TimestampAdjuster.MODE_NO_OFFSET); + timestampAdjuster = new TimestampAdjuster(TimestampAdjuster.DO_NOT_OFFSET); } switch (parserName) { case PARSER_NAME_MATROSKA: diff --git a/api/Android.bp b/api/Android.bp index 70f995a44c86..a22c2f6af35a 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -32,6 +32,7 @@ bootstrap_go_package { "soong", "soong-android", "soong-genrule", + "soong-java", ], srcs: ["api.go"], pluginFor: ["soong_build"], @@ -102,47 +103,38 @@ genrule { visibility: ["//visibility:public"], } -genrule { - name: "frameworks-base-api-current.txt", - srcs: [ - ":android.net.ipsec.ike{.public.api.txt}", - ":art.module.public.api{.public.api.txt}", - ":conscrypt.module.public.api{.public.api.txt}", - ":framework-appsearch{.public.api.txt}", - ":framework-connectivity{.public.api.txt}", - ":framework-connectivity-tiramisu{.public.api.txt}", - ":framework-graphics{.public.api.txt}", - ":framework-media{.public.api.txt}", - ":framework-mediaprovider{.public.api.txt}", - ":framework-nearby{.public.api.txt}", - ":framework-permission{.public.api.txt}", - ":framework-permission-s{.public.api.txt}", - ":framework-scheduling{.public.api.txt}", - ":framework-sdkextensions{.public.api.txt}", - ":framework-statsd{.public.api.txt}", - ":framework-supplementalprocess{.public.api.txt}", - ":framework-tethering{.public.api.txt}", - ":framework-uwb{.public.api.txt}", - ":framework-wifi{.public.api.txt}", - ":i18n.module.public.api{.public.api.txt}", - ":non-updatable-current.txt", - ], - out: ["current.txt"], - tools: ["metalava"], - cmd: metalava_cmd + "$(in) --api $(out)", - dists: [ - { - targets: ["droidcore"], - dir: "api", - dest: "current.txt", - }, - { - targets: ["sdk"], - dir: "apistubs/android/public/api", - dest: "android.txt", - }, +combined_apis { + name: "frameworks-base-api", + bootclasspath: [ + "android.net.ipsec.ike", + "art.module.public.api", + "conscrypt.module.public.api", + "framework-appsearch", + "framework-connectivity", + "framework-connectivity-tiramisu", + "framework-graphics", + "framework-media", + "framework-mediaprovider", + "framework-nearby", + "framework-permission", + "framework-permission-s", + "framework-scheduling", + "framework-sdkextensions", + "framework-statsd", + "framework-supplementalprocess", + "framework-tethering", + "framework-uwb", + "framework-wifi", + "i18n.module.public.api", + ], + conditional_bootclasspath: [ + "framework-supplementalapi", + ], + system_server_classpath: [ + "service-media-s", + "service-permission", + "service-supplementalprocess", ], - visibility: ["//visibility:public"], } genrule { @@ -162,120 +154,6 @@ genrule { } genrule { - name: "frameworks-base-api-current.srcjar", - srcs: [ - ":android.net.ipsec.ike{.public.stubs.source}", - ":api-stubs-docs-non-updatable", - ":art.module.public.api{.public.stubs.source}", - ":conscrypt.module.public.api{.public.stubs.source}", - ":framework-appsearch{.public.stubs.source}", - ":framework-connectivity{.public.stubs.source}", - ":framework-connectivity-tiramisu{.public.stubs.source}", - ":framework-graphics{.public.stubs.source}", - ":framework-media{.public.stubs.source}", - ":framework-mediaprovider{.public.stubs.source}", - ":framework-nearby{.public.stubs.source}", - ":framework-permission{.public.stubs.source}", - ":framework-permission-s{.public.stubs.source}", - ":framework-scheduling{.public.stubs.source}", - ":framework-sdkextensions{.public.stubs.source}", - ":framework-statsd{.public.stubs.source}", - ":framework-supplementalprocess{.public.stubs.source}", - ":framework-tethering{.public.stubs.source}", - ":framework-uwb{.public.stubs.source}", - ":framework-wifi{.public.stubs.source}", - ":i18n.module.public.api{.public.stubs.source}", - ], - out: ["current.srcjar"], - tools: ["merge_zips"], - cmd: "$(location merge_zips) $(out) $(in)", - visibility: ["//visibility:private"], // Used by make module in //development, mind. -} - -genrule { - name: "frameworks-base-api-removed.txt", - srcs: [ - ":android.net.ipsec.ike{.public.removed-api.txt}", - ":art.module.public.api{.public.removed-api.txt}", - ":conscrypt.module.public.api{.public.removed-api.txt}", - ":framework-appsearch{.public.removed-api.txt}", - ":framework-connectivity{.public.removed-api.txt}", - ":framework-connectivity-tiramisu{.public.removed-api.txt}", - ":framework-graphics{.public.removed-api.txt}", - ":framework-media{.public.removed-api.txt}", - ":framework-mediaprovider{.public.removed-api.txt}", - ":framework-nearby{.public.removed-api.txt}", - ":framework-permission{.public.removed-api.txt}", - ":framework-permission-s{.public.removed-api.txt}", - ":framework-scheduling{.public.removed-api.txt}", - ":framework-sdkextensions{.public.removed-api.txt}", - ":framework-statsd{.public.removed-api.txt}", - ":framework-supplementalprocess{.public.removed-api.txt}", - ":framework-tethering{.public.removed-api.txt}", - ":framework-uwb{.public.removed-api.txt}", - ":framework-wifi{.public.removed-api.txt}", - ":i18n.module.public.api{.public.removed-api.txt}", - ":non-updatable-removed.txt", - ], - out: ["removed.txt"], - tools: ["metalava"], - cmd: metalava_cmd + "$(in) --api $(out)", - dists: [ - { - targets: ["droidcore"], - dir: "api", - dest: "removed.txt", - }, - { - targets: ["sdk"], - dir: "apistubs/android/public/api", - dest: "removed.txt", - }, - ], -} - -genrule { - name: "frameworks-base-api-system-current.txt", - srcs: [ - ":art.module.public.api{.system.api.txt}", - ":android.net.ipsec.ike{.system.api.txt}", - ":framework-appsearch{.system.api.txt}", - ":framework-connectivity{.system.api.txt}", - ":framework-connectivity-tiramisu{.system.api.txt}", - ":framework-graphics{.system.api.txt}", - ":framework-media{.system.api.txt}", - ":framework-mediaprovider{.system.api.txt}", - ":framework-nearby{.system.api.txt}", - ":framework-permission{.system.api.txt}", - ":framework-permission-s{.system.api.txt}", - ":framework-scheduling{.system.api.txt}", - ":framework-sdkextensions{.system.api.txt}", - ":framework-statsd{.system.api.txt}", - ":framework-supplementalprocess{.system.api.txt}", - ":framework-tethering{.system.api.txt}", - ":framework-uwb{.system.api.txt}", - ":framework-wifi{.system.api.txt}", - ":non-updatable-system-current.txt", - ], - out: ["system-current.txt"], - tools: ["metalava"], - cmd: metalava_cmd + "$(in) --api $(out)", - dists: [ - { - targets: ["droidcore"], - dir: "api", - dest: "system-current.txt", - }, - { - targets: ["sdk"], - dir: "apistubs/android/system/api", - dest: "android.txt", - }, - ], - visibility: ["//visibility:public"], -} - -genrule { name: "frameworks-base-api-system-current-compat", srcs: [ ":android.api.system.latest", @@ -294,87 +172,6 @@ genrule { } genrule { - name: "frameworks-base-api-system-removed.txt", - srcs: [ - ":art.module.public.api{.system.removed-api.txt}", - ":android.net.ipsec.ike{.system.removed-api.txt}", - ":framework-appsearch{.system.removed-api.txt}", - ":framework-connectivity{.system.removed-api.txt}", - ":framework-connectivity-tiramisu{.system.removed-api.txt}", - ":framework-graphics{.system.removed-api.txt}", - ":framework-media{.system.removed-api.txt}", - ":framework-mediaprovider{.system.removed-api.txt}", - ":framework-nearby{.system.removed-api.txt}", - ":framework-permission{.system.removed-api.txt}", - ":framework-permission-s{.system.removed-api.txt}", - ":framework-scheduling{.system.removed-api.txt}", - ":framework-sdkextensions{.system.removed-api.txt}", - ":framework-statsd{.system.removed-api.txt}", - ":framework-supplementalprocess{.system.removed-api.txt}", - ":framework-tethering{.system.removed-api.txt}", - ":framework-uwb{.system.removed-api.txt}", - ":framework-wifi{.system.removed-api.txt}", - ":non-updatable-system-removed.txt", - ], - out: ["system-removed.txt"], - tools: ["metalava"], - cmd: metalava_cmd + "$(in) --api $(out)", - dists: [ - { - targets: ["droidcore"], - dir: "api", - dest: "system-removed.txt", - }, - { - targets: ["sdk"], - dir: "apistubs/android/system/api", - dest: "removed.txt", - }, - ], - visibility: ["//visibility:public"], -} - -genrule { - name: "frameworks-base-api-module-lib-current.txt", - srcs: [ - ":art.module.public.api{.module-lib.api.txt}", - ":android.net.ipsec.ike{.module-lib.api.txt}", - ":framework-appsearch{.module-lib.api.txt}", - ":framework-connectivity{.module-lib.api.txt}", - ":framework-connectivity-tiramisu{.module-lib.api.txt}", - ":framework-graphics{.module-lib.api.txt}", - ":framework-media{.module-lib.api.txt}", - ":framework-mediaprovider{.module-lib.api.txt}", - ":framework-nearby{.module-lib.api.txt}", - ":framework-permission{.module-lib.api.txt}", - ":framework-permission-s{.module-lib.api.txt}", - ":framework-scheduling{.module-lib.api.txt}", - ":framework-sdkextensions{.module-lib.api.txt}", - ":framework-statsd{.module-lib.api.txt}", - ":framework-supplementalprocess{.module-lib.api.txt}", - ":framework-tethering{.module-lib.api.txt}", - ":framework-uwb{.module-lib.api.txt}", - ":framework-wifi{.module-lib.api.txt}", - ":non-updatable-module-lib-current.txt", - ], - out: ["module-lib-current.txt"], - tools: ["metalava"], - cmd: metalava_cmd + "$(in) --api $(out)", - dists: [ - { - targets: ["droidcore"], - dir: "api", - dest: "module-lib-current.txt", - }, - { - targets: ["sdk"], - dir: "apistubs/android/module-lib/api", - dest: "android.txt", - }, - ], -} - -genrule { name: "frameworks-base-api-module-lib-current-compat", srcs: [ ":android.api.module-lib.latest", @@ -396,46 +193,6 @@ genrule { } genrule { - name: "frameworks-base-api-module-lib-removed.txt", - srcs: [ - ":art.module.public.api{.module-lib.removed-api.txt}", - ":android.net.ipsec.ike{.module-lib.removed-api.txt}", - ":framework-appsearch{.module-lib.removed-api.txt}", - ":framework-connectivity{.module-lib.removed-api.txt}", - ":framework-connectivity-tiramisu{.module-lib.removed-api.txt}", - ":framework-graphics{.module-lib.removed-api.txt}", - ":framework-media{.module-lib.removed-api.txt}", - ":framework-mediaprovider{.module-lib.removed-api.txt}", - ":framework-nearby{.module-lib.removed-api.txt}", - ":framework-permission{.module-lib.removed-api.txt}", - ":framework-permission-s{.module-lib.removed-api.txt}", - ":framework-scheduling{.module-lib.removed-api.txt}", - ":framework-sdkextensions{.module-lib.removed-api.txt}", - ":framework-statsd{.module-lib.removed-api.txt}", - ":framework-supplementalprocess{.module-lib.removed-api.txt}", - ":framework-tethering{.module-lib.removed-api.txt}", - ":framework-uwb{.module-lib.removed-api.txt}", - ":framework-wifi{.module-lib.removed-api.txt}", - ":non-updatable-module-lib-removed.txt", - ], - out: ["module-lib-removed.txt"], - tools: ["metalava"], - cmd: metalava_cmd + "$(in) --api $(out)", - dists: [ - { - targets: ["droidcore"], - dir: "api", - dest: "module-lib-removed.txt", - }, - { - targets: ["sdk"], - dir: "apistubs/android/module-lib/api", - dest: "removed.txt", - }, - ], -} - -genrule { name: "combined-removed-dex", visibility: [ "//frameworks/base/boot", @@ -451,90 +208,3 @@ genrule { out: ["combined-removed-dex.txt"], cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)", } - -genrule { - name: "frameworks-base-api-system-server-current.txt", - srcs: [ - ":service-media-s{.system-server.api.txt}", - ":service-permission{.system-server.api.txt}", - ":non-updatable-system-server-current.txt", - ], - out: ["system-server-current.txt"], - tools: ["metalava"], - cmd: metalava_cmd + "$(in) --api $(out)", - dists: [ - { - targets: ["droidcore"], - dir: "api", - dest: "system-server-current.txt", - }, - { - targets: ["sdk"], - dir: "apistubs/android/system-server/api", - dest: "android.txt", - }, - ], -} - -genrule { - name: "frameworks-base-api-system-server-removed.txt", - srcs: [ - ":service-media-s{.system-server.removed-api.txt}", - ":service-permission{.system-server.removed-api.txt}", - ":non-updatable-system-server-removed.txt", - ], - out: ["system-server-removed.txt"], - tools: ["metalava"], - cmd: metalava_cmd + "$(in) --api $(out)", - dists: [ - { - targets: ["droidcore"], - dir: "api", - dest: "system-server-removed.txt", - }, - { - targets: ["sdk"], - dir: "apistubs/android/system-server/api", - dest: "removed.txt", - }, - ], -} - -// This rule will filter classes present in the jar files of mainline modules -// from the lint database in api-versions.xml. -// This is done to reduce the number of false positive NewApi findings in -// java libraries that compile against the module SDK -genrule { - name: "api-versions-xml-public-filtered", - srcs: [ - // Note: order matters: first parameter is the full api-versions.xml - // after that the stubs files in any order - // stubs files are all modules that export API surfaces EXCEPT ART - ":framework-doc-stubs{.api_versions.xml}", - ":android.net.ipsec.ike.stubs{.jar}", - ":conscrypt.module.public.api.stubs{.jar}", - ":framework-appsearch.stubs{.jar}", - ":framework-connectivity.stubs{.jar}", - ":framework-connectivity-tiramisu.stubs{.jar}", - ":framework-graphics.stubs{.jar}", - ":framework-media.stubs{.jar}", - ":framework-mediaprovider.stubs{.jar}", - ":framework-nearby.stubs{.jar}", - ":framework-permission.stubs{.jar}", - ":framework-permission-s.stubs{.jar}", - ":framework-scheduling.stubs{.jar}", - ":framework-sdkextensions.stubs{.jar}", - ":framework-statsd.stubs{.jar}", - ":framework-supplementalprocess.stubs{.jar}", - ":framework-tethering.stubs{.jar}", - ":framework-uwb.stubs{.jar}", - ":framework-wifi.stubs{.jar}", - ":i18n.module.public.api.stubs{.jar}", - ], - out: ["api-versions-public-filtered.xml"], - tools: ["api_versions_trimmer"], - cmd: "$(location api_versions_trimmer) $(out) $(in)", - dist: { - targets: ["sdk"], - }, -} diff --git a/api/api.go b/api/api.go index 976b140f407f..4b6ebc1947e9 100644 --- a/api/api.go +++ b/api/api.go @@ -15,12 +15,19 @@ package api import ( + "sort" + "github.com/google/blueprint/proptools" "android/soong/android" "android/soong/genrule" + "android/soong/java" ) +const art = "art.module.public.api" +const conscrypt = "conscrypt.module.public.api" +const i18n = "i18n.module.public.api" + // The intention behind this soong plugin is to generate a number of "merged" // API-related modules that would otherwise require a large amount of very // similar Android.bp boilerplate to define. For example, the merged current.txt @@ -30,22 +37,12 @@ import ( // The properties of the combined_apis module type. type CombinedApisProperties struct { - // Module libraries that have public APIs - Public []string - // Module libraries that have system APIs - System []string - // Module libraries that have module_library APIs - Module_lib []string - // Module libraries that have system_server APIs - System_server []string - // ART module library. The only API library not removed from the filtered api database, because - // 1) ART apis are available by default to all modules, while other module-to-module deps are - // explicit and probably receive more scrutiny anyway - // 2) The number of ART/libcore APIs is large, so not linting them would create a large gap - // 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have - // per-module lint databases that excludes just that module's APIs. Alas, that's more - // difficult to achieve. - Art_module string + // Module libraries in the bootclasspath + Bootclasspath []string + // Module libraries on the bootclasspath if include_nonpublic_framework_api is true. + Conditional_bootclasspath []string + // Module libraries in system server + System_server_classpath []string } type CombinedApis struct { @@ -77,6 +74,19 @@ type genruleProps struct { Visibility []string } +type libraryProps struct { + Name *string + Sdk_version *string + Static_libs []string + Visibility []string +} + +type fgProps struct { + Name *string + Srcs []string + Visibility []string +} + // Struct to pass parameters for the various merged [current|removed].txt file modules we create. type MergedTxtDefinition struct { // "current.txt" or "removed.txt" @@ -105,9 +115,9 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) { props := genruleProps{} props.Name = proptools.StringPtr(ctx.ModuleName() + "-" + filename) props.Tools = []string{"metalava"} - props.Out = []string{txt.TxtFilename} + props.Out = []string{filename} props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --api $(out)") - props.Srcs = createSrcs(txt.BaseTxt, txt.Modules, txt.ModuleTag) + props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...) props.Dists = []android.Dist{ { Targets: []string{"droidcore"}, @@ -130,12 +140,36 @@ func createMergedStubsSrcjar(ctx android.LoadHookContext, modules []string) { props.Tools = []string{"merge_zips"} props.Out = []string{"current.srcjar"} props.Cmd = proptools.StringPtr("$(location merge_zips) $(out) $(in)") - props.Srcs = createSrcs(":api-stubs-docs-non-updatable", modules, "{.public.stubs.source}") + props.Srcs = append([]string{":api-stubs-docs-non-updatable"}, createSrcs(modules, "{.public.stubs.source}")...) props.Visibility = []string{"//visibility:private"} // Used by make module in //development, mind ctx.CreateModule(genrule.GenRuleFactory, &props) } +// This produces the same annotations.zip as framework-doc-stubs, but by using +// outputs from individual modules instead of all the source code. +func createMergedAnnotations(ctx android.LoadHookContext, modules []string) { + // Conscrypt and i18n currently do not enable annotations + modules = removeAll(modules, []string{conscrypt, i18n}) + props := genruleProps{} + props.Name = proptools.StringPtr("sdk-annotations.zip") + props.Tools = []string{"merge_annotation_zips", "soong_zip"} + props.Out = []string{"annotations.zip"} + props.Cmd = proptools.StringPtr("$(location merge_annotation_zips) $(genDir)/out $(in) && " + + "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out") + props.Srcs = append([]string{":android-non-updatable-doc-stubs{.annotations.zip}"}, createSrcs(modules, "{.public.annotations.zip}")...) + ctx.CreateModule(genrule.GenRuleFactory, &props) +} + func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) { + // For the filtered api versions, we prune all APIs except art module's APIs. because + // 1) ART apis are available by default to all modules, while other module-to-module deps are + // explicit and probably receive more scrutiny anyway + // 2) The number of ART/libcore APIs is large, so not linting them would create a large gap + // 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have + // per-module lint databases that excludes just that module's APIs. Alas, that's more + // difficult to achieve. + modules = remove(modules, art) + props := genruleProps{} props.Name = proptools.StringPtr("api-versions-xml-public-filtered") props.Tools = []string{"api_versions_trimmer"} @@ -144,59 +178,62 @@ func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) { // Note: order matters: first parameter is the full api-versions.xml // after that the stubs files in any order // stubs files are all modules that export API surfaces EXCEPT ART - props.Srcs = createSrcs(":framework-doc-stubs{.api_versions.xml}", modules, ".stubs{.jar}") + props.Srcs = append([]string{":framework-doc-stubs{.api_versions.xml}"}, createSrcs(modules, ".stubs{.jar}")...) props.Dists = []android.Dist{{Targets: []string{"sdk"}}} ctx.CreateModule(genrule.GenRuleFactory, &props) } -func createSrcs(base string, modules []string, tag string) []string { - a := make([]string, 0, len(modules)+1) - a = append(a, base) - for _, module := range modules { - a = append(a, ":"+module+tag) - } - return a +func createMergedModuleLibStubs(ctx android.LoadHookContext, modules []string) { + // The user of this module compiles against the "core" SDK, so remove core libraries to avoid dupes. + modules = removeAll(modules, []string{art, conscrypt, i18n}) + props := libraryProps{} + props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api") + props.Static_libs = transformArray(modules, "", ".stubs.module_lib") + props.Sdk_version = proptools.StringPtr("module_current") + props.Visibility = []string{"//frameworks/base"} + ctx.CreateModule(java.LibraryFactory, &props) } -func remove(s []string, v string) []string { - s2 := make([]string, 0, len(s)) - for _, sv := range s { - if sv != v { - s2 = append(s2, sv) - } - } - return s2 +func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []string) { + props := fgProps{} + props.Name = proptools.StringPtr("all-modules-public-stubs-source") + props.Srcs = createSrcs(modules, "{.public.stubs.source}") + props.Visibility = []string{"//frameworks/base"} + ctx.CreateModule(android.FileGroupFactory, &props) } -func createMergedTxts(ctx android.LoadHookContext, props CombinedApisProperties) { +func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) { var textFiles []MergedTxtDefinition + // Two module libraries currently do not support @SystemApi so only have the public scope. + bcpWithSystemApi := removeAll(bootclasspath, []string{conscrypt, i18n}) + tagSuffix := []string{".api.txt}", ".removed-api.txt}"} for i, f := range []string{"current.txt", "removed.txt"} { textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, BaseTxt: ":non-updatable-" + f, - Modules: props.Public, + Modules: bootclasspath, ModuleTag: "{.public" + tagSuffix[i], Scope: "public", }) textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, BaseTxt: ":non-updatable-system-" + f, - Modules: props.System, + Modules: bcpWithSystemApi, ModuleTag: "{.system" + tagSuffix[i], Scope: "system", }) textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, BaseTxt: ":non-updatable-module-lib-" + f, - Modules: props.Module_lib, + Modules: bcpWithSystemApi, ModuleTag: "{.module-lib" + tagSuffix[i], Scope: "module-lib", }) textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, BaseTxt: ":non-updatable-system-server-" + f, - Modules: props.System_server, + Modules: system_server_classpath, ModuleTag: "{.system-server" + tagSuffix[i], Scope: "system-server", }) @@ -207,12 +244,22 @@ func createMergedTxts(ctx android.LoadHookContext, props CombinedApisProperties) } func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) { - createMergedTxts(ctx, a.properties) + bootclasspath := a.properties.Bootclasspath + if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") { + bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...) + sort.Strings(bootclasspath) + } + createMergedTxts(ctx, bootclasspath, a.properties.System_server_classpath) + + createMergedStubsSrcjar(ctx, bootclasspath) - createMergedStubsSrcjar(ctx, a.properties.Public) + createMergedModuleLibStubs(ctx, bootclasspath) - // For the filtered api versions, we prune all APIs except art module's APIs. - createFilteredApiVersions(ctx, remove(a.properties.Public, a.properties.Art_module)) + createMergedAnnotations(ctx, bootclasspath) + + createFilteredApiVersions(ctx, bootclasspath) + + createPublicStubsSourceFilegroup(ctx, bootclasspath) } func combinedApisModuleFactory() android.Module { @@ -222,3 +269,36 @@ func combinedApisModuleFactory() android.Module { android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) }) return module } + +// Various utility methods below. + +// Creates an array of ":<m><tag>" for each m in <modules>. +func createSrcs(modules []string, tag string) []string { + return transformArray(modules, ":", tag) +} + +// Creates an array of "<prefix><m><suffix>", for each m in <modules>. +func transformArray(modules []string, prefix, suffix string) []string { + a := make([]string, 0, len(modules)) + for _, module := range modules { + a = append(a, prefix+module+suffix) + } + return a +} + +func removeAll(s []string, vs []string) []string { + for _, v := range vs { + s = remove(s, v) + } + return s +} + +func remove(s []string, v string) []string { + s2 := make([]string, 0, len(s)) + for _, sv := range s { + if sv != v { + s2 = append(s2, sv) + } + } + return s2 +} diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index 13bf197aa9dc..fbb99d2aea89 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -43,7 +43,7 @@ enum { #define DEFAULT_DELAY_NS (1000000000LL) -#define DEFAULT_BYTES_SIZE_LIMIT (96 * 1024 * 1024) // 96MB +#define DEFAULT_BYTES_SIZE_LIMIT (400 * 1024 * 1024) // 400MB #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day // Skip these sections (for dumpstate only) diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp index 23d80d7953b7..dd33fdfc28c3 100644 --- a/cmds/incidentd/src/WorkDirectory.cpp +++ b/cmds/incidentd/src/WorkDirectory.cpp @@ -533,7 +533,7 @@ status_t ReportFile::load_envelope_impl(bool cleanup) { WorkDirectory::WorkDirectory() :mDirectory("/data/misc/incidents"), mMaxFileCount(100), - mMaxDiskUsageBytes(100 * 1024 * 1024) { // Incident reports can take up to 100MB on disk. + mMaxDiskUsageBytes(400 * 1024 * 1024) { // Incident reports can take up to 400MB on disk. // TODO: Should be a flag. create_directory(mDirectory.c_str()); } diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java index 260c8a47ea3c..b384e702ac4e 100644 --- a/cmds/sm/src/com/android/commands/sm/Sm.java +++ b/cmds/sm/src/com/android/commands/sm/Sm.java @@ -259,7 +259,8 @@ public final class Sm { public void runDisableAppDataIsolation() throws RemoteException { if (!SystemProperties.getBoolean( ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) { - throw new IllegalStateException("Storage app data isolation is not enabled."); + System.err.println("Storage app data isolation is not enabled."); + return; } final String pkgName = nextArg(); final int pid = Integer.parseInt(nextArg()); diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 9c044b5e632e..52f883b5fbb7 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -71,6 +71,8 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer"; private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression"; private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls"; + private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS = + "cleanup-orphan-phone-accounts"; private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode"; /** @@ -125,6 +127,9 @@ public final class Telecom extends BaseCommand { + " provider after a call to emergency services.\n" + "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have" + " gotten wedged in Telecom.\n" + + "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that" + + " no longer have a valid UserHandle or accounts that no longer belongs to an" + + " installed package.\n" + "usage: telecom set-emer-phone-account-filter <PACKAGE>\n" + "\n" + "telecom set-phone-account-enabled: Enables the given phone account, if it has" @@ -227,6 +232,9 @@ public final class Telecom extends BaseCommand { case COMMAND_CLEANUP_STUCK_CALLS: runCleanupStuckCalls(); break; + case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS: + runCleanupOrphanPhoneAccounts(); + break; case COMMAND_RESET_CAR_MODE: runResetCarMode(); break; @@ -362,6 +370,11 @@ public final class Telecom extends BaseCommand { mTelecomService.cleanupStuckCalls(); } + private void runCleanupOrphanPhoneAccounts() throws RemoteException { + System.out.println("Success - cleaned up " + mTelecomService.cleanupOrphanPhoneAccounts() + + " phone accounts."); + } + private void runResetCarMode() throws RemoteException { mTelecomService.resetCarMode(); } diff --git a/core/api/current.txt b/core/api/current.txt index 407855b107cd..0ce315adc310 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -17,6 +17,7 @@ package android { field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION"; field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE"; field public static final String ACCESS_NOTIFICATION_POLICY = "android.permission.ACCESS_NOTIFICATION_POLICY"; + field public static final String ACCESS_SUPPLEMENTAL_APIS = "android.permission.ACCESS_SUPPLEMENTAL_APIS"; field public static final String ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE"; field public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER"; field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION"; @@ -948,6 +949,7 @@ package android { field public static final int left = 16843181; // 0x10101ad field public static final int letterSpacing = 16843958; // 0x10104b6 field public static final int level = 16844032; // 0x1010500 + field public static final int lineBreakStyle = 16844365; // 0x101064d field public static final int lineHeight = 16844159; // 0x101057f field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 @@ -971,6 +973,7 @@ package android { field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208 field public static final int listViewStyle = 16842868; // 0x1010074 field public static final int listViewWhiteStyle = 16842869; // 0x1010075 + field public static final int localeConfig; field public static final int lockTaskMode = 16844013; // 0x10104ed field public static final int logo = 16843454; // 0x10102be field public static final int logoDescription = 16844009; // 0x10104e9 @@ -1312,6 +1315,7 @@ package android { field public static final int shouldDisableView = 16843246; // 0x10101ee field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c field public static final int showAsAction = 16843481; // 0x10102d9 + field public static final int showBackground; field public static final int showClockAndComplications; field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDividers = 16843561; // 0x1010329 @@ -3991,7 +3995,7 @@ package android.app { method @Deprecated public void onTabUnselected(android.app.ActionBar.Tab, android.app.FragmentTransaction); } - @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { ctor public Activity(); method public void addContentView(android.view.View, android.view.ViewGroup.LayoutParams); method public void closeContextMenu(); @@ -4034,6 +4038,7 @@ package android.app { method public int getMaxNumPictureInPictureActions(); method public final android.media.session.MediaController getMediaController(); method @NonNull public android.view.MenuInflater getMenuInflater(); + method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method public final android.app.Activity getParent(); method @Nullable public android.content.Intent getParentActivityIntent(); method public android.content.SharedPreferences getPreferences(int); @@ -4912,7 +4917,7 @@ package android.app { method public void onDateSet(android.widget.DatePicker, int, int, int); } - public class Dialog implements android.content.DialogInterface android.view.KeyEvent.Callback android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + public class Dialog implements android.content.DialogInterface android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { ctor public Dialog(@NonNull @UiContext android.content.Context); ctor public Dialog(@NonNull @UiContext android.content.Context, @StyleRes int); ctor protected Dialog(@NonNull @UiContext android.content.Context, boolean, @Nullable android.content.DialogInterface.OnCancelListener); @@ -4932,6 +4937,7 @@ package android.app { method @NonNull @UiContext public final android.content.Context getContext(); method @Nullable public android.view.View getCurrentFocus(); method @NonNull public android.view.LayoutInflater getLayoutInflater(); + method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method @Nullable public final android.app.Activity getOwnerActivity(); method @Nullable public final android.view.SearchEvent getSearchEvent(); method public final int getVolumeControlStream(); @@ -5696,8 +5702,20 @@ package android.app { method @Deprecated public android.view.Window startActivity(String, android.content.Intent); } + public class LocaleConfig { + ctor public LocaleConfig(@NonNull android.content.Context); + method public int getStatus(); + method @Nullable public android.os.LocaleList getSupportedLocales(); + field public static final int STATUS_NOT_SPECIFIED = 1; // 0x1 + field public static final int STATUS_PARSING_FAILED = 2; // 0x2 + field public static final int STATUS_SUCCESS = 0; // 0x0 + field public static final String TAG_LOCALE = "locale"; + field public static final String TAG_LOCALE_CONFIG = "locale-config"; + } + public class LocaleManager { method @NonNull public android.os.LocaleList getApplicationLocales(); + method @NonNull @RequiresPermission(value="android.permission.READ_APP_SPECIFIC_LOCALES", conditional=true) public android.os.LocaleList getApplicationLocales(@NonNull String); method public void setApplicationLocales(@NonNull android.os.LocaleList); } @@ -7273,6 +7291,10 @@ package android.app.admin { method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String); method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String); method public CharSequence getDeviceOwnerLockScreenInfo(); + method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); method @NonNull public String getEnrollmentSpecificId(); method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); @@ -7496,6 +7518,7 @@ package android.app.admin { field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE"; field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE"; field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED"; + field public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED"; field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE"; field public static final String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED"; field public static final String ACTION_PROFILE_OWNER_CHANGED = "android.app.action.PROFILE_OWNER_CHANGED"; @@ -7531,6 +7554,7 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE"; field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE"; field public static final String EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES = "android.app.extra.PROVISIONING_ALLOWED_PROVISIONING_MODES"; + field public static final String EXTRA_PROVISIONING_ALLOW_OFFLINE = "android.app.extra.PROVISIONING_ALLOW_OFFLINE"; field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME"; field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE = "android.app.extra.PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE"; field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM"; @@ -7544,6 +7568,7 @@ package android.app.admin { field @Deprecated public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS"; field public static final String EXTRA_PROVISIONING_IMEI = "android.app.extra.PROVISIONING_IMEI"; field public static final String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION"; + field public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON = "android.app.extra.PROVISIONING_KEEP_SCREEN_ON"; field public static final String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED"; field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE"; field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME"; @@ -7571,6 +7596,8 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE"; field public static final String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID"; field public static final String EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_USER_CERTIFICATE"; + field public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID"; + field public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = "android.app.extra.RESOURCE_TYPE_DRAWABLE"; field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1 field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2 field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1 @@ -7665,6 +7692,35 @@ package android.app.admin { method public void onApplicationUserDataCleared(String, boolean); } + public final class DevicePolicyResources { + ctor public DevicePolicyResources(); + } + + public static final class DevicePolicyResources.Drawable { + field public static final int INVALID_ID = -1; // 0xffffffff + field public static final int WORK_PROFILE_ICON = 1; // 0x1 + field public static final int WORK_PROFILE_ICON_BADGE = 0; // 0x0 + field public static final int WORK_PROFILE_OFF_ICON = 2; // 0x2 + field public static final int WORK_PROFILE_USER_ICON = 3; // 0x3 + } + + public static final class DevicePolicyResources.Drawable.Source { + field public static final int HOME_WIDGET = 2; // 0x2 + field public static final int LAUNCHER_OFF_BUTTON = 3; // 0x3 + field public static final int NOTIFICATION = 0; // 0x0 + field public static final int PROFILE_SWITCH_ANIMATION = 1; // 0x1 + field public static final int QUICK_SETTINGS = 4; // 0x4 + field public static final int STATUS_BAR = 5; // 0x5 + field public static final int UNDEFINED = -1; // 0xffffffff + } + + public static final class DevicePolicyResources.Drawable.Style { + field public static final int DEFAULT = -1; // 0xffffffff + field public static final int OUTLINE = 2; // 0x2 + field public static final int SOLID_COLORED = 0; // 0x0 + field public static final int SOLID_NOT_COLORED = 1; // 0x1 + } + public final class DnsEvent extends android.app.admin.NetworkEvent implements android.os.Parcelable { method public String getHostname(); method public java.util.List<java.net.InetAddress> getInetAddresses(); @@ -9208,6 +9264,7 @@ package android.bluetooth { field public static final int SOURCE_CODEC_TYPE_APTX = 2; // 0x2 field public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; // 0x3 field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240 + field public static final int SOURCE_CODEC_TYPE_LC3 = 5; // 0x5 field public static final int SOURCE_CODEC_TYPE_LDAC = 4; // 0x4 field public static final int SOURCE_CODEC_TYPE_SBC = 0; // 0x0 } @@ -9743,13 +9800,14 @@ package android.bluetooth { field public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; // 0x2 field public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; // 0x1 field public static final int ERROR_DEVICE_NOT_BONDED = 3; // 0x3 - field public static final int ERROR_FEATURE_NOT_SUPPORTED = 10; // 0xa - field public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 101; // 0x65 - field public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 102; // 0x66 + field public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 200; // 0xc8 + field public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 201; // 0xc9 field public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; // 0x6 field public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8; // 0x8 field public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9; // 0x9 field public static final int ERROR_UNKNOWN = 2147483647; // 0x7fffffff + field public static final int FEATURE_NOT_SUPPORTED = 11; // 0xb + field public static final int FEATURE_SUPPORTED = 10; // 0xa field public static final int SUCCESS = 0; // 0x0 } @@ -10772,7 +10830,7 @@ package android.content { method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection); method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int); method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection); - method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL", android.Manifest.permission.INTERACT_ACROSS_PROFILES}, conditional=true) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle); method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String); method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int); method @NonNull public int[] checkCallingOrSelfUriPermissions(@NonNull java.util.List<android.net.Uri>, int); @@ -10876,6 +10934,8 @@ package android.content { method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); method public abstract void revokeUriPermission(android.net.Uri, int); method public abstract void revokeUriPermission(String, android.net.Uri, int); + method public void selfRevokePermission(@NonNull String); + method public void selfRevokePermissions(@NonNull java.util.Collection<java.lang.String>); method public abstract void sendBroadcast(@RequiresPermission android.content.Intent); method public abstract void sendBroadcast(@RequiresPermission android.content.Intent, @Nullable String); method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); @@ -12762,11 +12822,16 @@ package android.content.pm { method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR; field public static final int INVALID_ID = -1; // 0xffffffff - field public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; // 0x2 - field public static final int STAGED_SESSION_CONFLICT = 4; // 0x4 - field public static final int STAGED_SESSION_NO_ERROR = 0; // 0x0 - field public static final int STAGED_SESSION_UNKNOWN = 3; // 0x3 - field public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; // 0x1 + field public static final int SESSION_ACTIVATION_FAILED = 2; // 0x2 + field public static final int SESSION_CONFLICT = 4; // 0x4 + field public static final int SESSION_NO_ERROR = 0; // 0x0 + field public static final int SESSION_UNKNOWN_ERROR = 3; // 0x3 + field public static final int SESSION_VERIFICATION_FAILED = 1; // 0x1 + field @Deprecated public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; // 0x2 + field @Deprecated public static final int STAGED_SESSION_CONFLICT = 4; // 0x4 + field @Deprecated public static final int STAGED_SESSION_NO_ERROR = 0; // 0x0 + field @Deprecated public static final int STAGED_SESSION_UNKNOWN = 3; // 0x3 + field @Deprecated public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; // 0x1 } public static class PackageInstaller.SessionParams implements android.os.Parcelable { @@ -13410,6 +13475,7 @@ package android.content.pm { public final class ShortcutInfo implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.content.ComponentName getActivity(); + method @NonNull public java.util.List<java.lang.String> getCapabilityParameterValues(@NonNull String, @NonNull String); method @Nullable public java.util.Set<java.lang.String> getCategories(); method @Nullable public CharSequence getDisabledMessage(); method public int getDisabledReason(); @@ -13424,6 +13490,7 @@ package android.content.pm { method public int getRank(); method @Nullable public CharSequence getShortLabel(); method public android.os.UserHandle getUserHandle(); + method public boolean hasCapability(@NonNull String); method public boolean hasKeyFieldsOnly(); method public boolean isCached(); method public boolean isDeclaredInManifest(); @@ -13448,6 +13515,7 @@ package android.content.pm { public static class ShortcutInfo.Builder { ctor public ShortcutInfo.Builder(android.content.Context, String); + method @NonNull public android.content.pm.ShortcutInfo.Builder addCapabilityBinding(@NonNull String, @Nullable String, @Nullable java.util.List<java.lang.String>); method @NonNull public android.content.pm.ShortcutInfo build(); method @NonNull public android.content.pm.ShortcutInfo.Builder setActivity(@NonNull android.content.ComponentName); method @NonNull public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>); @@ -15221,6 +15289,11 @@ package android.graphics { public class BitmapShader extends android.graphics.Shader { ctor public BitmapShader(@NonNull android.graphics.Bitmap, @NonNull android.graphics.Shader.TileMode, @NonNull android.graphics.Shader.TileMode); + method public int getFilterMode(); + method public void setFilterMode(int); + field public static final int FILTER_MODE_DEFAULT = 0; // 0x0 + field public static final int FILTER_MODE_LINEAR = 2; // 0x2 + field public static final int FILTER_MODE_NEAREST = 1; // 0x1 } public enum BlendMode { @@ -15978,6 +16051,8 @@ package android.graphics { method public String getFontFeatureSettings(); method public float getFontMetrics(android.graphics.Paint.FontMetrics); method public android.graphics.Paint.FontMetrics getFontMetrics(); + method public void getFontMetricsInt(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); + method public void getFontMetricsInt(@NonNull char[], @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt); method public android.graphics.Paint.FontMetricsInt getFontMetricsInt(); method public float getFontSpacing(); @@ -16625,6 +16700,7 @@ package android.graphics { method public void setFloatUniform(@NonNull String, float, float, float); method public void setFloatUniform(@NonNull String, float, float, float, float); method public void setFloatUniform(@NonNull String, @NonNull float[]); + method public void setInputBuffer(@NonNull String, @NonNull android.graphics.BitmapShader); method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader); method public void setIntUniform(@NonNull String, int); method public void setIntUniform(@NonNull String, int, int); @@ -17510,6 +17586,17 @@ package android.graphics.pdf { package android.graphics.text { + public final class LineBreakConfig { + ctor public LineBreakConfig(); + method public int getLineBreakStyle(); + method public void set(@Nullable android.graphics.text.LineBreakConfig); + method public void setLineBreakStyle(int); + field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1 + field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0 + field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2 + field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3 + } + public class LineBreaker { method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 @@ -17565,6 +17652,7 @@ package android.graphics.text { ctor public MeasuredText.Builder(@NonNull android.graphics.text.MeasuredText); method @NonNull public android.graphics.text.MeasuredText.Builder appendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float); method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, boolean); + method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean); method @NonNull public android.graphics.text.MeasuredText build(); method @Deprecated @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean); method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(int); @@ -18005,6 +18093,7 @@ package android.hardware { field public static final String STRING_TYPE_GRAVITY = "android.sensor.gravity"; field public static final String STRING_TYPE_GYROSCOPE = "android.sensor.gyroscope"; field public static final String STRING_TYPE_GYROSCOPE_UNCALIBRATED = "android.sensor.gyroscope_uncalibrated"; + field public static final String STRING_TYPE_HEAD_TRACKER = "android.sensor.head_tracker"; field public static final String STRING_TYPE_HEART_BEAT = "android.sensor.heart_beat"; field public static final String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate"; field public static final String STRING_TYPE_HINGE_ANGLE = "android.sensor.hinge_angle"; @@ -18035,6 +18124,7 @@ package android.hardware { field public static final int TYPE_GRAVITY = 9; // 0x9 field public static final int TYPE_GYROSCOPE = 4; // 0x4 field public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; // 0x10 + field public static final int TYPE_HEAD_TRACKER = 37; // 0x25 field public static final int TYPE_HEART_BEAT = 31; // 0x1f field public static final int TYPE_HEART_RATE = 21; // 0x15 field public static final int TYPE_HINGE_ANGLE = 36; // 0x24 @@ -18097,6 +18187,7 @@ package android.hardware { method public void onFlushCompleted(android.hardware.Sensor); method public void onSensorAdditionalInfo(android.hardware.SensorAdditionalInfo); method public void onSensorChanged(android.hardware.SensorEvent); + method public void onSensorDiscontinuity(@NonNull android.hardware.Sensor); } public interface SensorEventListener { @@ -18320,10 +18411,12 @@ package android.hardware.biometrics { ctor public BiometricPrompt.CryptoObject(@NonNull java.security.Signature); ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Cipher); ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Mac); - ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential); + ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential); + ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession); method public javax.crypto.Cipher getCipher(); - method @Nullable public android.security.identity.IdentityCredential getIdentityCredential(); + method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential(); method public javax.crypto.Mac getMac(); + method @Nullable public android.security.identity.PresentationSession getPresentationSession(); method public java.security.Signature getSignature(); } @@ -18450,12 +18543,14 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REPROCESS_MAX_CAPTURE_STALL; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> REQUEST_AVAILABLE_CAPABILITIES; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DynamicRangeProfiles> REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_INPUT_STREAMS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_OUTPUT_PROC; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_OUTPUT_PROC_STALLING; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_OUTPUT_RAW; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_PARTIAL_RESULT_COUNT; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> REQUEST_PIPELINE_MAX_DEPTH; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_ROTATE_AND_CROP_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE; @@ -18463,6 +18558,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION; @@ -18779,6 +18875,7 @@ package android.hardware.camera2 { field public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6; // 0x6 field public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9; // 0x9 field public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; // 0x8 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18; // 0x12 field public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; // 0xb field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1 @@ -19132,6 +19229,25 @@ package android.hardware.camera2.params { field public static final long NORMAL = 0L; // 0x0L } + public final class DynamicRangeProfiles { + ctor public DynamicRangeProfiles(@NonNull int[]); + method @NonNull public java.util.Set<java.lang.Integer> getProfileCaptureRequestConstraints(int); + method @NonNull public java.util.Set<java.lang.Integer> getSupportedProfiles(); + field public static final int DOLBY_VISION_10B_HDR_OEM = 64; // 0x40 + field public static final int DOLBY_VISION_10B_HDR_OEM_PO = 128; // 0x80 + field public static final int DOLBY_VISION_10B_HDR_REF = 16; // 0x10 + field public static final int DOLBY_VISION_10B_HDR_REF_PO = 32; // 0x20 + field public static final int DOLBY_VISION_8B_HDR_OEM = 1024; // 0x400 + field public static final int DOLBY_VISION_8B_HDR_OEM_PO = 2048; // 0x800 + field public static final int DOLBY_VISION_8B_HDR_REF = 256; // 0x100 + field public static final int DOLBY_VISION_8B_HDR_REF_PO = 512; // 0x200 + field public static final int HDR10 = 4; // 0x4 + field public static final int HDR10_PLUS = 8; // 0x8 + field public static final int HLG10 = 2; // 0x2 + field public static final int PUBLIC_MAX = 4096; // 0x1000 + field public static final int STANDARD = 1; // 0x1 + } + public final class ExtensionSessionConfiguration { ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback); method @NonNull public java.util.concurrent.Executor getExecutor(); @@ -19178,8 +19294,10 @@ package android.hardware.camera2.params { } public static final class MandatoryStreamCombination.MandatoryStreamInformation { + method public int get10BitFormat(); method @NonNull public java.util.List<android.util.Size> getAvailableSizes(); method public int getFormat(); + method public boolean is10BitCapable(); method public boolean isInput(); method public boolean isMaximumSize(); method public boolean isUltraHighResolution(); @@ -19233,12 +19351,14 @@ package android.hardware.camera2.params { method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader); method public int describeContents(); method public void enableSurfaceSharing(); + method public int getDynamicRangeProfile(); method public int getMaxSharedSurfaceCount(); method @Nullable public android.view.Surface getSurface(); method public int getSurfaceGroupId(); method @NonNull public java.util.List<android.view.Surface> getSurfaces(); method public void removeSensorPixelModeUsed(int); method public void removeSurface(@NonNull android.view.Surface); + method public void setDynamicRangeProfile(int); method public void setPhysicalCameraId(@Nullable String); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; @@ -19264,6 +19384,7 @@ package android.hardware.camera2.params { method @Nullable public java.util.Set<java.lang.Integer> getValidOutputFormatsForInput(int); method public boolean isOutputSupportedFor(int); method public boolean isOutputSupportedFor(@NonNull android.view.Surface); + field public static final int USECASE_10BIT_OUTPUT = 8; // 0x8 field public static final int USECASE_LOW_LATENCY_SNAPSHOT = 6; // 0x6 field public static final int USECASE_PREVIEW = 0; // 0x0 field public static final int USECASE_RAW = 5; // 0x5 @@ -19741,6 +19862,7 @@ package android.inputmethodservice { @UiContext public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService { ctor public InputMethodService(); method @Deprecated public boolean enableHardwareAcceleration(); + method public final void finishStylusHandwriting(); method public int getBackDisposition(); method public int getCandidatesHiddenVisibility(); method public android.view.inputmethod.InputBinding getCurrentInputBinding(); @@ -19750,6 +19872,7 @@ package android.inputmethodservice { method @Deprecated public int getInputMethodWindowRecommendedHeight(); method public android.view.LayoutInflater getLayoutInflater(); method public int getMaxWidth(); + method @Nullable public final android.view.Window getStylusHandwritingWindow(); method public CharSequence getTextForImeAction(int); method public android.app.Dialog getWindow(); method public void hideStatusIcon(); @@ -19780,6 +19903,7 @@ package android.inputmethodservice { method public void onFinishCandidatesView(boolean); method public void onFinishInput(); method public void onFinishInputView(boolean); + method public void onFinishStylusHandwriting(); method public void onInitializeInterface(); method public boolean onInlineSuggestionsResponse(@NonNull android.view.inputmethod.InlineSuggestionsResponse); method public boolean onKeyDown(int, android.view.KeyEvent); @@ -19790,6 +19914,7 @@ package android.inputmethodservice { method public void onStartCandidatesView(android.view.inputmethod.EditorInfo, boolean); method public void onStartInput(android.view.inputmethod.EditorInfo, boolean); method public void onStartInputView(android.view.inputmethod.EditorInfo, boolean); + method public boolean onStartStylusHandwriting(); method public void onUnbindInput(); method @Deprecated public void onUpdateCursor(android.graphics.Rect); method public void onUpdateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo); @@ -20135,6 +20260,24 @@ package android.location { field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAntennaInfo.SphericalCorrections> CREATOR; } + public final class GnssAutomaticGainControl implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0) public long getCarrierFrequencyHz(); + method public int getConstellationType(); + method @FloatRange(from=0xffffd8f0, to=10000) public double getLevelDb(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAutomaticGainControl> CREATOR; + } + + public static final class GnssAutomaticGainControl.Builder { + ctor public GnssAutomaticGainControl.Builder(); + ctor public GnssAutomaticGainControl.Builder(@NonNull android.location.GnssAutomaticGainControl); + method @NonNull public android.location.GnssAutomaticGainControl build(); + method @NonNull public android.location.GnssAutomaticGainControl.Builder setCarrierFrequencyHz(@IntRange(from=0) long); + method @NonNull public android.location.GnssAutomaticGainControl.Builder setConstellationType(int); + method @NonNull public android.location.GnssAutomaticGainControl.Builder setLevelDb(@FloatRange(from=0xffffd8f0, to=10000) double); + } + public final class GnssCapabilities implements android.os.Parcelable { method public int describeContents(); method public boolean hasAntennaInfo(); @@ -20191,7 +20334,7 @@ package android.location { method public double getAccumulatedDeltaRangeMeters(); method public int getAccumulatedDeltaRangeState(); method public double getAccumulatedDeltaRangeUncertaintyMeters(); - method public double getAutomaticGainControlLevelDb(); + method @Deprecated public double getAutomaticGainControlLevelDb(); method @FloatRange(from=0, to=63) public double getBasebandCn0DbHz(); method @Deprecated public long getCarrierCycles(); method public float getCarrierFrequencyHz(); @@ -20213,7 +20356,7 @@ package android.location { method public int getState(); method public int getSvid(); method public double getTimeOffsetNanos(); - method public boolean hasAutomaticGainControlLevelDb(); + method @Deprecated public boolean hasAutomaticGainControlLevelDb(); method public boolean hasBasebandCn0DbHz(); method @Deprecated public boolean hasCarrierCycles(); method public boolean hasCarrierFrequencyHz(); @@ -20275,11 +20418,21 @@ package android.location { public final class GnssMeasurementsEvent implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.location.GnssClock getClock(); + method @NonNull public java.util.Collection<android.location.GnssAutomaticGainControl> getGnssAutomaticGainControls(); method @NonNull public java.util.Collection<android.location.GnssMeasurement> getMeasurements(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementsEvent> CREATOR; } + public static final class GnssMeasurementsEvent.Builder { + ctor public GnssMeasurementsEvent.Builder(); + ctor public GnssMeasurementsEvent.Builder(@NonNull android.location.GnssMeasurementsEvent); + method @NonNull public android.location.GnssMeasurementsEvent build(); + method @NonNull public android.location.GnssMeasurementsEvent.Builder setClock(@NonNull android.location.GnssClock); + method @NonNull public android.location.GnssMeasurementsEvent.Builder setGnssAutomaticGainControls(@NonNull java.util.Collection<android.location.GnssAutomaticGainControl>); + method @NonNull public android.location.GnssMeasurementsEvent.Builder setMeasurements(@NonNull java.util.Collection<android.location.GnssMeasurement>); + } + public abstract static class GnssMeasurementsEvent.Callback { ctor public GnssMeasurementsEvent.Callback(); method public void onGnssMeasurementsReceived(android.location.GnssMeasurementsEvent); @@ -20951,6 +21104,7 @@ package android.media { method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations(); method @NonNull public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations(); method public int getAllowedCapturePolicy(); + method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAudioDevicesForAttributes(@NonNull android.media.AudioAttributes); method public int getAudioHwSyncForSession(int); method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices(); method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice(); @@ -21892,16 +22046,29 @@ package android.media { method public android.media.Image acquireNextImage(); method public void close(); method public void discardFreeBuffers(); + method public long getDataSpace(); + method public int getHardwareBufferFormat(); method public int getHeight(); method public int getImageFormat(); method public int getMaxImages(); method public android.view.Surface getSurface(); + method public long getUsage(); method public int getWidth(); method @NonNull public static android.media.ImageReader newInstance(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int); method @NonNull public static android.media.ImageReader newInstance(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int, long); method public void setOnImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler); } + public static final class ImageReader.Builder { + ctor public ImageReader.Builder(@IntRange(from=1) int, @IntRange(from=1) int); + method @NonNull public android.media.ImageReader build(); + method @NonNull public android.media.ImageReader.Builder setDefaultDataSpace(long); + method @NonNull public android.media.ImageReader.Builder setDefaultHardwareBufferFormat(int); + method @NonNull public android.media.ImageReader.Builder setImageFormat(int); + method @NonNull public android.media.ImageReader.Builder setMaxImages(int); + method @NonNull public android.media.ImageReader.Builder setUsage(long); + } + public static interface ImageReader.OnImageAvailableListener { method public void onImageAvailable(android.media.ImageReader); } @@ -25547,6 +25714,7 @@ package android.media.midi { public final class MidiDeviceInfo implements android.os.Parcelable { method public int describeContents(); + method public int getDefaultProtocol(); method public int getId(); method public int getInputPortCount(); method public int getOutputPortCount(); @@ -25563,6 +25731,14 @@ package android.media.midi { field public static final String PROPERTY_SERIAL_NUMBER = "serial_number"; field public static final String PROPERTY_USB_DEVICE = "usb_device"; field public static final String PROPERTY_VERSION = "version"; + field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3; // 0x3 + field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4; // 0x4 + field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1; // 0x1 + field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2; // 0x2 + field public static final int PROTOCOL_UMP_MIDI_2_0 = 17; // 0x11 + field public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18; // 0x12 + field public static final int PROTOCOL_UMP_USE_MIDI_CI = 0; // 0x0 + field public static final int PROTOCOL_UNKNOWN = -1; // 0xffffffff field public static final int TYPE_BLUETOOTH = 3; // 0x3 field public static final int TYPE_USB = 1; // 0x1 field public static final int TYPE_VIRTUAL = 2; // 0x2 @@ -25604,10 +25780,14 @@ package android.media.midi { public final class MidiManager { method public android.media.midi.MidiDeviceInfo[] getDevices(); + method @NonNull public java.util.Collection<android.media.midi.MidiDeviceInfo> getDevicesForTransport(int); method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler); method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler); method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler); + method public void registerDeviceCallbackForTransport(@NonNull android.media.midi.MidiManager.DeviceCallback, @Nullable android.os.Handler, int); method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback); + field public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; // 0x1 + field public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; // 0x2 } public static class MidiManager.DeviceCallback { @@ -27362,11 +27542,13 @@ package android.net { method @NonNull public android.net.VpnService.Builder addDnsServer(@NonNull java.net.InetAddress); method @NonNull public android.net.VpnService.Builder addDnsServer(@NonNull String); method @NonNull public android.net.VpnService.Builder addRoute(@NonNull java.net.InetAddress, int); + method @NonNull public android.net.VpnService.Builder addRoute(@NonNull android.net.IpPrefix); method @NonNull public android.net.VpnService.Builder addRoute(@NonNull String, int); method @NonNull public android.net.VpnService.Builder addSearchDomain(@NonNull String); method @NonNull public android.net.VpnService.Builder allowBypass(); method @NonNull public android.net.VpnService.Builder allowFamily(int); method @Nullable public android.os.ParcelFileDescriptor establish(); + method @NonNull public android.net.VpnService.Builder excludeRoute(@NonNull android.net.IpPrefix); method @NonNull public android.net.VpnService.Builder setBlocking(boolean); method @NonNull public android.net.VpnService.Builder setConfigureIntent(@NonNull android.app.PendingIntent); method @NonNull public android.net.VpnService.Builder setHttpProxy(@NonNull android.net.ProxyInfo); @@ -27684,6 +27866,25 @@ package android.net.sip { package android.net.vcn { + public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate { + method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds(); + method public int getOpportunistic(); + method public int getRoaming(); + method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds(); + } + + public static final class VcnCellUnderlyingNetworkTemplate.Builder { + ctor public VcnCellUnderlyingNetworkTemplate.Builder(); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build(); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>); + } + public final class VcnConfig implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs(); @@ -27702,6 +27903,7 @@ package android.net.vcn { method @NonNull public String getGatewayConnectionName(); method @IntRange(from=0x500) public int getMaxMtu(); method @NonNull public long[] getRetryIntervalsMillis(); + method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities(); } public static final class VcnGatewayConnectionConfig.Builder { @@ -27711,6 +27913,7 @@ package android.net.vcn { method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>); } public class VcnManager { @@ -27734,6 +27937,30 @@ package android.net.vcn { method public abstract void onStatusChanged(int); } + public abstract class VcnUnderlyingNetworkTemplate { + method public int getMetered(); + method public int getMinEntryDownstreamBandwidthKbps(); + method public int getMinEntryUpstreamBandwidthKbps(); + method public int getMinExitDownstreamBandwidthKbps(); + method public int getMinExitUpstreamBandwidthKbps(); + field public static final int MATCH_ANY = 0; // 0x0 + field public static final int MATCH_FORBIDDEN = 2; // 0x2 + field public static final int MATCH_REQUIRED = 1; // 0x1 + } + + public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate { + method @NonNull public java.util.Set<java.lang.String> getSsids(); + } + + public static final class VcnWifiUnderlyingNetworkTemplate.Builder { + ctor public VcnWifiUnderlyingNetworkTemplate.Builder(); + method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build(); + method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int); + method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int); + method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int); + method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>); + } + } package android.nfc { @@ -31830,6 +32057,7 @@ package android.os { method @IntRange(from=0xffffffff) public int indexOf(java.util.Locale); method public boolean isEmpty(); method public static boolean isPseudoLocale(@Nullable android.icu.util.ULocale); + method public static boolean matchesLanguageAndScript(@NonNull java.util.Locale, @NonNull java.util.Locale); method public static void setDefault(@NonNull @Size(min=1) android.os.LocaleList); method @IntRange(from=0) public int size(); method @NonNull public String toLanguageTags(); @@ -36052,7 +36280,7 @@ package android.provider { field public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone"; field public static final String END_BUTTON_BEHAVIOR = "end_button_behavior"; field public static final String FONT_SCALE = "font_scale"; - field public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; + field @Deprecated public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; field @Deprecated public static final String HTTP_PROXY = "http_proxy"; field @Deprecated public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; field @Deprecated public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; @@ -36096,7 +36324,7 @@ package android.provider { field public static final String USER_ROTATION = "user_rotation"; field @Deprecated public static final String USE_GOOGLE_MAIL = "use_google_mail"; field public static final String VIBRATE_ON = "vibrate_on"; - field public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; + field @Deprecated public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; field @Deprecated public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger"; field @Deprecated public static final String WALLPAPER_ACTIVITY = "wallpaper_activity"; field @Deprecated public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count"; @@ -37963,6 +38191,51 @@ package android.security.identity { ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable); } + public class CredentialDataRequest { + method @NonNull public java.util.Map<java.lang.String,java.util.Collection<java.lang.String>> getDeviceSignedEntriesToRequest(); + method @NonNull public java.util.Map<java.lang.String,java.util.Collection<java.lang.String>> getIssuerSignedEntriesToRequest(); + method @Nullable public byte[] getReaderSignature(); + method @Nullable public byte[] getRequestMessage(); + method public boolean isAllowUsingExhaustedKeys(); + method public boolean isAllowUsingExpiredKeys(); + method public boolean isIncrementUseCount(); + } + + public static final class CredentialDataRequest.Builder { + ctor public CredentialDataRequest.Builder(); + method @NonNull public android.security.identity.CredentialDataRequest build(); + method @NonNull public android.security.identity.CredentialDataRequest.Builder setAllowUsingExhaustedKeys(boolean); + method @NonNull public android.security.identity.CredentialDataRequest.Builder setAllowUsingExpiredKeys(boolean); + method @NonNull public android.security.identity.CredentialDataRequest.Builder setDeviceSignedEntriesToRequest(@NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>); + method @NonNull public android.security.identity.CredentialDataRequest.Builder setIncrementUseCount(boolean); + method @NonNull public android.security.identity.CredentialDataRequest.Builder setIssuerSignedEntriesToRequest(@NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>); + method @NonNull public android.security.identity.CredentialDataRequest.Builder setReaderSignature(@NonNull byte[]); + method @NonNull public android.security.identity.CredentialDataRequest.Builder setRequestMessage(@NonNull byte[]); + } + + public abstract class CredentialDataResult { + method @Nullable public abstract byte[] getDeviceMac(); + method @NonNull public abstract byte[] getDeviceNameSpaces(); + method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getDeviceSignedEntries(); + method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getIssuerSignedEntries(); + method @NonNull public abstract byte[] getStaticAuthenticationData(); + } + + public static interface CredentialDataResult.Entries { + method @Nullable public byte[] getEntry(@NonNull String, @NonNull String); + method @NonNull public java.util.Collection<java.lang.String> getEntryNames(@NonNull String); + method @NonNull public java.util.Collection<java.lang.String> getNamespaces(); + method @NonNull public java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String); + method public int getStatus(@NonNull String, @NonNull String); + field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3 + field public static final int STATUS_NOT_REQUESTED = 2; // 0x2 + field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6 + field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1 + field public static final int STATUS_OK = 0; // 0x0 + field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5 + field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4 + } + public class DocTypeNotSupportedException extends android.security.identity.IdentityCredentialException { ctor public DocTypeNotSupportedException(@NonNull String); ctor public DocTypeNotSupportedException(@NonNull String, @NonNull Throwable); @@ -37974,19 +38247,19 @@ package android.security.identity { } public abstract class IdentityCredential { - method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair(); - method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException; + method @Deprecated @NonNull public abstract java.security.KeyPair createEphemeralKeyPair(); + method @Deprecated @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException; method @NonNull public byte[] delete(@NonNull byte[]); - method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]); + method @Deprecated @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]); method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification(); method @NonNull public abstract int[] getAuthenticationDataUsageCount(); method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(); - method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException; + method @Deprecated @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException; method @NonNull public byte[] proveOwnership(@NonNull byte[]); - method public abstract void setAllowUsingExhaustedKeys(boolean); - method public void setAllowUsingExpiredKeys(boolean); + method @Deprecated public abstract void setAllowUsingExhaustedKeys(boolean); + method @Deprecated public void setAllowUsingExpiredKeys(boolean); method public abstract void setAvailableAuthenticationKeys(int, int); - method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException; + method @Deprecated public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException; method @Deprecated public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException; method public void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull java.time.Instant, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException; method @NonNull public byte[] update(@NonNull android.security.identity.PersonalizationData); @@ -37999,6 +38272,7 @@ package android.security.identity { public abstract class IdentityCredentialStore { method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException; + method @NonNull public android.security.identity.PresentationSession createPresentationSession(int) throws android.security.identity.CipherSuiteNotSupportedException; method @Deprecated @Nullable public abstract byte[] deleteCredentialByName(@NonNull String); method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException; method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context); @@ -38037,22 +38311,29 @@ package android.security.identity { method @NonNull public android.security.identity.PersonalizationData.Builder putEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]); } - public abstract class ResultData { - method @NonNull public abstract byte[] getAuthenticatedData(); - method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String); - method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String); - method @Nullable public abstract byte[] getMessageAuthenticationCode(); - method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaces(); - method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String); - method @NonNull public abstract byte[] getStaticAuthenticationData(); - method public abstract int getStatus(@NonNull String, @NonNull String); - field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3 - field public static final int STATUS_NOT_REQUESTED = 2; // 0x2 - field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6 - field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1 - field public static final int STATUS_OK = 0; // 0x0 - field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5 - field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4 + public abstract class PresentationSession { + method @Nullable public abstract android.security.identity.CredentialDataResult getCredentialData(@NonNull String, @NonNull android.security.identity.CredentialDataRequest) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException; + method @NonNull public abstract java.security.KeyPair getEphemeralKeyPair(); + method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException; + method public abstract void setSessionTranscript(@NonNull byte[]); + } + + @Deprecated public abstract class ResultData { + method @Deprecated @NonNull public abstract byte[] getAuthenticatedData(); + method @Deprecated @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String); + method @Deprecated @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String); + method @Deprecated @Nullable public abstract byte[] getMessageAuthenticationCode(); + method @Deprecated @NonNull public abstract java.util.Collection<java.lang.String> getNamespaces(); + method @Deprecated @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String); + method @Deprecated @NonNull public abstract byte[] getStaticAuthenticationData(); + method @Deprecated public abstract int getStatus(@NonNull String, @NonNull String); + field @Deprecated public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3 + field @Deprecated public static final int STATUS_NOT_REQUESTED = 2; // 0x2 + field @Deprecated public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6 + field @Deprecated public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1 + field @Deprecated public static final int STATUS_OK = 0; // 0x0 + field @Deprecated public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5 + field @Deprecated public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4 } public class SessionTranscriptMismatchException extends android.security.identity.IdentityCredentialException { @@ -39822,8 +40103,10 @@ package android.speech { ctor public RecognitionService(); method public final android.os.IBinder onBind(android.content.Intent); method protected abstract void onCancel(android.speech.RecognitionService.Callback); + method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback); method protected abstract void onStartListening(android.content.Intent, android.speech.RecognitionService.Callback); method protected abstract void onStopListening(android.speech.RecognitionService.Callback); + method public void triggerModelDownload(@NonNull android.content.Intent); field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService"; field public static final String SERVICE_META_DATA = "android.speech"; } @@ -39841,6 +40124,36 @@ package android.speech { method public void rmsChanged(float) throws android.os.RemoteException; } + public static class RecognitionService.SupportCallback { + method public void onError(int); + method public void onSupportResult(@NonNull android.speech.RecognitionSupport); + } + + public final class RecognitionSupport implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getInstalledLanguages(); + method @NonNull public java.util.List<java.lang.String> getPendingLanguages(); + method @NonNull public java.util.List<java.lang.String> getSupportedLanguages(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.speech.RecognitionSupport> CREATOR; + } + + public static final class RecognitionSupport.Builder { + ctor public RecognitionSupport.Builder(); + method @NonNull public android.speech.RecognitionSupport.Builder addInstalledLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addPendingLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addSupportedLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport build(); + method @NonNull public android.speech.RecognitionSupport.Builder setInstalledLanguages(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.speech.RecognitionSupport.Builder setPendingLanguages(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.speech.RecognitionSupport.Builder setSupportedLanguages(@NonNull java.util.List<java.lang.String>); + } + + public interface RecognitionSupportCallback { + method public void onError(int); + method public void onSupportResult(@NonNull android.speech.RecognitionSupport); + } + public class RecognizerIntent { method public static final android.content.Intent getVoiceDetailsIntent(android.content.Context); field public static final String ACTION_GET_LANGUAGE_DETAILS = "android.speech.action.GET_LANGUAGE_DETAILS"; @@ -39890,6 +40203,7 @@ package android.speech { public class SpeechRecognizer { method @MainThread public void cancel(); + method public void checkRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionSupportCallback); method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context); method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context); method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName); @@ -39899,8 +40213,10 @@ package android.speech { method @MainThread public void setRecognitionListener(android.speech.RecognitionListener); method @MainThread public void startListening(android.content.Intent); method @MainThread public void stopListening(); + method public void triggerModelDownload(@NonNull android.content.Intent); field public static final String CONFIDENCE_SCORES = "confidence_scores"; field public static final int ERROR_AUDIO = 3; // 0x3 + field public static final int ERROR_CANNOT_CHECK_SUPPORT = 14; // 0xe field public static final int ERROR_CLIENT = 5; // 0x5 field public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9; // 0x9 field public static final int ERROR_LANGUAGE_NOT_SUPPORTED = 12; // 0xc @@ -41937,6 +42253,7 @@ package android.telephony { field public static final int EPDG_ADDRESS_PCO = 2; // 0x2 field public static final int EPDG_ADDRESS_PLMN = 1; // 0x1 field public static final int EPDG_ADDRESS_STATIC = 0; // 0x0 + field public static final int EPDG_ADDRESS_VISITED_COUNTRY = 4; // 0x4 field public static final int ID_TYPE_FQDN = 2; // 0x2 field public static final int ID_TYPE_KEY_ID = 11; // 0xb field public static final int ID_TYPE_RFC822_ADDR = 3; // 0x3 @@ -43198,6 +43515,7 @@ package android.telephony { field public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; // 0x7b field public static final int RESULT_RIL_CANCELLED = 119; // 0x77 field public static final int RESULT_RIL_ENCODING_ERR = 109; // 0x6d + field public static final int RESULT_RIL_GENERIC_ERROR = 124; // 0x7c field public static final int RESULT_RIL_INTERNAL_ERR = 113; // 0x71 field public static final int RESULT_RIL_INVALID_ARGUMENTS = 104; // 0x68 field public static final int RESULT_RIL_INVALID_MODEM_STATE = 115; // 0x73 @@ -44006,6 +44324,7 @@ package android.telephony.data { field public static final int TYPE_DEFAULT = 17; // 0x11 field public static final int TYPE_DUN = 8; // 0x8 field public static final int TYPE_EMERGENCY = 512; // 0x200 + field public static final int TYPE_ENTERPRISE = 16384; // 0x4000 field public static final int TYPE_FOTA = 32; // 0x20 field public static final int TYPE_HIPRI = 16; // 0x10 field public static final int TYPE_IA = 256; // 0x100 @@ -44202,7 +44521,7 @@ package android.telephony.euicc { method public boolean isEnabled(); method public boolean isSimPortAvailable(int); method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException; - method @Deprecated @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent); + method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, int, @NonNull android.app.PendingIntent); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent); field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; @@ -44222,6 +44541,7 @@ package android.telephony.euicc { field public static final int ERROR_INSTALL_PROFILE = 10009; // 0x2719 field public static final int ERROR_INVALID_ACTIVATION_CODE = 10001; // 0x2711 field public static final int ERROR_INVALID_CONFIRMATION_CODE = 10002; // 0x2712 + field public static final int ERROR_INVALID_PORT = 10017; // 0x2721 field public static final int ERROR_INVALID_RESPONSE = 10015; // 0x271f field public static final int ERROR_NO_PROFILES_AVAILABLE = 10013; // 0x271d field public static final int ERROR_OPERATION_BUSY = 10016; // 0x2720 @@ -44899,6 +45219,7 @@ package android.text { public class BoringLayout extends android.text.Layout implements android.text.TextUtils.EllipsizeCallback { ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); + ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public void ellipsized(int, int); method public int getBottomPadding(); method public int getEllipsisCount(int); @@ -44913,9 +45234,12 @@ package android.text { method public int getTopPadding(); method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint); method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint, android.text.BoringLayout.Metrics); + method @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics); method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); + method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); + method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); } @@ -45124,6 +45448,7 @@ package android.text { method public abstract int getTopPadding(); method public final int getWidth(); method public final void increaseWidthTo(int); + method public boolean isFallbackLineSpacingEnabled(); method public boolean isRtlCharAt(int); method protected final boolean isSpanned(); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 @@ -45211,6 +45536,7 @@ package android.text { public static final class PrecomputedText.Params { method public int getBreakStrategy(); method public int getHyphenationFrequency(); + method @Nullable public android.graphics.text.LineBreakConfig getLineBreakConfig(); method @NonNull public android.text.TextDirectionHeuristic getTextDirection(); method @NonNull public android.text.TextPaint getTextPaint(); } @@ -45221,6 +45547,7 @@ package android.text { method @NonNull public android.text.PrecomputedText.Params build(); method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int); method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int); + method @NonNull public android.text.PrecomputedText.Params.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method public android.text.PrecomputedText.Params.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic); } @@ -45381,6 +45708,7 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setIncludePad(boolean); method @NonNull public android.text.StaticLayout.Builder setIndents(@Nullable int[], @Nullable int[]); method @NonNull public android.text.StaticLayout.Builder setJustificationMode(int); + method @NonNull public android.text.StaticLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method @NonNull public android.text.StaticLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float); method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int); method public android.text.StaticLayout.Builder setText(CharSequence); @@ -47094,6 +47422,15 @@ package android.util { field public float ydpi; } + public interface Dumpable { + method public void dump(@NonNull java.io.PrintWriter, @Nullable String[]); + method @NonNull public default String getDumpableName(); + } + + public interface DumpableContainer { + method public boolean addDumpable(@NonNull android.util.Dumpable); + } + public class EventLog { method public static int getTagCode(String); method public static String getTagName(int); @@ -47847,15 +48184,33 @@ package android.view { public final class Choreographer { method public static android.view.Choreographer getInstance(); + method public void postExtendedFrameCallback(@NonNull android.view.Choreographer.ExtendedFrameCallback); method public void postFrameCallback(android.view.Choreographer.FrameCallback); method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long); + method public void removeExtendedFrameCallback(@Nullable android.view.Choreographer.ExtendedFrameCallback); method public void removeFrameCallback(android.view.Choreographer.FrameCallback); } + public static interface Choreographer.ExtendedFrameCallback { + method public void onVsync(@NonNull android.view.Choreographer.FrameData); + } + public static interface Choreographer.FrameCallback { method public void doFrame(long); } + public static class Choreographer.FrameData { + method public long getFrameTimeNanos(); + method @NonNull public android.view.Choreographer.FrameTimeline[] getFrameTimelines(); + method @NonNull public android.view.Choreographer.FrameTimeline getPreferredFrameTimeline(); + } + + public static class Choreographer.FrameTimeline { + method public long getDeadlineNanos(); + method public long getExpectedPresentTimeNanos(); + method public long getVsyncId(); + } + public interface CollapsibleActionView { method public void onActionViewCollapsed(); method public void onActionViewExpanded(); @@ -48001,6 +48356,18 @@ package android.view { method @NonNull public android.graphics.Insets getWaterfallInsets(); } + public static final class DisplayCutout.Builder { + ctor public DisplayCutout.Builder(); + method @NonNull public android.view.DisplayCutout build(); + method @NonNull public android.view.DisplayCutout.Builder setBoundingRectBottom(@NonNull android.graphics.Rect); + method @NonNull public android.view.DisplayCutout.Builder setBoundingRectLeft(@NonNull android.graphics.Rect); + method @NonNull public android.view.DisplayCutout.Builder setBoundingRectRight(@NonNull android.graphics.Rect); + method @NonNull public android.view.DisplayCutout.Builder setBoundingRectTop(@NonNull android.graphics.Rect); + method @NonNull public android.view.DisplayCutout.Builder setCutoutPath(@NonNull android.graphics.Path); + method @NonNull public android.view.DisplayCutout.Builder setSafeInsets(@NonNull android.graphics.Insets); + method @NonNull public android.view.DisplayCutout.Builder setWaterfallInsets(@NonNull android.graphics.Insets); + } + public final class DragAndDropPermissions implements android.os.Parcelable { method public int describeContents(); method public void release(); @@ -48155,7 +48522,7 @@ package android.view { field public static final int CLOCK_TICK = 4; // 0x4 field public static final int CONFIRM = 16; // 0x10 field public static final int CONTEXT_CLICK = 6; // 0x6 - field public static final int FLAG_IGNORE_GLOBAL_SETTING = 2; // 0x2 + field @Deprecated public static final int FLAG_IGNORE_GLOBAL_SETTING = 2; // 0x2 field public static final int FLAG_IGNORE_VIEW_SETTING = 1; // 0x1 field public static final int GESTURE_END = 13; // 0xd field public static final int GESTURE_START = 12; // 0xc @@ -49093,6 +49460,7 @@ package android.view { field public static final int EDGE_LEFT = 4; // 0x4 field public static final int EDGE_RIGHT = 8; // 0x8 field public static final int EDGE_TOP = 1; // 0x1 + field public static final int FLAG_CANCELED = 32; // 0x20 field public static final int FLAG_WINDOW_IS_OBSCURED = 1; // 0x1 field public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 2; // 0x2 field public static final int INVALID_POINTER_ID = -1; // 0xffffffff @@ -49130,6 +49498,22 @@ package android.view { field public int toolType; } + public interface OnBackInvokedCallback { + method public default void onBackInvoked(); + } + + public abstract class OnBackInvokedDispatcher { + ctor public OnBackInvokedDispatcher(); + method public abstract void registerOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback, int); + method public abstract void unregisterOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback); + field public static final int PRIORITY_DEFAULT = 0; // 0x0 + field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240 + } + + public interface OnBackInvokedDispatcherOwner { + method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); + } + public interface OnReceiveContentListener { method @Nullable public android.view.ContentInfo onReceiveContent(@NonNull android.view.View, @NonNull android.view.ContentInfo); } @@ -49553,7 +49937,7 @@ package android.view { field @NonNull public static final android.os.Parcelable.Creator<android.view.VerifiedMotionEvent> CREATOR; } - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { ctor public View(android.content.Context); ctor public View(android.content.Context, @Nullable android.util.AttributeSet); ctor public View(android.content.Context, @Nullable android.util.AttributeSet, int); @@ -49757,6 +50141,7 @@ package android.view { method @IdRes public int getNextFocusLeftId(); method @IdRes public int getNextFocusRightId(); method @IdRes public int getNextFocusUpId(); + method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method @ColorInt public int getOutlineAmbientShadowColor(); method public android.view.ViewOutlineProvider getOutlineProvider(); @@ -52195,7 +52580,10 @@ package android.view.accessibility { method public final float getFontScale(); method @Nullable public final java.util.Locale getLocale(); method @NonNull public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle(); + method public boolean isCallCaptioningEnabled(); method public final boolean isEnabled(); + method public final boolean isSystemAudioCaptioningRequested(); + method public final boolean isSystemAudioCaptioningUiRequested(); method public void removeCaptioningChangeListener(@NonNull android.view.accessibility.CaptioningManager.CaptioningChangeListener); } @@ -52224,6 +52612,8 @@ package android.view.accessibility { method public void onEnabledChanged(boolean); method public void onFontScaleChanged(float); method public void onLocaleChanged(@Nullable java.util.Locale); + method public void onSystemAudioCaptioningChanged(boolean); + method public void onSystemAudioCaptioningUiChanged(boolean); method public void onUserStyleChanged(@NonNull android.view.accessibility.CaptioningManager.CaptionStyle); } @@ -52266,6 +52656,7 @@ package android.view.animation { method public int getRepeatCount(); method public int getRepeatMode(); method protected float getScaleFactor(); + method public boolean getShowBackground(); method public long getStartOffset(); method public long getStartTime(); method public boolean getTransformation(long, android.view.animation.Transformation); @@ -52291,6 +52682,7 @@ package android.view.animation { method public void setInterpolator(android.view.animation.Interpolator); method public void setRepeatCount(int); method public void setRepeatMode(int); + method public void setShowBackground(boolean); method public void setStartOffset(long); method public void setStartTime(long); method public void setZAdjustment(int); @@ -52796,6 +53188,7 @@ package android.view.inputmethod { method public int getCharacterBoundsFlags(int); method public CharSequence getComposingText(); method public int getComposingTextStart(); + method @Nullable public android.view.inputmethod.EditorBoundsInfo getEditorBoundsInfo(); method public float getInsertionMarkerBaseline(); method public float getInsertionMarkerBottom(); method public int getInsertionMarkerFlags(); @@ -52817,11 +53210,27 @@ package android.view.inputmethod { method public android.view.inputmethod.CursorAnchorInfo build(); method public void reset(); method public android.view.inputmethod.CursorAnchorInfo.Builder setComposingText(int, CharSequence); + method @NonNull public android.view.inputmethod.CursorAnchorInfo.Builder setEditorBoundsInfo(@Nullable android.view.inputmethod.EditorBoundsInfo); method public android.view.inputmethod.CursorAnchorInfo.Builder setInsertionMarkerLocation(float, float, float, float, int); method public android.view.inputmethod.CursorAnchorInfo.Builder setMatrix(android.graphics.Matrix); method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int); } + public final class EditorBoundsInfo implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.graphics.RectF getEditorBounds(); + method @Nullable public android.graphics.RectF getHandwritingBounds(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorBoundsInfo> CREATOR; + } + + public static final class EditorBoundsInfo.Builder { + ctor public EditorBoundsInfo.Builder(); + method @NonNull public android.view.inputmethod.EditorBoundsInfo build(); + method @NonNull public android.view.inputmethod.EditorBoundsInfo.Builder setEditorBounds(@Nullable android.graphics.RectF); + method @NonNull public android.view.inputmethod.EditorBoundsInfo.Builder setHandwritingBounds(@Nullable android.graphics.RectF); + } + public class EditorInfo implements android.text.InputType android.os.Parcelable { ctor public EditorInfo(); method public int describeContents(); @@ -53131,6 +53540,7 @@ package android.view.inputmethod { method public boolean showSoftInput(android.view.View, int, android.os.ResultReceiver); method @Deprecated public void showSoftInputFromInputMethod(android.os.IBinder, int); method @Deprecated public void showStatusIcon(android.os.IBinder, String, @DrawableRes int); + method public void startStylusHandwriting(@NonNull android.view.View); method @Deprecated public boolean switchToLastInputMethod(android.os.IBinder); method @Deprecated public boolean switchToNextInputMethod(android.os.IBinder, boolean); method @Deprecated public void toggleSoftInput(int, int); @@ -54947,6 +55357,7 @@ package android.widget { method protected boolean isInFilterMode(); method public boolean isItemChecked(int); method public boolean isScrollingCacheEnabled(); + method public boolean isSelectedChildViewEnabled(); method public boolean isSmoothScrollbarEnabled(); method public boolean isStackFromBottom(); method public boolean isTextFilterEnabled(); @@ -54982,6 +55393,7 @@ package android.widget { method public void setRemoteViewsAdapter(android.content.Intent); method public void setScrollIndicators(android.view.View, android.view.View); method public void setScrollingCacheEnabled(boolean); + method public void setSelectedChildViewEnabled(boolean); method public void setSelectionFromTop(int, int); method public void setSelector(@DrawableRes int); method public void setSelector(android.graphics.drawable.Drawable); @@ -57394,6 +57806,7 @@ package android.widget { method public final android.text.Layout getLayout(); method public float getLetterSpacing(); method public int getLineBounds(int, android.graphics.Rect); + method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig(); method public int getLineCount(); method public int getLineHeight(); method public float getLineSpacingExtra(); @@ -57521,6 +57934,7 @@ package android.widget { method public void setKeyListener(android.text.method.KeyListener); method public void setLastBaselineToBottomHeight(@IntRange(from=0) @Px int); method public void setLetterSpacing(float); + method public void setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method public void setLineHeight(@IntRange(from=0) @Px int); method public void setLineSpacing(float, float); method public void setLines(int); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 53bc8a6e0323..9abf00bb250b 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -9,6 +9,10 @@ package android { package android.app { + @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + method public final boolean addDumpable(@NonNull android.util.Dumpable); + } + public class ActivityManager { method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener); method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener); @@ -67,7 +71,12 @@ package android.app.admin { package android.app.usage { public class NetworkStatsManager { + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setDefaultGlobalAlert(long); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setStatsProviderWarningAndLimitAsync(@NonNull String, long, long); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setUidForeground(int, boolean); } } @@ -88,6 +97,14 @@ package android.content { field public static final String TEST_NETWORK_SERVICE = "test_network"; } + public class Intent implements java.lang.Cloneable android.os.Parcelable { + field public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED"; + field public static final String EXTRA_SETTING_NAME = "setting_name"; + field public static final String EXTRA_SETTING_NEW_VALUE = "new_value"; + field public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value"; + field public static final String EXTRA_SETTING_RESTORED_FROM_SDK_INT = "restored_from_sdk_int"; + } + } package android.content.pm { @@ -152,6 +169,7 @@ package android.media { public final class BtProfileConnectionInfo implements android.os.Parcelable { method @NonNull public static android.media.BtProfileConnectionInfo a2dpInfo(boolean, int); + method @NonNull public static android.media.BtProfileConnectionInfo a2dpSinkInfo(int); method public int describeContents(); method public boolean getIsLeOutput(); method public int getProfile(); @@ -236,8 +254,10 @@ package android.net { public class NetworkPolicyManager { method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getMultipathPreference(@NonNull android.net.Network); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getRestrictBackgroundStatus(int); + method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.telephony.SubscriptionPlan getSubscriptionPlan(@NonNull android.net.NetworkTemplate); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidRestrictedOnMeteredNetworks(int); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyStatsProviderWarningOrLimitReached(); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerNetworkPolicyCallback(@Nullable java.util.concurrent.Executor, @NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterNetworkPolicyCallback(@NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback); } @@ -356,6 +376,7 @@ package android.os { } public class Process { + field public static final int NFC_UID = 1027; // 0x403 field public static final int VPN_UID = 1016; // 0x3f8 } diff --git a/core/api/removed.txt b/core/api/removed.txt index 07639fbf5378..311b110f1997 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -513,7 +513,7 @@ package android.util { package android.view { - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { method protected void initializeFadingEdge(android.content.res.TypedArray); method protected void initializeScrollbars(android.content.res.TypedArray); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1794c1314cca..88b0086b9fda 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -23,6 +23,7 @@ package android { field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER"; field public static final String ACCESS_TV_SHARED_FILTER = "android.permission.ACCESS_TV_SHARED_FILTER"; field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER"; + field public static final String ACCESS_ULTRASOUND = "android.permission.ACCESS_ULTRASOUND"; field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; @@ -149,6 +150,7 @@ package android { field public static final String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS"; field public static final String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS"; field public static final String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL"; + field public static final String MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED = "android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"; field public static final String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE"; field public static final String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES"; field public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE"; @@ -178,6 +180,7 @@ package android { field public static final String MANAGE_USB = "android.permission.MANAGE_USB"; field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS"; field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE"; + field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN"; field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN"; field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE"; field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED"; @@ -291,13 +294,16 @@ package android { field public static final String SET_ORIENTATION = "android.permission.SET_ORIENTATION"; field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED"; field public static final String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY"; + field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION"; field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"; field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT"; + field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT"; field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE"; field public static final String SHUTDOWN = "android.permission.SHUTDOWN"; field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS"; field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"; field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND"; + field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES"; field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS"; field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE"; field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; @@ -364,7 +370,6 @@ package android { field public static final int config_showDefaultAssistant = 17891329; // 0x1110001 field public static final int config_showDefaultEmergency = 17891330; // 0x1110002 field public static final int config_showDefaultHome = 17891331; // 0x1110003 - field public static final int config_systemCaptionsServiceCallsEnabled; } public static final class R.color { @@ -388,6 +393,7 @@ package android { field public static final int config_customMediaKeyDispatcher = 17039404; // 0x104002c field public static final int config_customMediaSessionPolicyProvider = 17039405; // 0x104002d field public static final int config_defaultAssistant = 17039393; // 0x1040021 + field public static final int config_defaultAutomotiveNavigation; field public static final int config_defaultBrowser = 17039394; // 0x1040022 field public static final int config_defaultCallRedirection = 17039397; // 0x1040025 field public static final int config_defaultCallScreening = 17039398; // 0x1040026 @@ -440,7 +446,7 @@ package android.accounts { package android.app { - @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { method public void convertFromTranslucent(); method public boolean convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions); method @Deprecated public boolean isBackgroundVisibleBehind(); @@ -769,20 +775,33 @@ package android.app { } public class KeyguardManager { + method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public long addWeakEscrowToken(@NonNull byte[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenActivatedListener); method public android.content.Intent createConfirmFactoryResetCredentialIntent(CharSequence, CharSequence, CharSequence); method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public int getMinLockLength(boolean, int); method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public boolean getPrivateNotificationsAllowed(); method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean isValidLockPasswordComplexity(int, @NonNull byte[], int); + method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenActive(long, @NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenValid(long, @NonNull byte[], @NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean registerWeakEscrowTokenRemovedListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener); + method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean removeWeakEscrowToken(long, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback); method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int); method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean unregisterWeakEscrowTokenRemovedListener(@NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener); field public static final int PASSWORD = 0; // 0x0 field public static final int PATTERN = 2; // 0x2 field public static final int PIN = 1; // 0x1 } + public static interface KeyguardManager.WeakEscrowTokenActivatedListener { + method public void onWeakEscrowTokenActivated(long, @NonNull android.os.UserHandle); + } + + public static interface KeyguardManager.WeakEscrowTokenRemovedListener { + method public void onWeakEscrowTokenRemoved(long, @NonNull android.os.UserHandle); + } + public class LocaleManager { - method @NonNull @RequiresPermission(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES) public android.os.LocaleList getApplicationLocales(@NonNull String); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void setApplicationLocales(@NonNull String, @NonNull android.os.LocaleList); } @@ -903,15 +922,21 @@ package android.app { method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void addOnProjectionStateChangedListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.OnProjectionStateChangedListener); method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int); method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public int getActiveProjectionTypes(); + method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getNightModeCustomType(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public java.util.Set<java.lang.String> getProjectingPackages(int); method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int); method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void removeOnProjectionStateChangedListener(@NonNull android.app.UiModeManager.OnProjectionStateChangedListener); method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public boolean setNightModeActivatedForCustomMode(int, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public void setNightModeCustomType(int); field public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = "android.app.action.ENTER_CAR_MODE_PRIORITIZED"; field public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = "android.app.action.EXIT_CAR_MODE_PRIORITIZED"; field public static final int DEFAULT_PRIORITY = 0; // 0x0 field public static final String EXTRA_CALLING_PACKAGE = "android.app.extra.CALLING_PACKAGE"; field public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY"; + field public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1; // 0x1 + field public static final int MODE_NIGHT_CUSTOM_TYPE_SCHEDULE = 0; // 0x0 + field public static final int MODE_NIGHT_CUSTOM_TYPE_UNKNOWN = -1; // 0xffffffff field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1 field public static final int PROJECTION_TYPE_NONE = 0; // 0x0 @@ -969,14 +994,28 @@ package android.app { public class WallpaperManager { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int); + method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount(); method public void setDisplayOffset(android.os.IBinder, int, int); method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName); + method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float); } } package android.app.admin { + public final class DevicePolicyDrawableResource implements android.os.Parcelable { + ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, int, @DrawableRes int); + ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, @DrawableRes int); + method public int describeContents(); + method @DrawableRes public int getCallingPackageResourceId(); + method public int getDrawableId(); + method public int getDrawableSource(); + method public int getDrawableStyle(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyDrawableResource> CREATOR; + } + public class DevicePolicyKeyguardService extends android.app.Service { ctor public DevicePolicyKeyguardService(); method @Nullable public void dismiss(); @@ -1009,8 +1048,10 @@ package android.app.admin { method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull int[]); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set<android.app.admin.DevicePolicyDrawableResource>); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); @@ -1021,12 +1062,12 @@ package android.app.admin { field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE"; field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED"; - field public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION"; - field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; - field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE"; + field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION"; + field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; + field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE"; field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER"; field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"; - field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER"; + field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd @@ -1042,16 +1083,19 @@ package android.app.admin { field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2 field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3 field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4 + field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER"; field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME"; field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI"; field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL"; field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME"; field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE"; + field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT = "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT"; field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER"; field public static final String EXTRA_PROVISIONING_SUPPORTED_MODES = "android.app.extra.PROVISIONING_SUPPORTED_MODES"; field public static final String EXTRA_PROVISIONING_SUPPORT_URL = "android.app.extra.PROVISIONING_SUPPORT_URL"; field public static final String EXTRA_PROVISIONING_TRIGGER = "android.app.extra.PROVISIONING_TRIGGER"; field public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION"; + field public static final String EXTRA_ROLE_HOLDER_STATE = "android.app.extra.ROLE_HOLDER_STATE"; field public static final int FLAG_SUPPORTED_MODES_DEVICE_OWNER = 4; // 0x4 field public static final int FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED = 1; // 0x1 field public static final int FLAG_SUPPORTED_MODES_PERSONALLY_OWNED = 2; // 0x2 @@ -1067,6 +1111,7 @@ package android.app.admin { field public static final int RESULT_DEVICE_OWNER_SET = 123; // 0x7b field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1; // 0x1 field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2; // 0x2 + field public static final int RESULT_UPDATE_ROLE_HOLDER = 2; // 0x2 field public static final int RESULT_WORK_PROFILE_CREATED = 122; // 0x7a field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4 field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5 @@ -1389,13 +1434,6 @@ package android.app.backup { } -package android.app.communal { - - public final class CommunalManager { - } - -} - package android.app.compat { public final class CompatChanges { @@ -2220,9 +2258,12 @@ package android.bluetooth { public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean connect(android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int connectAudio(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int disconnectAudio(); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getAudioState(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInbandRingingEnabled(); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean isInbandRingingEnabled(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean startScoUsingVirtualVoiceCall(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean stopScoUsingVirtualVoiceCall(); @@ -2301,6 +2342,14 @@ package android.bluetooth { public final class BluetoothStatusCodes { field public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000; // 0x3e8 + field public static final int ERROR_AUDIO_DEVICE_ALREADY_CONNECTED = 1116; // 0x45c + field public static final int ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED = 1117; // 0x45d + field public static final int ERROR_AUDIO_ROUTE_BLOCKED = 1118; // 0x45e + field public static final int ERROR_CALL_ACTIVE = 1119; // 0x45f + field public static final int ERROR_NOT_ACTIVE_DEVICE = 12; // 0xc + field public static final int ERROR_NO_ACTIVE_DEVICES = 13; // 0xd + field public static final int ERROR_PROFILE_NOT_CONNECTED = 14; // 0xe + field public static final int ERROR_TIMEOUT = 15; // 0xf } public final class BluetoothUuid { @@ -2601,7 +2650,6 @@ package android.content { field public static final String BATTERY_STATS_SERVICE = "batterystats"; field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000 field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000 - field public static final String COMMUNAL_SERVICE = "communal"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; field public static final String ETHERNET_SERVICE = "ethernet"; @@ -2831,7 +2879,7 @@ package android.content.pm { } public class CrossProfileApps { - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES}) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); } public class DataLoaderParams { @@ -3806,7 +3854,7 @@ package android.hardware.hdmi { } public class HdmiDeviceInfo implements android.os.Parcelable { - ctor public HdmiDeviceInfo(); + ctor @Deprecated public HdmiDeviceInfo(); method public int describeContents(); method public int getAdopterId(); method public int getDeviceId(); @@ -3827,6 +3875,7 @@ package android.hardware.hdmi { method public boolean isSourceType(); method public void writeToParcel(android.os.Parcel, int); field public static final int ADDR_INTERNAL = 0; // 0x0 + field public static final int ADDR_INVALID = -1; // 0xffffffff field @NonNull public static final android.os.Parcelable.Creator<android.hardware.hdmi.HdmiDeviceInfo> CREATOR; field public static final int DEVICE_AUDIO_SYSTEM = 5; // 0x5 field public static final int DEVICE_INACTIVE = -1; // 0xffffffff @@ -3840,6 +3889,7 @@ package android.hardware.hdmi { field public static final int PATH_INTERNAL = 0; // 0x0 field public static final int PATH_INVALID = 65535; // 0xffff field public static final int PORT_INVALID = -1; // 0xffffffff + field public static final int VENDOR_ID_UNKNOWN = 16777215; // 0xffffff } public final class HdmiHotplugEvent implements android.os.Parcelable { @@ -4140,7 +4190,7 @@ package android.hardware.location { public class ContextHubClient implements java.io.Closeable { method public void close(); method @NonNull public android.hardware.location.ContextHubInfo getAttachedHub(); - method public int getId(); + method @IntRange(from=0, to=65535) public int getId(); method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessageToNanoApp(@NonNull android.hardware.location.NanoAppMessage); } @@ -5624,6 +5674,7 @@ package android.media { method public int getCapturePreset(); method public int getSystemUsage(); method public static boolean isSystemUsage(int); + field @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public static final int CONTENT_TYPE_ULTRASOUND = 1997; // 0x7cd field public static final int FLAG_BEACON = 8; // 0x8 field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 64; // 0x40 field public static final int FLAG_BYPASS_MUTE = 128; // 0x80 @@ -5640,6 +5691,7 @@ package android.media { method public android.media.AudioAttributes.Builder setCapturePreset(int); method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioAttributes.Builder setHotwordModeEnabled(boolean); method public android.media.AudioAttributes.Builder setInternalCapturePreset(int); + method @NonNull public android.media.AudioAttributes.Builder setInternalContentType(int); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int); } @@ -5855,6 +5907,7 @@ package android.media { field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int ECHO_REFERENCE = 1997; // 0x7cd field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public static final int HOTWORD = 1999; // 0x7cf field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce + field @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public static final int ULTRASOUND = 2000; // 0x7d0 } public final class MediaRouter2 { @@ -6337,6 +6390,7 @@ package android.media.tv { method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig); method @NonNull public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String); + method public int getClientPriority(int, @Nullable String); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos(); method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList(); method @Nullable public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String); @@ -6354,6 +6408,7 @@ package android.media.tv { public static final class TvInputManager.Hardware { method public void overrideAudioSink(int, String, int, int, int); + method public void overrideAudioSink(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) int, int, int); method public void setStreamVolume(float); method public boolean setSurface(android.view.Surface, android.media.tv.TvStreamConfig); } @@ -6456,7 +6511,9 @@ package android.media.tv.tuner { } public class Lnb implements java.lang.AutoCloseable { + method public void addCallback(@NonNull android.media.tv.tuner.LnbCallback, @NonNull java.util.concurrent.Executor); method public void close(); + method public boolean removeCallback(@NonNull android.media.tv.tuner.LnbCallback); method public int sendDiseqcMessage(@NonNull byte[]); method public int setSatellitePosition(int); method public int setTone(int); @@ -6493,6 +6550,7 @@ package android.media.tv.tuner { method public void clearOnTuneEventListener(); method public void clearResourceLostListener(); method public void close(); + method public void closeFrontend(); method public int connectCiCam(int); method public int connectFrontendToCiCam(int); method public int disconnectCiCam(); @@ -6521,6 +6579,7 @@ package android.media.tv.tuner { method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener); method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener); method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner); + method public int transferOwner(@NonNull android.media.tv.tuner.Tuner); method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings); method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void updateResourcePriority(int, int); field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff @@ -8104,11 +8163,12 @@ package android.net { field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK"; } - public final class NetworkStats implements android.os.Parcelable { + public final class NetworkStats implements java.lang.Iterable<android.net.NetworkStats.Entry> android.os.Parcelable { ctor public NetworkStats(long, int); method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats); method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry); method public int describeContents(); + method @NonNull public java.util.Iterator<android.net.NetworkStats.Entry> iterator(); method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStats> CREATOR; @@ -8132,6 +8192,17 @@ package android.net { public static class NetworkStats.Entry { ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long); + method public int getDefaultNetwork(); + method public int getMetered(); + method public long getOperations(); + method public int getRoaming(); + method public long getRxBytes(); + method public long getRxPackets(); + method public int getSet(); + method public int getTag(); + method public long getTxBytes(); + method public long getTxPackets(); + method public int getUid(); } @Deprecated public class RssiCurve implements android.os.Parcelable { @@ -8168,6 +8239,7 @@ package android.net { public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); + method public static void setThreadStatsTagDownload(); method public static void setThreadStatsTagRestore(); field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = -113; // 0xffffff8f field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = -128; // 0xffffff80 @@ -9120,6 +9192,7 @@ package android.os { field public static final int EVENT_UNSPECIFIED = 0; // 0x0 field public static final int REASON_ACCOUNT_TRANSFER = 104; // 0x68 field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67 + field public static final int REASON_BLUETOOTH_BROADCAST = 203; // 0xcb field public static final int REASON_GEOFENCING = 100; // 0x64 field public static final int REASON_LOCATION_PROVIDER = 312; // 0x138 field public static final int REASON_OTHER = 1; // 0x1 @@ -9603,6 +9676,7 @@ package android.permission { method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>); + method @BinderThread public void onSelfRevokePermissions(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable); method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); @@ -10818,9 +10892,11 @@ package android.service.games { public class GameService extends android.app.Service { ctor public GameService(); - method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); + method public final void createGameSession(@IntRange(from=0) int); + method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); method public void onConnected(); method public void onDisconnected(); + method public void onGameStarted(@NonNull android.service.games.GameStartedEvent); field public static final String ACTION_GAME_SERVICE = "android.service.games.action.GAME_SERVICE"; field public static final String SERVICE_META_DATA = "android.game_service"; } @@ -10829,15 +10905,25 @@ package android.service.games { ctor public GameSession(); method public void onCreate(); method public void onDestroy(); + method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams); } public abstract class GameSessionService extends android.app.Service { ctor public GameSessionService(); - method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); + method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); method @NonNull public abstract android.service.games.GameSession onNewSession(@NonNull android.service.games.CreateGameSessionRequest); field public static final String ACTION_GAME_SESSION_SERVICE = "android.service.games.action.GAME_SESSION_SERVICE"; } + public final class GameStartedEvent implements android.os.Parcelable { + ctor public GameStartedEvent(@IntRange(from=0) int, @NonNull String); + method public int describeContents(); + method @NonNull public String getPackageName(); + method @IntRange(from=0) public int getTaskId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.games.GameStartedEvent> CREATOR; + } + } package android.service.notification { @@ -12917,6 +13003,7 @@ package android.telephony { field public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0; // 0x0 field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2 field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1 + field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5 field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4 field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3 field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE"; @@ -13085,6 +13172,7 @@ package android.telephony { } public final class UiccSlotMapping implements android.os.Parcelable { + ctor public UiccSlotMapping(int, int, int); method public int describeContents(); method @IntRange(from=0) public int getLogicalSlotIndex(); method @IntRange(from=0) public int getPhysicalSlotIndex(); @@ -13142,6 +13230,7 @@ package android.telephony.data { field public static final String TYPE_DEFAULT_STRING = "default"; field public static final String TYPE_DUN_STRING = "dun"; field public static final String TYPE_EMERGENCY_STRING = "emergency"; + field public static final String TYPE_ENTERPRISE_STRING = "enterprise"; field public static final String TYPE_FOTA_STRING = "fota"; field public static final String TYPE_HIPRI_STRING = "hipri"; field public static final String TYPE_IA_STRING = "ia"; @@ -14002,14 +14091,21 @@ package android.telephony.ims { public class ImsService extends android.app.Service { ctor public ImsService(); - method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int); - method public android.telephony.ims.feature.RcsFeature createRcsFeature(int); - method public void disableIms(int); - method public void enableIms(int); - method public android.telephony.ims.stub.ImsConfigImplBase getConfig(int); + method @Nullable public android.telephony.ims.feature.MmTelFeature createEmergencyOnlyMmTelFeature(int); + method @Deprecated public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int); + method @Nullable public android.telephony.ims.feature.MmTelFeature createMmTelFeatureForSubscription(int, int); + method @Deprecated public android.telephony.ims.feature.RcsFeature createRcsFeature(int); + method @Nullable public android.telephony.ims.feature.RcsFeature createRcsFeatureForSubscription(int, int); + method @Deprecated public void disableIms(int); + method public void disableImsForSubscription(int, int); + method @Deprecated public void enableIms(int); + method public void enableImsForSubscription(int, int); + method @Deprecated public android.telephony.ims.stub.ImsConfigImplBase getConfig(int); + method @NonNull public android.telephony.ims.stub.ImsConfigImplBase getConfigForSubscription(int, int); method @NonNull public java.util.concurrent.Executor getExecutor(); method public long getImsServiceCapabilities(); - method public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int); + method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int); + method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int); method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int); method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException; method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures(); @@ -15136,6 +15232,11 @@ package android.view.accessibility { method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int); } + public class CaptioningManager { + method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningRequested(boolean); + method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningUiRequested(boolean); + } + } package android.view.autofill { diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 716f43e1d68a..9a0642356303 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -103,6 +103,16 @@ ProtectedMember: android.service.notification.NotificationAssistantService#attac +RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimAmount(): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimmingAmount(): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimAmount(float): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimmingAmount(float): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) + + SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean): diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 66250fcebe92..0e52c5df06d7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -43,7 +43,6 @@ package android { field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; - field public static final String WRITE_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; @@ -102,7 +101,7 @@ package android.animation { package android.app { - @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { method public void onMovedToDisplay(int, android.content.res.Configuration); } @@ -557,14 +556,6 @@ package android.app.blob { } -package android.app.communal { - - public final class CommunalManager { - method @RequiresPermission(android.Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean); - } - -} - package android.app.contentsuggestions { public final class ContentSuggestionsManager { @@ -1147,15 +1138,15 @@ package android.hardware.display { public final class DisplayManager { method public boolean areUserDisabledHdrTypesAllowed(); - method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode(); + method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearGlobalUserPreferredDisplayMode(); + method @Nullable public android.view.Display.Mode getGlobalUserPreferredDisplayMode(); method @NonNull public int[] getUserDisabledHdrTypes(); - method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode(); method public boolean isMinimalPostProcessingRequested(int); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode); method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int); method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]); - method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode); method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode(); field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2 field public static final int SWITCHING_TYPE_NONE = 0; // 0x0 @@ -1319,7 +1310,7 @@ package android.location { method public void setAccumulatedDeltaRangeMeters(double); method public void setAccumulatedDeltaRangeState(int); method public void setAccumulatedDeltaRangeUncertaintyMeters(double); - method public void setAutomaticGainControlLevelInDb(double); + method @Deprecated public void setAutomaticGainControlLevelInDb(double); method public void setBasebandCn0DbHz(double); method @Deprecated public void setCarrierCycles(long); method public void setCarrierFrequencyHz(float); @@ -1346,10 +1337,6 @@ package android.location { field public static final int ADR_STATE_ALL = 31; // 0x1f } - public final class GnssMeasurementsEvent implements android.os.Parcelable { - ctor public GnssMeasurementsEvent(android.location.GnssClock, android.location.GnssMeasurement[]); - } - public final class GnssNavigationMessage implements android.os.Parcelable { ctor public GnssNavigationMessage(); method public void reset(); @@ -2714,11 +2701,14 @@ package android.view { } public final class Display { + method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode(); method @NonNull public android.view.Display.Mode getDefaultMode(); method @NonNull public int[] getReportedHdrTypes(); method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut(); method public int getType(); + method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode(); method public boolean hasAccess(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode); field public static final int FLAG_TRUSTED = 128; // 0x80 field public static final int TYPE_EXTERNAL = 2; // 0x2 field public static final int TYPE_INTERNAL = 1; // 0x1 @@ -2733,6 +2723,13 @@ package android.view { method public boolean matches(int, int, float); } + public static final class Display.Mode.Builder { + ctor public Display.Mode.Builder(); + method @NonNull public android.view.Display.Mode build(); + method @NonNull public android.view.Display.Mode.Builder setRefreshRate(float); + method @NonNull public android.view.Display.Mode.Builder setResolution(int, int); + } + public class FocusFinder { method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean); } @@ -2782,7 +2779,7 @@ package android.view { method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams); } - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { method public android.view.View getTooltipView(); method public boolean isAutofilled(); method public static boolean isDefaultFocusHighlightEnabled(); diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index e3690e55f4da..01604e6becf0 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -737,6 +737,8 @@ MissingGetterMatchingBuilder: android.telephony.ims.stub.ImsFeatureConfiguration MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String): +MissingGetterMatchingBuilder: android.view.Display.Mode.Builder#setResolution(int, int): + android.view.Display.Mode does not declare a `getResolution()` method matching method android.view.Display.Mode.Builder.setResolution(int,int) MissingNullability: android.app.Activity#onMovedToDisplay(int, android.content.res.Configuration) parameter #1: diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index cf2b7aca8e52..1e0b143749f5 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -90,6 +90,7 @@ import android.transition.Scene; import android.transition.TransitionManager; import android.util.ArrayMap; import android.util.AttributeSet; +import android.util.Dumpable; import android.util.EventLog; import android.util.Log; import android.util.PrintWriterPrinter; @@ -110,6 +111,8 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedDispatcher; +import android.view.OnBackInvokedDispatcherOwner; import android.view.RemoteAnimationDefinition; import android.view.SearchEvent; import android.view.View; @@ -145,6 +148,7 @@ import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; import com.android.internal.policy.PhoneWindow; +import com.android.internal.util.dump.DumpableContainerImpl; import dalvik.system.VMRuntime; @@ -734,7 +738,8 @@ public class Activity extends ContextThemeWrapper Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback, - ContentCaptureManager.ContentCaptureClient { + ContentCaptureManager.ContentCaptureClient, + OnBackInvokedDispatcherOwner { private static final String TAG = "Activity"; private static final boolean DEBUG_LIFECYCLE = false; @@ -954,6 +959,9 @@ public class Activity extends ContextThemeWrapper private SplashScreen mSplashScreen; + @Nullable + private DumpableContainerImpl mDumpableContainer; + private final WindowControllerCallback mWindowControllerCallback = new WindowControllerCallback() { /** @@ -7081,8 +7089,23 @@ public class Activity extends ContextThemeWrapper dumpInner(prefix, fd, writer, args); } + /** + * See {@link android.util.DumpableContainer#addDumpable(Dumpable)}. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public final boolean addDumpable(@NonNull Dumpable dumpable) { + if (mDumpableContainer == null) { + mDumpableContainer = new DumpableContainerImpl(); + } + return mDumpableContainer.addDumpable(dumpable); + } + void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { + String innerPrefix = prefix + " "; + if (args != null && args.length > 0) { // Handle special cases switch (args[0]) { @@ -7095,12 +7118,33 @@ public class Activity extends ContextThemeWrapper case "--translation": dumpUiTranslation(prefix, writer); return; + case "--list-dumpables": + if (mDumpableContainer == null) { + writer.print(prefix); writer.println("No dumpables"); + return; + } + mDumpableContainer.listDumpables(prefix, writer); + return; + case "--dump-dumpable": + if (args.length == 1) { + writer.println("--dump-dumpable requires the dumpable name"); + return; + } + if (mDumpableContainer == null) { + writer.println("no dumpables"); + return; + } + // Strips --dump-dumpable NAME + String[] prunedArgs = new String[args.length - 2]; + System.arraycopy(args, 2, prunedArgs, 0, prunedArgs.length); + mDumpableContainer.dumpOneDumpable(prefix, writer, args[1], prunedArgs); + return; } } + writer.print(prefix); writer.print("Local Activity "); writer.print(Integer.toHexString(System.identityHashCode(this))); writer.println(" State:"); - String innerPrefix = prefix + " "; writer.print(innerPrefix); writer.print("mResumed="); writer.print(mResumed); writer.print(" mStopped="); writer.print(mStopped); writer.print(" mFinished="); @@ -7138,6 +7182,10 @@ public class Activity extends ContextThemeWrapper dumpUiTranslation(prefix, writer); ResourcesManager.getInstance().dump(prefix, writer); + + if (mDumpableContainer != null) { + mDumpableContainer.dumpAllDumpables(prefix, writer, args); + } } void dumpContentCaptureManager(String prefix, PrintWriter writer) { @@ -8627,4 +8675,22 @@ public class Activity extends ContextThemeWrapper return (w != null && w.peekDecorView() != null); } } + + /** + * Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this + * activity is attached to. + * + * Returns null if the activity is not attached to a window with a decor. + */ + @Nullable + @Override + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + if (mWindow != null) { + View decorView = mWindow.getDecorView(); + if (decorView != null) { + return decorView.getOnBackInvokedDispatcher(); + } + } + return null; + } } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 324e1aea81e7..96487de5e119 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -71,6 +71,9 @@ public abstract class ActivityManagerInternal { } // Access modes for handleIncomingUser. + /** + * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}. + */ public static final int ALLOW_NON_FULL = 0; /** * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} @@ -78,13 +81,18 @@ public abstract class ActivityManagerInternal { * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. */ public static final int ALLOW_NON_FULL_IN_PROFILE = 1; + /** + * Allows access to a caller only if it has the full + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. + */ public static final int ALLOW_FULL_ONLY = 2; /** * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} - * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group. - * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. + * if in the same profile group. + * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS} is required and suffices + * as in {@link #ALLOW_NON_FULL}. */ - public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3; + public static final int ALLOW_PROFILES_OR_NON_FULL = 3; /** * Returns profile information in free form string in two separate strings. diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d90010e0f7db..686ca3bad087 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1646,7 +1646,7 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) { - PropertyInvalidatedCache.dumpCacheInfo(pfd.getFileDescriptor(), args); + PropertyInvalidatedCache.dumpCacheInfo(pfd, args); IoUtils.closeQuietly(pfd); } @@ -4535,6 +4535,12 @@ public final class ActivityThread extends ClientTransactionHandler // we are back active so skip it. unscheduleGcIdler(); + // To investigate "duplciate Application objects" bug (b/185177290) + if (UserHandle.myUserId() != UserHandle.getUserId(data.info.applicationInfo.uid)) { + Slog.wtf(TAG, "handleCreateService called with wrong appinfo UID: myUserId=" + + UserHandle.myUserId() + " appinfo.uid=" + data.info.applicationInfo.uid); + } + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; @@ -6385,9 +6391,7 @@ public final class ActivityThread extends ClientTransactionHandler if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { - for (PropertyInvalidatedCache pic : PropertyInvalidatedCache.getActiveCaches()) { - pic.clear(); - } + PropertyInvalidatedCache.onTrimMemory(); } final ArrayList<ComponentCallbacks2> callbacks = diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 49c75c49b2d7..7b55b6c0e458 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -62,6 +62,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -73,12 +74,6 @@ import android.content.pm.SuspendDialogInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.result.ParseInput; -import android.content.pm.parsing.result.ParseResult; -import android.content.pm.parsing.result.ParseTypeImpl; import android.content.pm.pkg.FrameworkPackageUserState; import android.content.res.Configuration; import android.content.res.Resources; @@ -2335,37 +2330,6 @@ public class ApplicationPackageManager extends PackageManager { return info.loadLabel(this); } - @Nullable - public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, int flags) { - return getPackageArchiveInfo(archiveFilePath, PackageInfoFlags.of(flags)); - } - - @Nullable - public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, - PackageInfoFlags flags) { - long flagsBits = flags.getValue(); - if ((flagsBits & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) { - // Caller expressed no opinion about what encryption - // aware/unaware components they want to see, so match both - flagsBits |= PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - } - - boolean collectCertificates = (flagsBits & PackageManager.GET_SIGNATURES) != 0 - || (flagsBits & PackageManager.GET_SIGNING_CERTIFICATES) != 0; - - ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset(); - ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input, - new File(archiveFilePath), 0, getPermissionManager().getSplitPermissions(), - collectCertificates); - if (result.isError()) { - return null; - } - return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flagsBits, 0, 0, - null, FrameworkPackageUserState.DEFAULT, UserHandle.getCallingUserId()); - } - @Override public int installExistingPackage(String packageName) throws NameNotFoundException { return installExistingPackage(packageName, INSTALL_REASON_UNKNOWN); diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 0bb6ffa3a035..7812aba210e7 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -430,8 +430,8 @@ public class BroadcastOptions extends ComponentOptions { * permissions set by {@link #setRequireAllOfPermissions(String[])}, and none of the * permissions set by {@link #setRequireNoneOfPermissions(String[])} to get the broadcast. * - * @param requiredPermissions a list of Strings of permission the receiver must have, or null - * to clear any previously set value. + * @param requiredPermissions a list of Strings of permission the receiver must have. Set to + * null or an empty array to clear any previously set value. * @hide */ @SystemApi @@ -449,8 +449,8 @@ public class BroadcastOptions extends ComponentOptions { * permissions set by {@link #setRequireAllOfPermissions(String[])}, and none of the * permissions set by {@link #setRequireNoneOfPermissions(String[])} to get the broadcast. * - * @param excludedPermissions a list of Strings of permission the receiver must not have, - * or null to clear any previously set value. + * @param excludedPermissions a list of Strings of permission the receiver must not have. Set to + * null or an empty array to clear any previously set value. * @hide */ @SystemApi diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f3e9f105500e..fa48730d4950 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -113,6 +113,7 @@ import java.nio.ByteOrder; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; @@ -311,6 +312,14 @@ class ContextImpl extends Context { @ContextType private int mContextType; + /** + * {@code true} to indicate that the {@link Context} owns the {@link #getWindowContextToken()} + * and is responsible for detaching the token when the Context is released. + * + * @see #finalize() + */ + private boolean mOwnsToken = false; + @GuardedBy("mSync") private File mDatabasesDir; @GuardedBy("mSync") @@ -2170,6 +2179,11 @@ class ContextImpl extends Context { } @Override + public void selfRevokePermissions(@NonNull Collection<String> permissions) { + getSystemService(PermissionManager.class).selfRevokePermissions(permissions); + } + + @Override public int checkCallingPermission(String permission) { if (permission == null) { throw new IllegalArgumentException("permission is null"); @@ -3009,7 +3023,7 @@ class ContextImpl extends Context { // WindowContainer. We should detach from WindowContainer when the Context is finalized // if this Context is not a WindowContext. WindowContext finalization is handled in // WindowContext class. - if (mToken instanceof WindowTokenClient && mContextType != CONTEXT_TYPE_WINDOW_CONTEXT) { + if (mToken instanceof WindowTokenClient && mOwnsToken) { ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded(); } super.finalize(); @@ -3040,6 +3054,7 @@ class ContextImpl extends Context { token.attachContext(context); token.attachToDisplayContent(displayId); context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI; + context.mOwnsToken = true; return context; } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 306035341ea3..a7fb83bfcf5e 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -52,6 +52,8 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedDispatcher; +import android.view.OnBackInvokedDispatcherOwner; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; @@ -94,7 +96,8 @@ import java.lang.ref.WeakReference; * </div> */ public class Dialog implements DialogInterface, Window.Callback, - KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { + KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback, + OnBackInvokedDispatcherOwner { private static final String TAG = "Dialog"; @UnsupportedAppUsage private Activity mOwnerActivity; @@ -1439,4 +1442,22 @@ public class Dialog implements DialogInterface, Window.Callback, } } } + + /** + * Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this + * dialog is attached to. + * + * Returns null if the dialog is not attached to a window with a decor. + */ + @Nullable + @Override + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + if (mWindow != null) { + View decorView = mWindow.getDecorView(); + if (decorView != null) { + return decorView.getOnBackInvokedDispatcher(); + } + } + return null; + } } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index fdcf99d164cc..a82ecce2dc04 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -63,6 +63,7 @@ interface INotificationManager boolean isInInvalidMsgState(String pkg, int uid); boolean hasUserDemotedInvalidMsgApp(String pkg, int uid); void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted); + boolean hasSentValidBubble(String pkg, int uid); void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled); /** * Updates the notification's enabled state. Additionally locks importance for all of the diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl index 440dd629c114..55afed20c826 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -48,20 +48,43 @@ interface IUiModeManager { /** * Sets the night mode. + * <p> * The mode can be one of: - * 1 - notnight mode - * 2 - night mode - * 3 - automatic mode switching + * <ol>notnight mode</ol> + * <ol>night mode</ol> + * <ol>custom schedule mode switching</ol> */ void setNightMode(int mode); /** - * Gets the currently configured night mode. Return 1 for notnight, - * 2 for night, and 3 for automatic mode switching. + * Gets the currently configured night mode. + * <p> + * Returns + * <ol>notnight mode</ol> + * <ol>night mode</ol> + * <ol>custom schedule mode switching</ol> */ int getNightMode(); /** + * Sets the current night mode to {@link #MODE_NIGHT_CUSTOM} with the custom night mode type + * {@code nightModeCustomType}. + * + * @param nightModeCustomType + * @hide + */ + void setNightModeCustomType(int nightModeCustomType); + + /** + * Returns the custom night mode type. + * <p> + * If the current night mode is not {@link #MODE_NIGHT_CUSTOM}, returns + * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}. + * @hide + */ + int getNightModeCustomType(); + + /** * Sets the dark mode for the given application. This setting is persisted and will override the * system configuration for this application. * 1 - notnight mode @@ -81,8 +104,21 @@ interface IUiModeManager { boolean isNightModeLocked(); /** - * [De]Activates night mode - */ + * [De]activating night mode for the current user if the current night mode is custom and the + * custom type matches {@code nightModeCustomType}. + * + * @param nightModeCustomType the specify type of custom mode + * @param active {@code true} to activate night mode. Otherwise, deactivate night mode + * @return {@code true} if night mode has successfully activated for the requested + * {@code nightModeCustomType}. + * @hide + */ + boolean setNightModeActivatedForCustomMode(int nightModeCustom, boolean active); + + /** + * [De]Activates night mode. + * @hide + */ boolean setNightModeActivated(boolean active); /** diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 4f7c6841d6bb..28c273ec50a6 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -204,4 +204,27 @@ interface IWallpaperManager { * @hide */ void notifyGoingToSleep(int x, int y, in Bundle extras); + + /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + * + * @hide + */ + oneway void setWallpaperDimAmount(float dimAmount); + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + * + * @hide + */ + float getWallpaperDimAmount(); + + /** + * Whether the lock screen wallpaper is different from the system wallpaper. + * + * @hide + */ + boolean lockScreenWallpaperExists(); } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index dc71a3237b0b..14afd0fcebf8 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -17,6 +17,7 @@ package android.app; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -40,8 +41,10 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.os.UserHandle; import android.provider.Settings; import android.service.persistentdata.IPersistentDataBlockService; +import android.util.ArrayMap; import android.util.Log; import android.view.IOnKeyguardExitResult; import android.view.IWindowManager; @@ -49,6 +52,9 @@ import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; import com.android.internal.policy.IKeyguardDismissCallback; +import com.android.internal.util.Preconditions; +import com.android.internal.widget.IWeakEscrowTokenActivatedListener; +import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; import com.android.internal.widget.LockscreenCredential; @@ -57,6 +63,8 @@ import com.android.internal.widget.VerifyCredentialResponse; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; /** * Class that can be used to lock and unlock the keyguard. The @@ -69,10 +77,13 @@ public class KeyguardManager { private static final String TAG = "KeyguardManager"; private final Context mContext; + private final LockPatternUtils mLockPatternUtils; private final IWindowManager mWM; private final IActivityManager mAm; private final ITrustManager mTrustManager; private final INotificationManager mNotificationManager; + private final ArrayMap<WeakEscrowTokenRemovedListener, IWeakEscrowTokenRemovedListener> + mListeners = new ArrayMap<>(); /** * Intent used to prompt user for device credentials. @@ -455,8 +466,42 @@ public class KeyguardManager { public void onDismissCancelled() { } } + /** + * Callback passed to + * {@link KeyguardManager#addWeakEscrowToken} + * to notify caller of state change. + * @hide + */ + @SystemApi + public interface WeakEscrowTokenActivatedListener { + /** + * The method to be called when the token is activated. + * @param handle 64 bit handle corresponding to the escrow token + * @param user user for whom the weak escrow token has been added + */ + void onWeakEscrowTokenActivated(long handle, @NonNull UserHandle user); + } + + /** + * Listener passed to + * {@link KeyguardManager#registerWeakEscrowTokenRemovedListener} and + * {@link KeyguardManager#unregisterWeakEscrowTokenRemovedListener} + * to notify caller of an weak escrow token has been removed. + * @hide + */ + @SystemApi + public interface WeakEscrowTokenRemovedListener { + /** + * The method to be called when the token is removed. + * @param handle 64 bit handle corresponding to the escrow token + * @param user user for whom the escrow token has been added + */ + void onWeakEscrowTokenRemoved(long handle, @NonNull UserHandle user); + } + KeyguardManager(Context context) throws ServiceNotFoundException { mContext = context; + mLockPatternUtils = new LockPatternUtils(context); mWM = WindowManagerGlobal.getWindowManagerService(); mAm = ActivityManager.getService(); mTrustManager = ITrustManager.Stub.asInterface( @@ -785,7 +830,6 @@ public class KeyguardManager { return false; } - LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); int userId = mContext.getUserId(); if (isDeviceSecure(userId)) { Log.e(TAG, "Password already set, rejecting call to setLock"); @@ -799,7 +843,7 @@ public class KeyguardManager { try { LockscreenCredential credential = createLockscreenCredential( lockType, password); - success = lockPatternUtils.setLockCredential( + success = mLockPatternUtils.setLockCredential( credential, /* savedPassword= */ LockscreenCredential.createNone(), userId); @@ -813,6 +857,150 @@ public class KeyguardManager { } /** + * Create a weak escrow token for the current user, which can later be used to unlock FBE + * or change user password. + * + * After adding, if the user currently has a secure lockscreen, they will need to perform a + * confirm credential operation in order to activate the token for future use. If the user + * has no secure lockscreen, then the token is activated immediately. + * + * If the user changes or removes the lockscreen password, any activated weak escrow token will + * be removed. + * + * @return a unique 64-bit token handle which is needed to refer to this token later. + * @hide + */ + @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE) + @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) + @SystemApi + public long addWeakEscrowToken(@NonNull byte[] token, @NonNull UserHandle user, + @NonNull @CallbackExecutor Executor executor, + @NonNull WeakEscrowTokenActivatedListener listener) { + Objects.requireNonNull(token, "Token cannot be null."); + Objects.requireNonNull(user, "User cannot be null."); + Objects.requireNonNull(executor, "Executor cannot be null."); + Objects.requireNonNull(listener, "Listener cannot be null."); + int userId = user.getIdentifier(); + IWeakEscrowTokenActivatedListener internalListener = + new IWeakEscrowTokenActivatedListener.Stub() { + @Override + public void onWeakEscrowTokenActivated(long handle, int userId) { + UserHandle user = UserHandle.of(userId); + final long restoreToken = Binder.clearCallingIdentity(); + try { + executor.execute(() -> listener.onWeakEscrowTokenActivated(handle, user)); + } finally { + Binder.restoreCallingIdentity(restoreToken); + } + Log.i(TAG, "Weak escrow token activated."); + } + }; + return mLockPatternUtils.addWeakEscrowToken(token, userId, internalListener); + } + + /** + * Remove a weak escrow token. + * + * @return true if the given handle refers to a valid weak token previously returned from + * {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise. + * @hide + */ + @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE) + @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) + @SystemApi + public boolean removeWeakEscrowToken(long handle, @NonNull UserHandle user) { + Objects.requireNonNull(user, "User cannot be null."); + return mLockPatternUtils.removeWeakEscrowToken(handle, user.getIdentifier()); + } + + /** + * Check if the given weak escrow token is active or not. + * @hide + */ + @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE) + @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) + @SystemApi + public boolean isWeakEscrowTokenActive(long handle, @NonNull UserHandle user) { + Objects.requireNonNull(user, "User cannot be null."); + return mLockPatternUtils.isWeakEscrowTokenActive(handle, user.getIdentifier()); + } + + /** + * Check if the given weak escrow token is validate. + * @hide + */ + @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE) + @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) + @SystemApi + public boolean isWeakEscrowTokenValid(long handle, @NonNull byte[] token, + @NonNull UserHandle user) { + Objects.requireNonNull(token, "Token cannot be null."); + Objects.requireNonNull(user, "User cannot be null."); + return mLockPatternUtils.isWeakEscrowTokenValid(handle, token, user.getIdentifier()); + } + + /** + * Register the given WeakEscrowTokenRemovedListener. + * + * @return true if the listener is registered successfully, return false otherwise. + * @hide + */ + @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE) + @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) + @SystemApi + public boolean registerWeakEscrowTokenRemovedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull WeakEscrowTokenRemovedListener listener) { + Objects.requireNonNull(listener, "Listener cannot be null."); + Objects.requireNonNull(executor, "Executor cannot be null."); + Preconditions.checkArgument(!mListeners.containsKey(listener), + "Listener already registered: %s", listener); + IWeakEscrowTokenRemovedListener internalListener = + new IWeakEscrowTokenRemovedListener.Stub() { + @Override + public void onWeakEscrowTokenRemoved(long handle, int userId) { + UserHandle user = UserHandle.of(userId); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> listener.onWeakEscrowTokenRemoved(handle, user)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + if (mLockPatternUtils.registerWeakEscrowTokenRemovedListener(internalListener)) { + mListeners.put(listener, internalListener); + return true; + } else { + Log.e(TAG, "Listener failed to register"); + return false; + } + } + + /** + * Unregister the given WeakEscrowTokenRemovedListener. + * + * @return true if the listener is unregistered successfully, return false otherwise. + * @hide + */ + @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE) + @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) + @SystemApi + public boolean unregisterWeakEscrowTokenRemovedListener( + @NonNull WeakEscrowTokenRemovedListener listener) { + Objects.requireNonNull(listener, "Listener cannot be null."); + IWeakEscrowTokenRemovedListener internalListener = mListeners.get(listener); + Preconditions.checkArgument(internalListener != null, "Listener was not registered"); + if (mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(internalListener)) { + mListeners.remove(listener); + return true; + } else { + Log.e(TAG, "Listener failed to unregister."); + return false; + } + } + + /** * Set the lockscreen password to {@code newPassword} after validating the current password * against {@code currentPassword}. * <p>If no password is currently set, {@code currentPassword} should be set to {@code null}. @@ -832,13 +1020,12 @@ public class KeyguardManager { }) public boolean setLock(@LockTypes int newLockType, @Nullable byte[] newPassword, @LockTypes int currentLockType, @Nullable byte[] currentPassword) { - final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); final int userId = mContext.getUserId(); LockscreenCredential currentCredential = createLockscreenCredential( currentLockType, currentPassword); LockscreenCredential newCredential = createLockscreenCredential( newLockType, newPassword); - return lockPatternUtils.setLockCredential(newCredential, currentCredential, userId); + return mLockPatternUtils.setLockCredential(newCredential, currentCredential, userId); } /** @@ -857,10 +1044,9 @@ public class KeyguardManager { Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE }) public boolean checkLock(@LockTypes int lockType, @Nullable byte[] password) { - final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); final LockscreenCredential credential = createLockscreenCredential( lockType, password); - final VerifyCredentialResponse response = lockPatternUtils.verifyCredential( + final VerifyCredentialResponse response = mLockPatternUtils.verifyCredential( credential, mContext.getUserId(), /* flags= */ 0); if (response == null) { return false; diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 77af474a04ac..4e32e9a41869 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1341,15 +1341,43 @@ public final class LoadedApk { return mResources; } + /** + * Used to investigate "duplicate app objects" bug (b/185177290). + * makeApplication() should only be called on the main thread, so no synchronization should + * be needed, but syncing anyway just in case. + */ + @GuardedBy("sApplicationCache") + private static final ArrayMap<String, Application> sApplicationCache = new ArrayMap<>(4); + @UnsupportedAppUsage public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication"); + // For b/185177290. + final boolean wrongUser = + UserHandle.myUserId() != UserHandle.getUserId(mApplicationInfo.uid); + if (wrongUser) { + Slog.wtf(TAG, "makeApplication called with wrong appinfo UID: myUserId=" + + UserHandle.myUserId() + " appinfo.uid=" + mApplicationInfo.uid); + } + synchronized (sApplicationCache) { + final Application cached = sApplicationCache.get(mPackageName); + if (cached != null) { + // Looks like this is always happening for the system server, because + // the LoadedApk created in systemMain() -> attach() isn't cached properly? + if (!"android".equals(mPackageName)) { + Slog.wtf(TAG, "App instance already created for package=" + mPackageName + + " instance=" + cached); + } + // TODO Return the cached one, unles it's for the wrong user? + // For now, we just add WTF checks. + } + } + Application app = null; final String myProcessName = Process.myProcessName(); @@ -1397,6 +1425,9 @@ public final class LoadedApk { } mActivityThread.mAllApplications.add(app); mApplication = app; + synchronized (sApplicationCache) { + sApplicationCache.put(mPackageName, app); + } if (instrumentation != null) { try { diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java new file mode 100644 index 000000000000..436eac37b4fb --- /dev/null +++ b/core/java/android/app/LocaleConfig.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.LocaleList; +import android.util.AttributeSet; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashSet; +import java.util.Set; + +/** + * The LocaleConfig of an application. + * Defined in an XML resource file with an {@code <locale-config>} element and + * referenced in the manifest via {@code android:localeConfig} on + * {@code <application>}. + * + * For more information, see TODO(b/214154050): add link to guide + * + * @attr ref android.R.styleable#LocaleConfig_Locale_name + * @attr ref android.R.styleable#AndroidManifestApplication_localeConfig + */ +public class LocaleConfig { + + private static final String TAG = "LocaleConfig"; + public static final String TAG_LOCALE_CONFIG = "locale-config"; + public static final String TAG_LOCALE = "locale"; + private LocaleList mLocales; + private int mStatus; + + /** + * succeeded reading the LocaleConfig structure stored in an XML file. + */ + public static final int STATUS_SUCCESS = 0; + /** + * No android:localeConfig tag on <application>. + */ + public static final int STATUS_NOT_SPECIFIED = 1; + /** + * Malformed input in the XML file where the LocaleConfig was stored. + */ + public static final int STATUS_PARSING_FAILED = 2; + + /** @hide */ + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_SUCCESS, + STATUS_NOT_SPECIFIED, + STATUS_PARSING_FAILED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Status{} + + /** + * Returns the LocaleConfig for the provided application context + * + * @param context the context of the application + * + * @see Context#createPackageContext(String, int). + */ + public LocaleConfig(@NonNull Context context) { + int resId = 0; + Resources res = context.getResources(); + try { + //Get the resource id + resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes(); + //Get the parser to read XML data + XmlResourceParser parser = res.getXml(resId); + parseLocaleConfig(parser, res); + } catch (Resources.NotFoundException e) { + Slog.w(TAG, "The resource file pointed to by the given resource ID isn't found.", e); + mStatus = STATUS_NOT_SPECIFIED; + } catch (XmlPullParserException | IOException e) { + Slog.w(TAG, "Failed to parse XML configuration from " + + res.getResourceEntryName(resId), e); + mStatus = STATUS_PARSING_FAILED; + } + } + + /** + * Parse the XML content and get the locales supported by the application + */ + private void parseLocaleConfig(XmlResourceParser parser, Resources res) + throws IOException, XmlPullParserException { + XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG); + int outerDepth = parser.getDepth(); + AttributeSet attrs = Xml.asAttributeSet(parser); + Set<String> localeNames = new HashSet<String>(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (TAG_LOCALE.equals(parser.getName())) { + final TypedArray attributes = res.obtainAttributes( + attrs, com.android.internal.R.styleable.LocaleConfig_Locale); + String nameAttr = attributes.getString( + com.android.internal.R.styleable.LocaleConfig_Locale_name); + localeNames.add(nameAttr); + attributes.recycle(); + } else { + XmlUtils.skipCurrentTag(parser); + } + } + mStatus = STATUS_SUCCESS; + mLocales = LocaleList.forLanguageTags(String.join(",", localeNames)); + } + + /** + * Returns the locales supported by the specified application. + * + * <p><b>Note:</b> The locale format should follow the + * <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 regular expression</a> + * + * @return the {@link LocaleList} + */ + public @Nullable LocaleList getSupportedLocales() { + return mLocales; + } + + /** + * Get the status of reading the resource file where the LocaleConfig was stored. + * + * <p>Distinguish "the application didn't provide the resource file" from "the application + * provided malformed input" if {@link #getSupportedLocales()} returns {@code null}. + * + * @return {@code STATUS_SUCCESS} if the LocaleConfig structure existed in an XML file was + * successfully read, or {@code STATUS_NOT_SPECIFIED} if no android:localeConfig tag on + * <application> pointing to an XML file that stores the LocaleConfig, or + * {@code STATUS_PARSING_FAILED} if the application provided malformed input for the + * LocaleConfig structure. + * + * @see #STATUS_SUCCESS + * @see #STATUS_NOT_SPECIFIED + * @see #STATUS_PARSING_FAILED + * + */ + public @Status int getStatus() { + return mStatus; + } +} diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java index 2aa7c8e60236..522dc845f57c 100644 --- a/core/java/android/app/LocaleManager.java +++ b/core/java/android/app/LocaleManager.java @@ -55,6 +55,9 @@ public class LocaleManager { * * <p>Pass a {@link LocaleList#getEmptyLocaleList()} to reset to the system locale. * + * <p><b>Note:</b> The set locales are persisted; they are backed up if the user has enabled + * Backup & Restore. + * * @param locales the desired locales for the calling app. */ @UserHandleAware @@ -67,6 +70,9 @@ public class LocaleManager { * * <p>Pass a {@link LocaleList#getEmptyLocaleList()} to reset to the system locale. * + * <p><b>Note:</b> The set locales are persisted; they are backed up if the user has enabled + * Backup & Restore. + * * @param appPackageName the package name of the app for which to set the locales. * @param locales the desired locales for the specified app. * @hide @@ -95,18 +101,20 @@ public class LocaleManager { } /** - * Returns the current UI locales for a specific app (described by package name). + * Returns the current UI locales for a specified app (described by package name). * * <p>Returns a {@link LocaleList#getEmptyLocaleList()} if no app-specific locales are set. * - * <b>Note:</b> Non-system apps should read Locale information via their in-process - * LocaleLists. + * <p>This API can be used by an app's installer + * (per {@link android.content.pm.InstallSourceInfo#getInstallingPackageName}) to retrieve + * the app's locales. + * All other cases require {@code android.Manifest.permission#READ_APP_SPECIFIC_LOCALES}. + * Apps should generally retrieve their own locales via their in-process LocaleLists, + * or by calling {@link #getApplicationLocales()}. * * @param appPackageName the package name of the app for which to retrieve the locales. - * @hide */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_APP_SPECIFIC_LOCALES) + @RequiresPermission(value = Manifest.permission.READ_APP_SPECIFIC_LOCALES, conditional = true) @UserHandleAware @NonNull public LocaleList getApplicationLocales(@NonNull String appPackageName) { diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 978160c1c243..ec8d989c61d4 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; @@ -29,7 +30,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastPrintWriter; -import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -355,11 +355,12 @@ public abstract class PropertyInvalidatedCache<Query, Result> { private final int mMaxEntries; /** - * Make a new property invalidated cache. + * Make a new property invalidated cache. This constructor names the cache after the + * property name. New clients should prefer the constructor that takes an explicit + * cache name. * * @param maxEntries Maximum number of entries to cache; LRU discard - * @param propertyName Name of the system property holding the cache invalidation nonce - * Defaults the cache name to the property name. + * @param propertyName Name of the system property holding the cache invalidation nonce. */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { this(maxEntries, propertyName, propertyName); @@ -418,7 +419,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Enable or disable testing. The testing property map is cleared every time this * method is called. */ - @VisibleForTesting + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void setTestMode(boolean mode) { sTesting = mode; synchronized (sTestingPropertyMap) { @@ -426,12 +427,12 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } - /** - * Enable testing the specific cache key. Only keys in the map are subject to testing. - * There is no method to stop testing a property name. Just disable the test mode. - */ - @VisibleForTesting - public static void testPropertyName(String name) { + /** + * Enable testing the specific cache key. Only keys in the map are subject to testing. + * There is no method to stop testing a property name. Just disable the test mode. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static void testPropertyName(@NonNull String name) { synchronized (sTestingPropertyMap) { sTestingPropertyMap.put(name, (long) NONCE_UNSET); } @@ -505,21 +506,23 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * block. If this function returns null, the result of the cache query is null. There is no * "negative cache" in the query: we don't cache null results at all. */ - public abstract Result recompute(Query query); + public abstract @NonNull Result recompute(@NonNull Query query); /** * Return true if the query should bypass the cache. The default behavior is to * always use the cache but the method can be overridden for a specific class. */ - public boolean bypass(Query query) { + public boolean bypass(@NonNull Query query) { return false; } /** - * Determines if a pair of responses are considered equal. Used to determine whether - * a cache is inadvertently returning stale results when VERIFY is set to true. + * Determines if a pair of responses are considered equal. Used to determine whether a + * cache is inadvertently returning stale results when VERIFY is set to true. Some + * existing clients override this method, but it is now deprecated in favor of a valid + * equals() method on the Result class. */ - protected boolean resultEquals(Result cachedResult, Result fetchedResult) { + public boolean resultEquals(Result cachedResult, Result fetchedResult) { // If a service crashes and returns a null result, the cached value remains valid. if (fetchedResult != null) { return Objects.equals(cachedResult, fetchedResult); @@ -544,8 +547,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** - * Disable the use of this cache in this process. + * Disable the use of this cache in this process. This method is using during + * testing. To disable a cache in normal code, use disableLocal(). + * @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void disableInstance() { synchronized (mLock) { mDisabled = true; @@ -558,7 +564,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * using the key will be disabled now, and all future cache instances that use the key will be * disabled in their constructor. */ - public static final void disableLocal(@NonNull String name) { + private static final void disableLocal(@NonNull String name) { synchronized (sCorkLock) { sDisabledKeys.add(name); for (PropertyInvalidatedCache cache : sCaches.keySet()) { @@ -570,7 +576,21 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** + * Stop disabling local caches with a particular name. Any caches that are currently + * disabled remain disabled (the "disabled" setting is sticky). However, new caches + * with this name will not be disabled. It is not an error if the cache name is not + * found in the list of disabled caches. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public final void clearDisableLocal() { + synchronized (sCorkLock) { + sDisabledKeys.remove(mCacheName); + } + } + + /** * Disable this cache in the current process, and all other caches that use the same + * name. This does not affect caches that have a different name but use the same * property. */ public final void disableLocal() { @@ -580,6 +600,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Return whether the cache is disabled in this process. */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final boolean isDisabledLocal() { return mDisabled || !sEnabled; } @@ -587,7 +608,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Get a value from the cache or recompute it. */ - public Result query(Query query) { + public @NonNull Result query(@NonNull Query query) { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED; if (bypass(query)) { @@ -704,6 +725,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * the cache objects to invalidate all of the cache objects becomes confusing and you should * just use the static version of this function. */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void disableSystemWide() { disableSystemWide(mPropertyName); } @@ -714,7 +736,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * * @param name Name of the cache-key property to invalidate */ - public static void disableSystemWide(@NonNull String name) { + private static void disableSystemWide(@NonNull String name) { if (!sEnabled) { return; } @@ -784,8 +806,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { "invalidating cache [%s]: [%s] -> [%s]", name, nonce, Long.toString(newValue))); } - // TODO(dancol): add an atomic compare and exchange property set operation to avoid a - // small race with concurrent disable here. + // There is a small race with concurrent disables here. A compare-and-exchange + // property operation would be required to eliminate the race condition. setNonce(name, newValue); long invalidateCount = sInvalidates.getOrDefault(name, (long) 0); sInvalidates.put(name, ++invalidateCount); @@ -990,6 +1012,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } + /** + * Return the result generated by a given query to the cache, performing debugging checks when + * enabled. + */ private Result maybeCheckConsistency(Query query, Result proposedResult) { if (VERIFY) { Result resultToCompare = recompute(query); @@ -1007,24 +1033,27 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** - * Return the name of the cache, to be used in debug messages. The - * method is public so clients can use it. + * Return the name of the cache, to be used in debug messages. */ - public String cacheName() { + private final @NonNull String cacheName() { return mCacheName; } /** - * Return the query as a string, to be used in debug messages. The - * method is public so clients can use it in external debug messages. + * Return the query as a string, to be used in debug messages. New clients should not + * override this, but should instead add the necessary toString() method to the Query + * class. */ - public String queryToString(Query query) { + protected @NonNull String queryToString(@NonNull Query query) { return Objects.toString(query); } /** - * Disable all caches in the local process. Once disabled it is not - * possible to re-enable caching in the current process. + * Disable all caches in the local process. This is primarily useful for testing when + * the test needs to bypass the cache or when the test is for a server, and the test + * process does not have privileges to write SystemProperties. Once disabled it is not + * possible to re-enable caching in the current process. If a client wants to + * temporarily disable caching, use the corking mechanism. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void disableForTestMode() { @@ -1044,7 +1073,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of caches alive at the current time. */ - public static ArrayList<PropertyInvalidatedCache> getActiveCaches() { + private static @NonNull ArrayList<PropertyInvalidatedCache> getActiveCaches() { synchronized (sCorkLock) { return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); } @@ -1053,7 +1082,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of the active corks in a process. */ - public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() { + private static @NonNull ArrayList<Map.Entry<String, Integer>> getActiveCorks() { synchronized (sCorkLock) { return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet()); } @@ -1104,14 +1133,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** - * Dumps contents of every cache in the process to the provided FileDescriptor. + * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor. */ - public static void dumpCacheInfo(FileDescriptor fd, String[] args) { + public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) { ArrayList<PropertyInvalidatedCache> activeCaches; ArrayList<Map.Entry<String, Integer>> activeCorks; try ( - FileOutputStream fout = new FileOutputStream(fd); + FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(fout); ) { if (!sEnabled) { @@ -1142,4 +1171,13 @@ public abstract class PropertyInvalidatedCache<Query, Result> { Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances"); } } + + /** + * Trim memory by clearing all the caches. + */ + public static void onTrimMemory() { + for (PropertyInvalidatedCache pic : getActiveCaches()) { + pic.clear(); + } + } } diff --git a/core/java/android/app/ServiceStartNotAllowedException.java b/core/java/android/app/ServiceStartNotAllowedException.java index 33285b2190eb..b1f47eee4bfd 100644 --- a/core/java/android/app/ServiceStartNotAllowedException.java +++ b/core/java/android/app/ServiceStartNotAllowedException.java @@ -40,4 +40,11 @@ public abstract class ServiceStartNotAllowedException extends IllegalStateExcept return new BackgroundServiceStartNotAllowedException(message); } } + + @Override + public synchronized Throwable getCause() { + // "Cause" is often used for clustering exceptions, and developers don't want to have it + // for this exception. b/210890426 + return null; + } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 85ddff9a9311..67c42f69d079 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -26,8 +26,6 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; -import android.app.communal.CommunalManager; -import android.app.communal.ICommunalManager; import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.job.JobSchedulerFrameworkInitializer; @@ -130,6 +128,7 @@ import android.media.tv.interactive.ITvIAppManager; import android.media.tv.interactive.TvIAppManager; import android.media.tv.tunerresourcemanager.ITunerResourceManager; import android.media.tv.tunerresourcemanager.TunerResourceManager; +import android.nearby.NearbyFrameworkInitializer; import android.net.ConnectivityFrameworkInitializer; import android.net.ConnectivityFrameworkInitializerTiramisu; import android.net.EthernetManager; @@ -1519,21 +1518,6 @@ public final class SystemServiceRegistry { } }); - registerService(Context.COMMUNAL_SERVICE, CommunalManager.class, - new CachedServiceFetcher<CommunalManager>() { - @Override - public CommunalManager createService(ContextImpl ctx) { - if (!ctx.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_COMMUNAL_MODE)) { - return null; - } - IBinder iBinder = - ServiceManager.getService(Context.COMMUNAL_SERVICE); - return iBinder != null ? new CommunalManager( - ICommunalManager.Stub.asInterface(iBinder)) : null; - } - }); - sInitializing = true; try { // Note: the following functions need to be @SystemApis, once they become mainline @@ -1554,6 +1538,7 @@ public final class SystemServiceRegistry { UwbFrameworkInitializer.registerServiceWrappers(); SafetyCenterFrameworkInitializer.registerServiceWrappers(); ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers(); + NearbyFrameworkInitializer.registerServiceWrappers(); } finally { // If any of the above code throws, we're in a pretty bad shape and the process // will likely crash, but we'll reset it just in case there's an exception handler... diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index 973a8fb068d1..73a9e5a221c7 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -243,6 +243,45 @@ public class UiModeManager { */ public static final int MODE_NIGHT_YES = 2; + /** + * Granular types for {@link MODE_NIGHT_CUSTOM_TYPE_BEDTIME} + * @hide + */ + @IntDef(prefix = { "MODE_NIGHT_CUSTOM_TYPE_" }, value = { + MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, + MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NightModeCustomType {} + + /** + * A granular type for {@link #MODE_NIGHT_CUSTOM} which is unknown. + * <p> + * This is the default value when the night mode is set to value other than + * {@link #MODE_NIGHT_CUSTOM}. + * @hide + */ + @SystemApi + public static final int MODE_NIGHT_CUSTOM_TYPE_UNKNOWN = -1; + + /** + * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on a custom schedule. + * <p> + * This is the default value when night mode is set to {@link #MODE_NIGHT_CUSTOM} unless the + * the night mode custom type is specified by calling {@link #setNightModeCustomType(int)}. + * @hide + */ + @SystemApi + public static final int MODE_NIGHT_CUSTOM_TYPE_SCHEDULE = 0; + + /** + * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on the bedtime schedule. + * @hide + */ + @SystemApi + public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1; + private IUiModeManager mService; /** @@ -496,6 +535,45 @@ public class UiModeManager { } /** + * Sets the current night mode to {@link #MODE_NIGHT_CUSTOM} with the custom night mode type + * {@code nightModeCustomType}. + * + * @param nightModeCustomType + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) { + if (mService != null) { + try { + mService.setNightModeCustomType(nightModeCustomType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the custom night mode type. + * <p> + * If the current night mode is not {@link #MODE_NIGHT_CUSTOM}, returns + * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + public int getNightModeCustomType() { + if (mService != null) { + try { + return mService.getNightModeCustomType(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; + } + + /** * Sets and persist the night mode for this application. * <p> * The mode can be one of: @@ -599,11 +677,36 @@ public class UiModeManager { } /** + * [De]activating night mode for the current user if the current night mode is custom and the + * custom type matches {@code nightModeCustomType}. + * + * @param nightModeCustomType the specify type of custom mode + * @param active {@code true} to activate night mode. Otherwise, deactivate night mode + * @return {@code true} if night mode has successfully activated for the requested + * {@code nightModeCustomType}. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + public boolean setNightModeActivatedForCustomMode(@NightModeCustomType int nightModeCustomType, + boolean active) { + if (mService != null) { + try { + return mService.setNightModeActivatedForCustomMode(nightModeCustomType, active); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Activating night mode for the current user * * @return {@code true} if the change is successful * @hide */ + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public boolean setNightModeActivated(boolean active) { if (mService != null) { try { diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java index 7ef0a19ec44c..067a4c3c047e 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,7 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import android.util.MathUtils; import android.util.Size; import com.android.internal.graphics.ColorUtils; @@ -44,6 +46,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -173,6 +176,22 @@ public final class WallpaperColors implements Parcelable { if (bitmap == null) { throw new IllegalArgumentException("Bitmap can't be null"); } + return fromBitmap(bitmap, 0f /* dimAmount */); + } + + /** + * Constructs {@link WallpaperColors} from a bitmap with dimming applied. + * <p> + * Main colors will be extracted from the bitmap with dimming taken into account when + * calculating dark hints. + * + * @param bitmap Source where to extract from. + * @param dimAmount Wallpaper dim amount + * @hide + */ + public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap, + @FloatRange (from = 0f, to = 1f) float dimAmount) { + Objects.requireNonNull(bitmap, "Bitmap can't be null"); final int bitmapArea = bitmap.getWidth() * bitmap.getHeight(); boolean shouldRecycle = false; @@ -211,7 +230,7 @@ public final class WallpaperColors implements Parcelable { } - int hints = calculateDarkHints(bitmap); + int hints = calculateDarkHints(bitmap, dimAmount); if (shouldRecycle) { bitmap.recycle(); @@ -507,13 +526,15 @@ public final class WallpaperColors implements Parcelable { * Checks if image is bright and clean enough to support light text. * * @param source What to read. + * @param dimAmount How much wallpaper dim amount was applied. * @return Whether image supports dark text or not. */ - private static int calculateDarkHints(Bitmap source) { + private static int calculateDarkHints(Bitmap source, float dimAmount) { if (source == null) { return 0; } + dimAmount = MathUtils.saturate(dimAmount); int[] pixels = new int[source.getWidth() * source.getHeight()]; double totalLuminance = 0; final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA); @@ -521,24 +542,37 @@ public final class WallpaperColors implements Parcelable { source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */, source.getWidth(), source.getHeight()); + // Create a new black layer with dimAmount as the alpha to be accounted for when computing + // the luminance. + int dimmingLayerAlpha = (int) (255 * dimAmount); + int blackTransparent = ColorUtils.setAlphaComponent(Color.BLACK, dimmingLayerAlpha); + // This bitmap was already resized to fit the maximum allowed area. // Let's just loop through the pixels, no sweat! float[] tmpHsl = new float[3]; for (int i = 0; i < pixels.length; i++) { - ColorUtils.colorToHSL(pixels[i], tmpHsl); - final float luminance = tmpHsl[2]; - final int alpha = Color.alpha(pixels[i]); + int pixelColor = pixels[i]; + ColorUtils.colorToHSL(pixelColor, tmpHsl); + final int alpha = Color.alpha(pixelColor); + + // Apply composite colors where the foreground is a black layer with an alpha value of + // the dim amount and the background is the wallpaper pixel color. + int compositeColors = ColorUtils.compositeColors(blackTransparent, pixelColor); + + // Calculate the adjusted luminance of the dimmed wallpaper pixel color. + double adjustedLuminance = ColorUtils.calculateLuminance(compositeColors); + // Make sure we don't have a dark pixel mass that will // make text illegible. final boolean satisfiesTextContrast = ContrastColorUtil - .calculateContrast(pixels[i], Color.BLACK) > DARK_PIXEL_CONTRAST; + .calculateContrast(pixelColor, Color.BLACK) > DARK_PIXEL_CONTRAST; if (!satisfiesTextContrast && alpha != 0) { darkPixels++; if (DEBUG_DARK_PIXELS) { pixels[i] = Color.RED; } } - totalLuminance += luminance; + totalLuminance += adjustedLuminance; } int hints = 0; diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 772492dbfd5e..0a18588e0131 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -71,6 +72,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.view.Display; import android.view.WindowManagerGlobal; @@ -1489,27 +1491,18 @@ public class WallpaperManager { mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; - final Bitmap tmp = BitmapFactory.decodeStream(resources.openRawResource(resid)); + boolean ok = false; try { - // If the stream can't be decoded, treat it as an invalid input. - if (tmp != null) { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); - // The 'close()' is the trigger for any server-side image manipulation, - // so we must do that before waiting for completion. - fos.close(); - completion.waitForCompletion(); - } else { - throw new IllegalArgumentException( - "Resource 0x" + Integer.toHexString(resid) + " is invalid"); - } + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + copyStreamToWallpaperFile(resources.openRawResource(resid), fos); + // The 'close()' is the trigger for any server-side image manipulation, + // so we must do that before waiting for completion. + fos.close(); + completion.waitForCompletion(); } finally { // Might be redundant but completion shouldn't wait unless the write // succeeded; this is a fallback if it threw past the close+wait. IoUtils.closeQuietly(fos); - if (tmp != null) { - tmp.recycle(); - } } } } catch (RemoteException e) { @@ -1751,22 +1744,13 @@ public class WallpaperManager { result, which, completion, mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; - final Bitmap tmp = BitmapFactory.decodeStream(bitmapData); try { - // If the stream can't be decoded, treat it as an invalid input. - if (tmp != null) { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); - fos.close(); - completion.waitForCompletion(); - } else { - throw new IllegalArgumentException("InputStream is invalid"); - } + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + copyStreamToWallpaperFile(bitmapData, fos); + fos.close(); + completion.waitForCompletion(); } finally { IoUtils.closeQuietly(fos); - if (tmp != null) { - tmp.recycle(); - } } } } catch (RemoteException e) { @@ -2012,6 +1996,63 @@ public class WallpaperManager { } /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) + public void setWallpaperDimAmount(@FloatRange (from = 0f, to = 1f) float dimAmount) { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + sGlobals.mService.setWallpaperDimAmount(MathUtils.saturate(dimAmount)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) + public float getWallpaperDimAmount() { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + return sGlobals.mService.getWallpaperDimAmount(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Whether the lock screen wallpaper is different from the system wallpaper. + * + * @hide + */ + public boolean lockScreenWallpaperExists() { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + return sGlobals.mService.lockScreenWallpaperExists(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Set the live wallpaper. * * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT diff --git a/core/java/android/app/admin/DevicePolicyDrawableResource.aidl b/core/java/android/app/admin/DevicePolicyDrawableResource.aidl new file mode 100644 index 000000000000..6b73d9815fe6 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyDrawableResource.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.app.admin; + +parcelable DevicePolicyDrawableResource; diff --git a/core/java/android/app/admin/DevicePolicyDrawableResource.java b/core/java/android/app/admin/DevicePolicyDrawableResource.java new file mode 100644 index 000000000000..d32ff841f802 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyDrawableResource.java @@ -0,0 +1,203 @@ +/* + * 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 android.app.admin; + +import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Used to pass in the required information for updating an enterprise drawable resource using + * {@link DevicePolicyManager#setDrawables}. + * + * @hide + */ +@SystemApi +public final class DevicePolicyDrawableResource implements Parcelable { + private final @DevicePolicyResources.UpdatableDrawableId int mDrawableId; + private final @DevicePolicyResources.UpdatableDrawableStyle int mDrawableStyle; + private final @DevicePolicyResources.UpdatableDrawableSource int mDrawableSource; + private final @DrawableRes int mCallingPackageResourceId; + @NonNull private ParcelableResource mResource; + + /** + * Creates an object containing the required information for updating an enterprise drawable + * resource using {@link DevicePolicyManager#setDrawables}. + * + * <p>It will be used to update the drawable defined by {@code drawableId} with style + * {@code drawableStyle} located in source {@code drawableSource} to the drawable with ID + * {@code callingPackageResourceId} in the calling package</p> + * + * @param drawableId The ID of the drawable to update. + * @param drawableStyle The style of the drawable to update. + * @param drawableSource The source of the drawable to update. + * @param callingPackageResourceId The ID of the drawable resource in the calling package to + * use as an updated resource. + * + * @throws IllegalStateException if the resource with ID + * {@code callingPackageResourceId} doesn't exist in the {@code context} package. + */ + public DevicePolicyDrawableResource( + @NonNull Context context, + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @DrawableRes int callingPackageResourceId) { + this(drawableId, drawableStyle, drawableSource, callingPackageResourceId, + new ParcelableResource(context, callingPackageResourceId, + ParcelableResource.RESOURCE_TYPE_DRAWABLE)); + } + + private DevicePolicyDrawableResource( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @DrawableRes int callingPackageResourceId, + @NonNull ParcelableResource resource) { + this.mDrawableId = drawableId; + this.mDrawableStyle = drawableStyle; + this.mDrawableSource = drawableSource; + this.mCallingPackageResourceId = callingPackageResourceId; + this.mResource = resource; + } + + /** + * Creates an object containing the required information for updating an enterprise drawable + * resource using {@link DevicePolicyManager#setDrawables}. + * <p>It will be used to update the drawable defined by {@code drawableId} with style + * {@code drawableStyle} to the drawable with ID {@code callingPackageResourceId} in the + * calling package</p> + * + * @param drawableId The ID of the drawable to update. + * @param drawableStyle The style of the drawable to update. + * @param callingPackageResourceId The ID of the drawable resource in the calling package to + * use as an updated resource. + * + * @throws IllegalStateException if the resource with ID + * {@code callingPackageResourceId} doesn't exist in the calling package. + */ + public DevicePolicyDrawableResource( + @NonNull Context context, + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DrawableRes int callingPackageResourceId) { + this(context, drawableId, drawableStyle, DevicePolicyResources.Drawable.Source.UNDEFINED, + callingPackageResourceId); + } + + /** + * Returns the ID of the drawable to update. + */ + @DevicePolicyResources.UpdatableDrawableId + public int getDrawableId() { + return mDrawableId; + } + + /** + * Returns the style of the drawable to update + */ + @DevicePolicyResources.UpdatableDrawableStyle + public int getDrawableStyle() { + return mDrawableStyle; + } + + /** + * Returns the source of the drawable to update. + */ + @DevicePolicyResources.UpdatableDrawableSource + public int getDrawableSource() { + return mDrawableSource; + } + + /** + * Returns the ID of the drawable resource in the calling package to use as an updated + * resource. + */ + @DrawableRes + public int getCallingPackageResourceId() { + return mCallingPackageResourceId; + } + + /** + * Returns the {@link ParcelableResource} of the drawable. + * + * @hide + */ + @NonNull + public ParcelableResource getResource() { + return mResource; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DevicePolicyDrawableResource other = (DevicePolicyDrawableResource) o; + return mDrawableId == other.mDrawableId + && mDrawableStyle == other.mDrawableStyle + && mDrawableSource == other.mDrawableSource + && mCallingPackageResourceId == other.mCallingPackageResourceId + && mResource.equals(other.mResource); + } + + @Override + public int hashCode() { + return Objects.hash( + mDrawableId, mDrawableStyle, mDrawableSource, mCallingPackageResourceId, mResource); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDrawableId); + dest.writeInt(mDrawableStyle); + dest.writeInt(mDrawableSource); + dest.writeInt(mCallingPackageResourceId); + dest.writeTypedObject(mResource, flags); + } + + public static final @NonNull Creator<DevicePolicyDrawableResource> CREATOR = + new Creator<DevicePolicyDrawableResource>() { + @Override + public DevicePolicyDrawableResource createFromParcel(Parcel in) { + int drawableId = in.readInt(); + int drawableStyle = in.readInt(); + int drawableSource = in.readInt(); + int callingPackageResourceId = in.readInt(); + ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR); + + return new DevicePolicyDrawableResource( + drawableId, drawableStyle, drawableSource, callingPackageResourceId, + resource); + } + + @Override + public DevicePolicyDrawableResource[] newArray(int size) { + return new DevicePolicyDrawableResource[size]; + } + }; +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 7969cda7b84d..658dcf393d5d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,6 +16,9 @@ package android.app.admin; +import static android.app.admin.DevicePolicyResources.Drawable.INVALID_ID; +import static android.app.admin.DevicePolicyResources.Drawable.Source.UNDEFINED; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest.permission; @@ -52,7 +55,9 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.net.PrivateDnsConnectivityChecker; import android.net.ProxyInfo; import android.net.Uri; @@ -89,6 +94,7 @@ import android.telephony.data.ApnSetting; import android.text.TextUtils; import android.util.ArraySet; import android.util.DebugUtils; +import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; @@ -123,6 +129,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -480,6 +487,10 @@ public class DevicePolicyManager { * * <p>Device management role holders are required to have a handler for this intent action. * + * <p>If {@link #EXTRA_ROLE_HOLDER_STATE} is supplied to this intent, it is the responsibility + * of the role holder to restore its state from this extra. This is the same {@link Bundle} + * which the role holder returns alongside {@link #RESULT_UPDATE_ROLE_HOLDER}. + * * <p>A result code of {@link Activity#RESULT_OK} implies that managed profile provisioning * finished successfully. If it did not, a result code of {@link Activity#RESULT_CANCELED} * is used instead. @@ -488,8 +499,7 @@ public class DevicePolicyManager { * * @hide */ - // TODO(b/208628038): Uncomment when the permission is in place - // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER) + @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @SystemApi public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = @@ -528,6 +538,10 @@ public class DevicePolicyManager { * * <p>Device management role holders are required to have a handler for this intent action. * + * <p>If {@link #EXTRA_ROLE_HOLDER_STATE} is supplied to this intent, it is the responsibility + * of the role holder to restore its state from this extra. This is the same {@link Bundle} + * which the role holder returns alongside {@link #RESULT_UPDATE_ROLE_HOLDER}. + * * <p>The result codes can be either {@link #RESULT_WORK_PROFILE_CREATED}, {@link * #RESULT_DEVICE_OWNER_SET} or {@link Activity#RESULT_CANCELED} if provisioning failed. * @@ -535,8 +549,7 @@ public class DevicePolicyManager { * * @hide */ - // TODO(b/208628038): Uncomment when the permission is in place - // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER) + @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @SystemApi public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = @@ -561,14 +574,80 @@ public class DevicePolicyManager { * * @hide */ - // TODO(b/208628038): Uncomment when the permission is in place - // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER) + @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @SystemApi public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION"; /** + * {@link Activity} result code which can be returned by {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} and {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} to signal that an update + * to the role holder is required. + * + * <p>This result code must be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}. + * + * @hide + */ + @SystemApi + public static final int RESULT_UPDATE_ROLE_HOLDER = 2; + + /** + * A {@link Bundle} extra which describes the state of the role holder at the time when it + * returns {@link #RESULT_UPDATE_ROLE_HOLDER}. + * + * <p>After the update completes, the role holder's {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent will be relaunched, + * which will contain this extra. It is the role holder's responsibility to restore its + * state from this extra. + * + * @hide + */ + @SystemApi + public static final String EXTRA_ROLE_HOLDER_STATE = "android.app.extra.ROLE_HOLDER_STATE"; + + /** + * A {@code boolean} extra which determines whether to force a role holder update, regardless + * of any internal conditions {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER} might have. + * + * <p>This extra can be provided to intents with action {@link + * #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}. + * + * @hide + */ + @SystemApi + public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = + "android.app.extra.FORCE_UPDATE_ROLE_HOLDER"; + + /** + * A boolean extra indicating whether offline provisioning is allowed. + * + * <p>For the online provisioning flow, there will be an attempt to download and install + * the latest version of the device management role holder. The platform will then delegate + * provisioning to the device management role holder via role holder-specific provisioning + * actions. + * + * <p>For the offline provisioning flow, the provisioning flow will always be handled by + * the platform. + * + * <p>If this extra is set to {@code false}, the provisioning flow will enforce that an + * internet connection is established, which will start the online provisioning flow. If an + * internet connection cannot be established, provisioning will fail. + * + * <p>If this extra is set to {@code true}, the provisioning flow will still try to connect to + * the internet, but if it fails it will start the offline provisioning flow. + * + * <p>The default value is {@code false}. + * + * <p>This extra is respected when provided via the provisioning intent actions such as {@link + * #ACTION_PROVISION_MANAGED_PROFILE}. + */ + public static final String EXTRA_PROVISIONING_ALLOW_OFFLINE = + "android.app.extra.PROVISIONING_ALLOW_OFFLINE"; + + /** * Action: Bugreport sharing with device owner has been accepted by the user. * * @hide @@ -2832,6 +2911,20 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE"; /** + * A {@code boolean} flag that indicates whether the screen should be on throughout the + * provisioning flow. + * + * <p>The default value is {@code false}. + * + * <p>This extra can either be passed as an extra to the {@link + * #ACTION_PROVISION_MANAGED_PROFILE} intent, or it can be returned by the + * admin app when performing the admin-integrated provisioning flow as a result of the + * {@link #ACTION_GET_PROVISIONING_MODE} activity. + */ + public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON = + "android.app.extra.PROVISIONING_KEEP_SCREEN_ON"; + + /** * Activity action: Starts the administrator to show policy compliance for the provisioning. * This action is used any time that the administrator has an opportunity to show policy * compliance before the end of setup wizard. This could happen as part of the admin-integrated @@ -2857,10 +2950,12 @@ public class DevicePolicyManager { * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters a problem * that will not be solved by relaunching it again. * + * <p>If this activity has additional internal conditions which are not met, it should return + * {@link #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR}. + * * @hide */ - // TODO(b/208628038): Uncomment when the permission is in place - // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER) + @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @SystemApi public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = @@ -2885,6 +2980,38 @@ public class DevicePolicyManager { public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2; /** + * An {@link Intent} extra which resolves to a custom user consent screen. + * + * <p>If this extra is provided to the device management role holder via either {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE}, the device management role holder must + * launch this intent which shows the custom user consent screen, replacing its own standard + * consent screen. + * + * <p>If this extra is provided, it is the responsibility of the intent handler to show the + * list of disclaimers which are normally shown by the standard consent screen: + * <ul> + * <li>Disclaimers set by the IT admin via the {@link #EXTRA_PROVISIONING_DISCLAIMERS} + * provisioning extra</li> + * <li>For fully-managed device provisioning, disclaimers defined in system apps via the + * {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER} and {@link + * #EXTRA_PROVISIONING_DISCLAIMER_CONTENT} metadata in their manifests</li> + * <li>General disclaimer relevant to the provisioning mode</li> + * </ul> + * + * <p>If the custom consent screens are granted by the user {@link Activity#RESULT_OK} is + * returned, otherwise {@link Activity#RESULT_CANCELED} is returned. The device management + * role holder should ensure that the provisioning flow terminates immediately if consent + * is not granted by the user. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @SystemApi + public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT = + "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT"; + + /** * Maximum supported password length. Kind-of arbitrary. * @hide */ @@ -3153,6 +3280,36 @@ public class DevicePolicyManager { */ public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; + /** + * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management + * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated using, the updated resources + * can be retrieved using {@link #getDrawable}. + * + * <p>This broadcast is sent to registered receivers only. + * + * <p> The following extras will be included to identify the type of resource being updated: + * <ul> + * <li>{@link #EXTRA_RESOURCE_TYPE_DRAWABLE} for drawable resources</li> + * </ul> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = + "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED"; + + /** + * A boolean extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate that a + * resource of type {@link Drawable} is being updated. + */ + public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = + "android.app.extra.RESOURCE_TYPE_DRAWABLE"; + + /** + * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which + * drawable IDs (see {@link DevicePolicyResources.UpdatableDrawableId}) have been updated. + */ + public static final String EXTRA_RESOURCE_ID = + "android.app.extra.RESOURCE_ID"; + /** @hide */ @NonNull @TestApi @@ -14344,4 +14501,243 @@ public class DevicePolicyManager { public Intent createProvisioningIntentFromNfcIntent(@NonNull Intent nfcIntent) { return ProvisioningIntentHelper.createProvisioningIntentFromNfcIntent(nfcIntent); } + + /** + * For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if + * {@link DevicePolicyDrawableResource#getDrawableSource()} is not set or is set to + * {@link DevicePolicyResources.Drawable.Source#UNDEFINED}, it updates the drawable resource for + * the combination of {@link DevicePolicyDrawableResource#getDrawableId()} and + * {@link DevicePolicyDrawableResource#getDrawableStyle()}, (see + * {@link DevicePolicyResources.Drawable} and {@link DevicePolicyResources.Drawable.Style}) to + * the drawable with ID {@link DevicePolicyDrawableResource#getCallingPackageResourceId()}, + * meaning any system UI surface calling {@link #getDrawable} + * with {@code drawableId} and {@code drawableStyle} will get the new resource after this API + * is called. + * + * <p>Otherwise, if {@link DevicePolicyDrawableResource#getDrawableSource()} is set (see + * {@link DevicePolicyResources.Drawable.Source}, it overrides any drawables that was set for + * the same {@code drawableId} and {@code drawableStyle} for the provided source. + * + * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been updated successfully. + * + * <p>Important notes to consider when using this API: + * <ul> + * <li>{@link #getDrawable} references the resource + * {@link DevicePolicyDrawableResource#getCallingPackageResourceId()} in the + * calling package each time it gets called. You have to ensure that the resource is always + * available in the calling package as long as it is used as an updated resource. + * <li>You still have to re-call {@code setDrawables} even if you only make changes to the + * content of the resource with ID + * {@link DevicePolicyDrawableResource#getCallingPackageResourceId()} as the content might be + * cached and would need updating. + * </ul> + * + * @param drawables The list of {@link DevicePolicyDrawableResource} to update. + * + * @throws IllegalArgumentException if {@link DevicePolicyDrawableResource#getDrawableId()}, + * {@link DevicePolicyDrawableResource#getDrawableStyle()}, or + * {@link DevicePolicyDrawableResource#getDrawableSource()} aren't defined in + * {@link DevicePolicyResources.Drawable}. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void setDrawables(@NonNull Set<DevicePolicyDrawableResource> drawables) { + if (mService != null) { + try { + mService.setDrawables(new ArrayList<>(drawables)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Removes all updated drawables for the list of {@code drawableIds} (see + * {@link DevicePolicyResources.Drawable} that was previously set by calling + * {@link #setDrawables}, meaning any subsequent calls to {@link #getDrawable} for the provided + * IDs with any {@link DevicePolicyResources.Drawable.Style} and any + * {@link DevicePolicyResources.Drawable.Source} will return the default drawable from + * {@code defaultDrawableLoader}. + * + * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been reset successfully. + * + * @param drawableIds The list of IDs to remove. + * + * @throws IllegalArgumentException if IDs are not defined in + * {@link DevicePolicyResources.Drawable} + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void resetDrawables(@NonNull int[] drawableIds) { + if (mService != null) { + try { + mService.resetDrawables(drawableIds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the appropriate updated drawable for the {@code drawableId} + * (see {@link DevicePolicyResources.Drawable}), with style {@code drawableStyle} + * (see {@link DevicePolicyResources.Drawable.Style}) if one was set using + * {@code setDrawables}, otherwise returns the drawable from {@code defaultDrawableLoader}. + * + * <p>Also returns the drawable from {@code defaultDrawableLoader} if + * {@link DevicePolicyResources.Drawable#INVALID_ID} was passed. + * + * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to + * set a different value use + * {@link #getDrawableForDensity(int, int, int, Callable)}. + * + * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * + * <p>Note that each call to this API loads the resource from the package that called + * {@code setDrawables} to set the updated resource. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @Nullable + public Drawable getDrawable( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @NonNull Callable<Drawable> defaultDrawableLoader) { + return getDrawable(drawableId, drawableStyle, UNDEFINED, defaultDrawableLoader); + } + + /** + * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts + * a {@code drawableSource} (see {@link DevicePolicyResources.Drawable.Source}) which + * could result in returning a different drawable than {@link #getDrawable(int, int, Callable)} + * if an override was set for that specific source. + * + * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param drawableSource The source for the caller. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @Nullable + public Drawable getDrawable( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @NonNull Callable<Drawable> defaultDrawableLoader) { + Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); + if (drawableId == INVALID_ID) { + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getDrawable( + drawableId, drawableStyle, drawableSource); + if (resource == null) { + return ParcelableResource.loadDefaultDrawable( + defaultDrawableLoader); + } + return resource.getDrawable( + mContext, + /* density= */ 0, + defaultDrawableLoader); + + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated drawable from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + } + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + + /** + * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts + * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. + * + * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param density The desired screen density indicated by the resource as + * found in {@link DisplayMetrics}. A value of 0 means to use the + * density returned from {@link Resources#getConfiguration()}. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @Nullable + public Drawable getDrawableForDensity( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + int density, + @NonNull Callable<Drawable> defaultDrawableLoader) { + return getDrawableForDensity( + drawableId, + drawableStyle, + UNDEFINED, + density, + defaultDrawableLoader); + } + + /** + * Similar to {@link #getDrawable(int, int, int, Callable)}, but also accepts + * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. + * + * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param drawableSource The source for the caller. + * @param density The desired screen density indicated by the resource as + * found in {@link DisplayMetrics}. A value of 0 means to use the + * density returned from {@link Resources#getConfiguration()}. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @Nullable + public Drawable getDrawableForDensity( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + int density, + @NonNull Callable<Drawable> defaultDrawableLoader) { + Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); + if (drawableId == INVALID_ID) { + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getDrawable( + drawableId, drawableStyle, drawableSource); + if (resource == null) { + return ParcelableResource.loadDefaultDrawable( + defaultDrawableLoader); + } + return resource.getDrawable(mContext, density, defaultDrawableLoader); + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated drawable from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + } + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } } diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java new file mode 100644 index 000000000000..5133f26f8111 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -0,0 +1,243 @@ +/* + * 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 android.app.admin; + +import android.annotation.IntDef; +import android.annotation.SuppressLint; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashSet; +import java.util.Set; + +/** + * Class containing the required identifiers to update device management resources. + * + * <p>See {@link DevicePolicyManager#getDrawable}. + * + */ +public final class DevicePolicyResources { + + /** + * Resource identifiers used to update device management-related system drawable resources. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.INVALID_ID, + Drawable.WORK_PROFILE_ICON_BADGE, + Drawable.WORK_PROFILE_ICON, + Drawable.WORK_PROFILE_OFF_ICON, + Drawable.WORK_PROFILE_USER_ICON + }) + public @interface UpdatableDrawableId {} + + /** + * Identifiers to specify the desired style for the updatable device management system + * resource. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.Style.SOLID_COLORED, + Drawable.Style.SOLID_NOT_COLORED, + Drawable.Style.OUTLINE, + }) + public @interface UpdatableDrawableStyle {} + + /** + * Identifiers to specify the location if the updatable device management system resource. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.Source.UNDEFINED, + Drawable.Source.NOTIFICATION, + Drawable.Source.PROFILE_SWITCH_ANIMATION, + Drawable.Source.HOME_WIDGET, + Drawable.Source.LAUNCHER_OFF_BUTTON, + Drawable.Source.QUICK_SETTINGS, + Drawable.Source.STATUS_BAR + }) + public @interface UpdatableDrawableSource {} + + + /** + * Class containing the identifiers used to update device management-related system drawable. + */ + public static final class Drawable { + + private Drawable() { + } + + /** + * An ID for any drawable that can't be updated. + */ + public static final int INVALID_ID = -1; + + /** + * Specifically used to badge work profile app icons. + */ + public static final int WORK_PROFILE_ICON_BADGE = 0; + + /** + * General purpose work profile icon (i.e. generic icon badging). For badging app icons + * specifically, see {@link #WORK_PROFILE_ICON_BADGE}. + */ + public static final int WORK_PROFILE_ICON = 1; + + /** + * General purpose icon representing the work profile off state. + */ + public static final int WORK_PROFILE_OFF_ICON = 2; + + /** + * General purpose icon for the work profile user avatar. + */ + public static final int WORK_PROFILE_USER_ICON = 3; + + /** + * @hide + */ + public static final Set<Integer> UPDATABLE_DRAWABLE_IDS = buildDrawablesSet(); + + private static Set<Integer> buildDrawablesSet() { + Set<Integer> drawables = new HashSet<>(); + drawables.add(WORK_PROFILE_ICON_BADGE); + drawables.add(WORK_PROFILE_ICON); + drawables.add(WORK_PROFILE_OFF_ICON); + drawables.add(WORK_PROFILE_USER_ICON); + return drawables; + } + + /** + * Class containing the source identifiers used to update device management-related system + * drawable. + */ + public static final class Source { + + private Source() { + } + + /** + * A source identifier indicating that the updatable resource is used in a generic + * undefined location. + */ + public static final int UNDEFINED = -1; + + /** + * A source identifier indicating that the updatable drawable is used in notifications. + */ + public static final int NOTIFICATION = 0; + + /** + * A source identifier indicating that the updatable drawable is used in a cross + * profile switching animation. + */ + public static final int PROFILE_SWITCH_ANIMATION = 1; + + /** + * A source identifier indicating that the updatable drawable is used in a work + * profile home screen widget. + */ + public static final int HOME_WIDGET = 2; + + /** + * A source identifier indicating that the updatable drawable is used in the launcher + * turn off work button. + */ + public static final int LAUNCHER_OFF_BUTTON = 3; + + /** + * A source identifier indicating that the updatable drawable is used in quick settings. + */ + public static final int QUICK_SETTINGS = 4; + + /** + * A source identifier indicating that the updatable drawable is used in the status bar. + */ + public static final int STATUS_BAR = 5; + + /** + * @hide + */ + public static final Set<Integer> UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet(); + + private static Set<Integer> buildSourcesSet() { + Set<Integer> sources = new HashSet<>(); + sources.add(UNDEFINED); + sources.add(NOTIFICATION); + sources.add(PROFILE_SWITCH_ANIMATION); + sources.add(HOME_WIDGET); + sources.add(LAUNCHER_OFF_BUTTON); + sources.add(QUICK_SETTINGS); + sources.add(STATUS_BAR); + return sources; + } + } + + /** + * Class containing the style identifiers used to update device management-related system + * drawable. + */ + @SuppressLint("StaticUtils") + public static final class Style { + + private Style() { + } + + /** + * A style identifier indicating that the updatable drawable should use the default + * style. + */ + public static final int DEFAULT = -1; + + /** + * A style identifier indicating that the updatable drawable has a solid color fill. + */ + public static final int SOLID_COLORED = 0; + + /** + * A style identifier indicating that the updatable drawable has a solid non-colored + * fill. + */ + public static final int SOLID_NOT_COLORED = 1; + + /** + * A style identifier indicating that the updatable drawable is an outline. + */ + public static final int OUTLINE = 2; + + /** + * @hide + */ + public static final Set<Integer> UPDATABLE_DRAWABLE_STYLES = buildStylesSet(); + + private static Set<Integer> buildStylesSet() { + Set<Integer> styles = new HashSet<>(); + styles.add(DEFAULT); + styles.add(SOLID_COLORED); + styles.add(SOLID_NOT_COLORED); + styles.add(OUTLINE); + return styles; + } + } + } +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b9fcdf537806..832008755451 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -17,6 +17,8 @@ package android.app.admin; +import android.app.admin.DevicePolicyDrawableResource; +import android.app.admin.ParcelableResource; import android.app.admin.NetworkEvent; import android.app.IApplicationThread; import android.app.IServiceConnection; @@ -531,4 +533,7 @@ interface IDevicePolicyManager { boolean canUsbDataSignalingBeDisabled(); List<UserHandle> listForegroundAffiliatedUsers(); + void setDrawables(in List<DevicePolicyDrawableResource> resource); + void resetDrawables(in int[] drawableIds); + ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource); } diff --git a/core/java/android/app/admin/ParcelableResource.aidl b/core/java/android/app/admin/ParcelableResource.aidl new file mode 100644 index 000000000000..dd2b9751921d --- /dev/null +++ b/core/java/android/app/admin/ParcelableResource.aidl @@ -0,0 +1,20 @@ +/* + * 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 android.app.admin; + +parcelable ParcelableResource; diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java new file mode 100644 index 000000000000..e5171622be4a --- /dev/null +++ b/core/java/android/app/admin/ParcelableResource.java @@ -0,0 +1,286 @@ +/* + * 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 android.app.admin; + +import static java.util.Objects.requireNonNull; + +import android.annotation.AnyRes; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.concurrent.Callable; + +/** + * Used to store the required information to load a resource that was updated using + * {@link DevicePolicyManager#setDrawables}. + * + * @hide + */ +public final class ParcelableResource implements Parcelable { + + private static String TAG = "DevicePolicyManager"; + + private static final String ATTR_RESOURCE_ID = "resource-id"; + private static final String ATTR_PACKAGE_NAME = "package-name"; + private static final String ATTR_RESOURCE_NAME = "resource-name"; + private static final String ATTR_RESOURCE_TYPE = "resource-type"; + + public static final int RESOURCE_TYPE_DRAWABLE = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "RESOURCE_TYPE_" }, value = { + RESOURCE_TYPE_DRAWABLE + }) + public @interface ResourceType {} + + private final int mResourceId; + @NonNull private final String mPackageName; + @NonNull private final String mResourceName; + private final int mResourceType; + + /** + * + * Creates a {@code ParcelableDevicePolicyResource} for the given {@code resourceId} and + * verifies that it exists in the package of the given {@code context}. + * + * @param context for the package containing the {@code resourceId} to use as the updated + * resource + * @param resourceId of the resource to use as an updated resource + * @param resourceType see {@link ResourceType} + * @throws IllegalArgumentException if the given {@code resourceId} doesn't exist in the + * {@link Context#getResources()} of the given {@code context} + */ + public ParcelableResource(@NonNull Context context, @AnyRes int resourceId, + @ResourceType int resourceType) throws IllegalArgumentException { + Objects.requireNonNull(context, "context must be provided"); + + verifyResourceExistsInCallingPackage(context, resourceId, resourceType); + + this.mResourceId = resourceId; + this.mPackageName = context.getResources().getResourcePackageName(resourceId); + this.mResourceName = context.getResources().getResourceName(resourceId); + this.mResourceType = resourceType; + } + + /** + * Creates a {@code ParcelableDevicePolicyResource} with the given params, this DOES NOT make + * any verifications on whether the given {@code resourceId} actually exists. + */ + private ParcelableResource( + @AnyRes int resourceId, @NonNull String packageName, @NonNull String resourceName, + @ResourceType int resourceType) { + this.mResourceId = resourceId; + this.mPackageName = requireNonNull(packageName); + this.mResourceName = requireNonNull(resourceName); + this.mResourceType = resourceType; + } + + private static void verifyResourceExistsInCallingPackage( + Context context, @AnyRes int resourceId, @ResourceType int resourceType) + throws IllegalArgumentException { + switch (resourceType) { + case RESOURCE_TYPE_DRAWABLE: + if (!hasDrawableInCallingPackage(context, resourceId)) { + throw new IllegalArgumentException(String.format( + "Drawable with id %d doesn't exist in the calling package %s", + resourceId, + context.getPackageName())); + } + break; + default: + throw new IllegalArgumentException( + "Unknown ParcelableDevicePolicyResourceType: " + resourceType); + } + } + + private static boolean hasDrawableInCallingPackage(Context context, @AnyRes int resourceId) { + try { + return context.getDrawable(resourceId) != null; + } catch (Resources.NotFoundException e) { + return false; + } + } + + public @AnyRes int getResourceId() { + return mResourceId; + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + @NonNull + public String getResourceName() { + return mResourceName; + } + + public int getResourceType() { + return mResourceType; + } + + /** + * Loads the drawable with id {@code mResourceId} from {@code mPackageName} using the provided + * {@code density} and {@link Resources.Theme} and {@link Resources#getConfiguration} of the + * provided {@code context}. + * + * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated + * drawable was not found or could not be loaded.</p> + */ + @Nullable + public Drawable getDrawable( + Context context, + int density, + @NonNull Callable<Drawable> defaultDrawableLoader) { + // TODO(b/203548565): properly handle edge case when the device manager role holder is + // unavailable because it's being updated. + try { + Resources resources = getAppResourcesWithCallersConfiguration(context); + verifyResourceName(resources); + return resources.getDrawableForDensity(mResourceId, density, context.getTheme()); + } catch (PackageManager.NameNotFoundException | RuntimeException e) { + Slog.e(TAG, "Unable to load drawable resource " + mResourceName, e); + return loadDefaultDrawable(defaultDrawableLoader); + } + } + + private Resources getAppResourcesWithCallersConfiguration(Context context) + throws PackageManager.NameNotFoundException { + PackageManager pm = context.getPackageManager(); + ApplicationInfo ai = pm.getApplicationInfo( + mPackageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.GET_SHARED_LIBRARY_FILES); + return pm.getResourcesForApplication(ai, context.getResources().getConfiguration()); + } + + private void verifyResourceName(Resources resources) throws IllegalStateException { + String name = resources.getResourceName(mResourceId); + if (!mResourceName.equals(name)) { + throw new IllegalStateException(String.format("Current resource name %s for resource id" + + " %d has changed from the previously stored resource name %s.", + name, mResourceId, mResourceName)); + } + } + + /** + * returns the {@link Drawable} loaded from calling + * {@code defaultDrawableLoader}. + */ + public static Drawable loadDefaultDrawable( + @NonNull Callable<Drawable> defaultDrawableLoader) { + try { + return defaultDrawableLoader.call(); + } catch (Exception e) { + throw new RuntimeException("Couldn't load default drawable", e); + } + } + + /** + * Writes the content of the current {@code ParcelableDevicePolicyResource} to the xml file + * specified by {@code xmlSerializer}. + */ + public void writeToXmlFile(TypedXmlSerializer xmlSerializer) throws IOException { + xmlSerializer.attributeInt(/* namespace= */ null, ATTR_RESOURCE_ID, mResourceId); + xmlSerializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName); + xmlSerializer.attribute(/* namespace= */ null, ATTR_RESOURCE_NAME, mResourceName); + xmlSerializer.attributeInt(/* namespace= */ null, ATTR_RESOURCE_TYPE, mResourceType); + } + + /** + * Creates a new {@code ParcelableDevicePolicyResource} using the content of + * {@code xmlPullParser}. + */ + public static ParcelableResource createFromXml(TypedXmlPullParser xmlPullParser) + throws XmlPullParserException, IOException { + int resourceId = xmlPullParser.getAttributeInt(/* namespace= */ null, ATTR_RESOURCE_ID); + String packageName = xmlPullParser.getAttributeValue( + /* namespace= */ null, ATTR_PACKAGE_NAME); + String resourceName = xmlPullParser.getAttributeValue( + /* namespace= */ null, ATTR_RESOURCE_NAME); + int resourceType = xmlPullParser.getAttributeInt( + /* namespace= */ null, ATTR_RESOURCE_TYPE); + + return new ParcelableResource( + resourceId, packageName, resourceName, resourceType); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ParcelableResource other = (ParcelableResource) o; + return mResourceId == other.mResourceId + && mPackageName.equals(other.mPackageName) + && mResourceName.equals(other.mResourceName) + && mResourceType == other.mResourceType; + } + + @Override + public int hashCode() { + return Objects.hash(mResourceId, mPackageName, mResourceName, mResourceType); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResourceId); + dest.writeString(mPackageName); + dest.writeString(mResourceName); + dest.writeInt(mResourceType); + } + + public static final @NonNull Creator<ParcelableResource> CREATOR = + new Creator<ParcelableResource>() { + @Override + public ParcelableResource createFromParcel(Parcel in) { + int resourceId = in.readInt(); + String packageName = in.readString(); + String resourceName = in.readString(); + int resourceType = in.readInt(); + + return new ParcelableResource( + resourceId, packageName, resourceName, resourceType); + } + + @Override + public ParcelableResource[] newArray(int size) { + return new ParcelableResource[size]; + } + }; +} diff --git a/core/java/android/app/communal/CommunalManager.java b/core/java/android/app/communal/CommunalManager.java deleted file mode 100644 index c7368ade2dcc..000000000000 --- a/core/java/android/app/communal/CommunalManager.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.communal; - -import android.Manifest; -import android.annotation.RequiresFeature; -import android.annotation.RequiresPermission; -import android.annotation.SystemApi; -import android.annotation.SystemService; -import android.annotation.TestApi; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.RemoteException; - -/** - * System private class for talking with the - * {@link com.android.server.communal.CommunalManagerService} that handles communal mode state. - * - * @hide - */ -@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) -@SystemService(Context.COMMUNAL_SERVICE) -@RequiresFeature(PackageManager.FEATURE_COMMUNAL_MODE) -public final class CommunalManager { - private final ICommunalManager mService; - - /** @hide */ - public CommunalManager(ICommunalManager service) { - mService = service; - } - - /** - * Updates whether or not the communal view is currently showing over the lockscreen. - * - * @param isShowing Whether communal view is showing. - * - * @hide - */ - @TestApi - @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE) - public void setCommunalViewShowing(boolean isShowing) { - try { - mService.setCommunalViewShowing(isShowing); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } -} diff --git a/core/java/android/app/communal/OWNERS b/core/java/android/app/communal/OWNERS deleted file mode 100644 index b02883da854a..000000000000 --- a/core/java/android/app/communal/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -brycelee@google.com -justinkoh@google.com -lusilva@google.com -xilei@google.com
\ No newline at end of file diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index 9985cc02965b..edabccf23c2c 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricSourceType; */ interface ITrustManager { void reportUnlockAttempt(boolean successful, int userId); + void reportUserRequestedUnlock(int userId); void reportUnlockLockout(int timeoutMs, int userId); void reportEnabledTrustAgentsChanged(int userId); void registerTrustListener(in ITrustListener trustListener); @@ -36,5 +37,5 @@ interface ITrustManager { boolean isDeviceSecure(int userId); boolean isTrustUsuallyManaged(int userId); void unlockedByBiometricForUser(int userId, in BiometricSourceType source); - void clearAllBiometricRecognized(in BiometricSourceType target); + void clearAllBiometricRecognized(in BiometricSourceType target, int unlockedUser); } diff --git a/core/java/android/app/trust/OWNERS b/core/java/android/app/trust/OWNERS new file mode 100644 index 000000000000..e2c6ce15b51e --- /dev/null +++ b/core/java/android/app/trust/OWNERS @@ -0,0 +1 @@ +include /core/java/android/service/trust/OWNERS diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 65b2775fd66b..937fcf0c0709 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -85,6 +85,19 @@ public class TrustManager { } /** + * Reports that the user {@code userId} is likely interested in unlocking the device. + * + * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + */ + public void reportUserRequestedUnlock(int userId) { + try { + mService.reportUserRequestedUnlock(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Reports that user {@param userId} has entered a temporary device lockout. * * This generally occurs when the user has unsuccessfully tried to unlock the device too many @@ -156,7 +169,7 @@ public class TrustManager { @Override public void onTrustError(CharSequence message) { - Message m = mHandler.obtainMessage(MSG_TRUST_ERROR); + Message m = mHandler.obtainMessage(MSG_TRUST_ERROR, trustListener); m.getData().putCharSequence(DATA_MESSAGE, message); m.sendToTarget(); } @@ -217,9 +230,9 @@ public class TrustManager { * Clears authentication by the specified biometric type for all users. */ @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) - public void clearAllBiometricRecognized(BiometricSourceType source) { + public void clearAllBiometricRecognized(BiometricSourceType source, int unlockedUser) { try { - mService.clearAllBiometricRecognized(source); + mService.clearAllBiometricRecognized(source, unlockedUser); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 630b8d26f52e..d7e197ee5ed1 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -1277,6 +1277,28 @@ public final class UsageStatsManager { } } + /** @hide */ + public static String standbyBucketToString(int standbyBucket) { + switch (standbyBucket) { + case STANDBY_BUCKET_EXEMPTED: + return "EXEMPTED"; + case STANDBY_BUCKET_ACTIVE: + return "ACTIVE"; + case STANDBY_BUCKET_WORKING_SET: + return "WORKING_SET"; + case STANDBY_BUCKET_FREQUENT: + return "FREQUENT"; + case STANDBY_BUCKET_RARE: + return "RARE"; + case STANDBY_BUCKET_RESTRICTED: + return "RESTRICTED"; + case STANDBY_BUCKET_NEVER: + return "NEVER"; + default: + return String.valueOf(standbyBucket); + } + } + /** * {@hide} * Temporarily allowlist the specified app for a short duration. This is to allow an app diff --git a/core/java/android/bluetooth/Attributable.java b/core/java/android/bluetooth/Attributable.java deleted file mode 100644 index d9acbe3eefb9..000000000000 --- a/core/java/android/bluetooth/Attributable.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.AttributionSource; - -import java.util.List; - -/** - * Marker interface for a class which can have an {@link AttributionSource} - * assigned to it; these are typically {@link android.os.Parcelable} classes - * which need to be updated after crossing Binder transaction boundaries. - * - * @hide - */ -public interface Attributable { - void setAttributionSource(@NonNull AttributionSource attributionSource); - - static @Nullable <T extends Attributable> T setAttributionSource( - @Nullable T attributable, - @NonNull AttributionSource attributionSource) { - if (attributable != null) { - attributable.setAttributionSource(attributionSource); - } - return attributable; - } - - static @Nullable <T extends Attributable> List<T> setAttributionSource( - @Nullable List<T> attributableList, - @NonNull AttributionSource attributionSource) { - if (attributableList != null) { - final int size = attributableList.size(); - for (int i = 0; i < size; i++) { - setAttributionSource(attributableList.get(i), attributionSource); - } - } - return attributableList; - } -} diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java deleted file mode 100644 index 8b9cec17a196..000000000000 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ /dev/null @@ -1,1149 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - - -/** - * This class provides the public APIs to control the Bluetooth A2DP - * profile. - * - * <p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothA2dp proxy object. - * - * <p> Android only supports one connected Bluetooth A2dp device at a time. - * Each method is protected with its appropriate permission. - */ -public final class BluetoothA2dp implements BluetoothProfile { - private static final String TAG = "BluetoothA2dp"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the A2DP - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the change in the Playing state of the A2DP - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PLAYING_STATE_CHANGED = - "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED = - "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the selection of a connected device as active. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage(trackingBug = 171933273) - public static final String ACTION_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED"; - - /** - * Intent used to broadcast the change in the Audio Codec state of the - * A2DP Source profile. - * - * <p>This intent will have 2 extras: - * <ul> - * <li> {@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently - * connected, otherwise it is not included.</li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage(trackingBug = 181103983) - public static final String ACTION_CODEC_CONFIG_CHANGED = - "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED"; - - /** - * A2DP sink device is streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. - */ - public static final int STATE_PLAYING = 10; - - /** - * A2DP sink device is NOT streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. - */ - public static final int STATE_NOT_PLAYING = 11; - - /** @hide */ - @IntDef(prefix = "OPTIONAL_CODECS_", value = { - OPTIONAL_CODECS_SUPPORT_UNKNOWN, - OPTIONAL_CODECS_NOT_SUPPORTED, - OPTIONAL_CODECS_SUPPORTED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface OptionalCodecsSupportStatus {} - - /** - * We don't have a stored preference for whether or not the given A2DP sink device supports - * optional codecs. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; - - /** - * The given A2DP sink device does not support optional codecs. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; - - /** - * The given A2DP sink device does support optional codecs. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_SUPPORTED = 1; - - /** @hide */ - @IntDef(prefix = "OPTIONAL_CODECS_PREF_", value = { - OPTIONAL_CODECS_PREF_UNKNOWN, - OPTIONAL_CODECS_PREF_DISABLED, - OPTIONAL_CODECS_PREF_ENABLED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface OptionalCodecsPreferenceStatus {} - - /** - * We don't have a stored preference for whether optional codecs should be enabled or - * disabled for the given A2DP device. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1; - - /** - * Optional codecs should be disabled for the given A2DP device. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; - - /** - * Optional codecs should be enabled for the given A2DP device. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; - - /** @hide */ - @IntDef(prefix = "DYNAMIC_BUFFER_SUPPORT_", value = { - DYNAMIC_BUFFER_SUPPORT_NONE, - DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD, - DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - /** - * Indicates the supported type of Dynamic Audio Buffer is not supported. - * - * @hide - */ - @SystemApi - public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0; - - /** - * Indicates the supported type of Dynamic Audio Buffer is A2DP offload. - * - * @hide - */ - @SystemApi - public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; - - /** - * Indicates the supported type of Dynamic Audio Buffer is A2DP software encoding. - * - * @hide - */ - @SystemApi - public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp", - IBluetoothA2dp.class.getName()) { - @Override - public IBluetoothA2dp getServiceInterface(IBinder service) { - return IBluetoothA2dp.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothA2dp proxy object for interacting with the local - * Bluetooth A2DP service. - */ - /* package */ BluetoothA2dp(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - @UnsupportedAppUsage - /*package*/ void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothA2dp getService() { - return mProfileConnector.getService(); - } - - @Override - public void finalize() { - // The empty finalize needs to be kept or the - // cts signature tests would fail. - } - - /** - * Initiate connection to a profile of the remote Bluetooth device. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connectWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnectWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothA2dp service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevicesWithAttribution(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothA2dp service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStatesWithAttribution(states, - mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @BtProfileState int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothA2dp service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionStateWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Select a connected device as active. - * - * The active device selection is per profile. An active device's - * purpose is profile-specific. For example, A2DP audio streaming - * is to the active A2DP Sink device. If a remote device is not - * connected, it cannot be selected as active. - * - * <p> This API returns false in scenarios like the profile on the - * device is not connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that the - * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted - * with the active device. - * - * @param device the remote Bluetooth device. Could be null to clear - * the active device and stop streaming audio to a Bluetooth device. - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage(trackingBug = 171933273) - public boolean setActiveDevice(@Nullable BluetoothDevice device) { - if (DBG) log("setActiveDevice(" + device + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && ((device == null) || isValidDevice(device))) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setActiveDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connected device that is active. - * - * @return the connected device that is active or null if no device - * is active - * @hide - */ - @UnsupportedAppUsage(trackingBug = 171933273) - @Nullable - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothDevice getActiveDevice() { - if (VDBG) log("getActiveDevice()"); - final IBluetoothA2dp service = getService(); - final BluetoothDevice defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothDevice> recv = - new SynchronousResultReceiver(); - service.getActiveDevice(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothA2dp service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Checks if Avrcp device supports the absolute volume feature. - * - * @return true if device supports absolute volume - * @hide - */ - @RequiresNoPermission - public boolean isAvrcpAbsoluteVolumeSupported() { - if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isAvrcpAbsoluteVolumeSupported(recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Tells remote device to set an absolute volume. Only if absolute volume is supported - * - * @param volume Absolute volume to be set on AVRCP side - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setAvrcpAbsoluteVolume(int volume) { - if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume"); - final IBluetoothA2dp service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - service.setAvrcpAbsoluteVolume(volume, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Check if A2DP profile is streaming music. - * - * @param device BluetoothDevice device - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isA2dpPlaying(BluetoothDevice device) { - if (DBG) log("isA2dpPlaying(" + device + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isA2dpPlaying(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * This function checks if the remote device is an AVCRP - * target and thus whether we should send volume keys - * changes or not. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean shouldSendVolumeKeys(BluetoothDevice device) { - if (isEnabled() && isValidDevice(device)) { - ParcelUuid[] uuids = device.getUuids(); - if (uuids == null) return false; - - for (ParcelUuid uuid : uuids) { - if (uuid.equals(BluetoothUuid.AVRCP_TARGET)) { - return true; - } - } - } - return false; - } - - /** - * Gets the current codec status (configuration and capability). - * - * @param device the remote Bluetooth device. - * @return the current codec status - * @hide - */ - @UnsupportedAppUsage(trackingBug = 181103983) - @Nullable - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { - if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); - verifyDeviceNotNull(device, "getCodecStatus"); - final IBluetoothA2dp service = getService(); - final BluetoothCodecStatus defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<BluetoothCodecStatus> recv = - new SynchronousResultReceiver(); - service.getCodecStatus(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sets the codec configuration preference. - * - * @param device the remote Bluetooth device. - * @param codecConfig the codec configuration preference - * @hide - */ - @UnsupportedAppUsage(trackingBug = 181103983) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setCodecConfigPreference(@NonNull BluetoothDevice device, - @NonNull BluetoothCodecConfig codecConfig) { - if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); - verifyDeviceNotNull(device, "setCodecConfigPreference"); - if (codecConfig == null) { - Log.e(TAG, "setCodecConfigPreference: Codec config can't be null"); - throw new IllegalArgumentException("codecConfig cannot be null"); - } - final IBluetoothA2dp service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - service.setCodecConfigPreference(device, codecConfig, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Enables the optional codecs. - * - * @param device the remote Bluetooth device. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void enableOptionalCodecs(@NonNull BluetoothDevice device) { - if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); - verifyDeviceNotNull(device, "enableOptionalCodecs"); - enableDisableOptionalCodecs(device, true); - } - - /** - * Disables the optional codecs. - * - * @param device the remote Bluetooth device. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void disableOptionalCodecs(@NonNull BluetoothDevice device) { - if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); - verifyDeviceNotNull(device, "disableOptionalCodecs"); - enableDisableOptionalCodecs(device, false); - } - - /** - * Enables or disables the optional codecs. - * - * @param device the remote Bluetooth device. - * @param enable if true, enable the optional codecs, other disable them - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) { - final IBluetoothA2dp service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - if (enable) { - service.enableOptionalCodecs(device, mAttributionSource); - } else { - service.disableOptionalCodecs(device, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Returns whether this device supports optional codecs. - * - * @param device The device to check - * @return one of OPTIONAL_CODECS_SUPPORT_UNKNOWN, OPTIONAL_CODECS_NOT_SUPPORTED, or - * OPTIONAL_CODECS_SUPPORTED. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @OptionalCodecsSupportStatus - public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) { - if (DBG) log("isOptionalCodecsSupported(" + device + ")"); - verifyDeviceNotNull(device, "isOptionalCodecsSupported"); - final IBluetoothA2dp service = getService(); - final int defaultValue = OPTIONAL_CODECS_SUPPORT_UNKNOWN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.supportsOptionalCodecs(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns whether this device should have optional codecs enabled. - * - * @param device The device in question. - * @return one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or - * OPTIONAL_CODECS_PREF_DISABLED. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @OptionalCodecsPreferenceStatus - public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) { - if (DBG) log("isOptionalCodecsEnabled(" + device + ")"); - verifyDeviceNotNull(device, "isOptionalCodecsEnabled"); - final IBluetoothA2dp service = getService(); - final int defaultValue = OPTIONAL_CODECS_PREF_UNKNOWN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getOptionalCodecsEnabled(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sets a persistent preference for whether a given device should have optional codecs enabled. - * - * @param device The device to set this preference for. - * @param value Whether the optional codecs should be enabled for this device. This should be - * one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or - * OPTIONAL_CODECS_PREF_DISABLED. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device, - @OptionalCodecsPreferenceStatus int value) { - if (DBG) log("setOptionalCodecsEnabled(" + device + ")"); - verifyDeviceNotNull(device, "setOptionalCodecsEnabled"); - if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN - && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED - && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { - Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value); - return; - } - final IBluetoothA2dp service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - service.setOptionalCodecsEnabled(device, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Get the supported type of the Dynamic Audio Buffer. - * <p>Possible return values are - * {@link #DYNAMIC_BUFFER_SUPPORT_NONE}, - * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD}, - * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}. - * - * @return supported type of Dynamic Audio Buffer feature - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @Type int getDynamicBufferSupport() { - if (VDBG) log("getDynamicBufferSupport()"); - final IBluetoothA2dp service = getService(); - final int defaultValue = DYNAMIC_BUFFER_SUPPORT_NONE; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getDynamicBufferSupport(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Return the record of {@link BufferConstraints} object that - * has the default/maximum/minimum audio buffer. This can be used to inform what the controller - * has support for the audio buffer. - * - * @return a record with {@link BufferConstraints} or null if report is unavailable - * or unsupported - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @Nullable BufferConstraints getBufferConstraints() { - if (VDBG) log("getBufferConstraints()"); - final IBluetoothA2dp service = getService(); - final BufferConstraints defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BufferConstraints> recv = - new SynchronousResultReceiver(); - service.getBufferConstraints(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set Dynamic Audio Buffer Size. - * - * @param codec audio codec - * @param value buffer millis - * @return true to indicate success, or false on immediate error - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setBufferLengthMillis(@BluetoothCodecConfig.SourceCodecType int codec, - int value) { - if (VDBG) log("setBufferLengthMillis(" + codec + ", " + value + ")"); - if (value < 0) { - Log.e(TAG, "Trying to set audio buffer length to a negative value: " + value); - return false; - } - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setBufferLengthMillis(codec, value, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Helper for converting a state to a string. - * - * For debug use only - strings are not internationalized. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public static String stateToString(int state) { - switch (state) { - case STATE_DISCONNECTED: - return "disconnected"; - case STATE_CONNECTING: - return "connecting"; - case STATE_CONNECTED: - return "connected"; - case STATE_DISCONNECTING: - return "disconnecting"; - case STATE_PLAYING: - return "playing"; - case STATE_NOT_PLAYING: - return "not playing"; - default: - return "<unknown state " + state + ">"; - } - } - - private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; - } - - private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { - if (device == null) { - Log.e(TAG, methodName + ": device param is null"); - throw new IllegalArgumentException("Device cannot be null"); - } - } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java deleted file mode 100755 index 59416818ceb3..000000000000 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Bluetooth A2DP Sink - * profile. - * - * <p>BluetoothA2dpSink is a proxy object for controlling the Bluetooth A2DP Sink - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothA2dpSink proxy object. - * - * @hide - */ -@SystemApi -public final class BluetoothA2dpSink implements BluetoothProfile { - private static final String TAG = "BluetoothA2dpSink"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the A2DP Sink - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * @hide - */ - @SystemApi - @SuppressLint("ActionValue") - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK, - "BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) { - @Override - public IBluetoothA2dpSink getServiceInterface(IBinder service) { - return IBluetoothA2dpSink.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothA2dp proxy object for interacting with the local - * Bluetooth A2DP service. - */ - /* package */ BluetoothA2dpSink(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /*package*/ void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothA2dpSink getService() { - return mProfileConnector.getService(); - } - - @Override - public void finalize() { - close(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> Currently, the system supports only 1 connection to the - * A2DP profile. The API will automatically disconnect connected - * devices before connecting. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothA2dpSink service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothA2dpSink service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getConnectionState(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the current audio configuration for the A2DP source device, - * or null if the device has no audio configuration - * - * @param device Remote bluetooth device. - * @return audio configuration for the device, or null - * - * {@see BluetoothAudioConfig} - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { - if (VDBG) log("getAudioConfig(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final BluetoothAudioConfig defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<BluetoothAudioConfig> recv = - new SynchronousResultReceiver(); - service.getAudioConfig(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothA2dpSink service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check if audio is playing on the bluetooth device (A2DP profile is streaming music). - * - * @param device BluetoothDevice device - * @return true if audio is playing (A2dp is streaming music), false otherwise - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean isAudioPlaying(@NonNull BluetoothDevice device) { - if (VDBG) log("isAudioPlaying(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isA2dpPlaying(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Helper for converting a state to a string. - * - * For debug use only - strings are not internationalized. - * - * @hide - */ - public static String stateToString(int state) { - switch (state) { - case STATE_DISCONNECTED: - return "disconnected"; - case STATE_CONNECTING: - return "connecting"; - case STATE_CONNECTED: - return "connected"; - case STATE_DISCONNECTING: - return "disconnecting"; - case BluetoothA2dp.STATE_PLAYING: - return "playing"; - case BluetoothA2dp.STATE_NOT_PLAYING: - return "not playing"; - default: - return "<unknown state " + state + ">"; - } - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java deleted file mode 100644 index c17a7b4b3dfd..000000000000 --- a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.ElapsedRealtimeLong; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Collections; -import java.util.List; - -/** - * Record of energy and activity information from controller and - * underlying bt stack state.Timestamp the record with system - * time. - * - * @hide - */ -@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) -public final class BluetoothActivityEnergyInfo implements Parcelable { - private final long mTimestamp; - private int mBluetoothStackState; - private long mControllerTxTimeMs; - private long mControllerRxTimeMs; - private long mControllerIdleTimeMs; - private long mControllerEnergyUsed; - private List<UidTraffic> mUidTraffic; - - /** @hide */ - @IntDef(prefix = { "BT_STACK_STATE_" }, value = { - BT_STACK_STATE_INVALID, - BT_STACK_STATE_STATE_ACTIVE, - BT_STACK_STATE_STATE_SCANNING, - BT_STACK_STATE_STATE_IDLE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BluetoothStackState {} - - public static final int BT_STACK_STATE_INVALID = 0; - public static final int BT_STACK_STATE_STATE_ACTIVE = 1; - public static final int BT_STACK_STATE_STATE_SCANNING = 2; - public static final int BT_STACK_STATE_STATE_IDLE = 3; - - /** @hide */ - public BluetoothActivityEnergyInfo(long timestamp, int stackState, - long txTime, long rxTime, long idleTime, long energyUsed) { - mTimestamp = timestamp; - mBluetoothStackState = stackState; - mControllerTxTimeMs = txTime; - mControllerRxTimeMs = rxTime; - mControllerIdleTimeMs = idleTime; - mControllerEnergyUsed = energyUsed; - } - - /** @hide */ - private BluetoothActivityEnergyInfo(Parcel in) { - mTimestamp = in.readLong(); - mBluetoothStackState = in.readInt(); - mControllerTxTimeMs = in.readLong(); - mControllerRxTimeMs = in.readLong(); - mControllerIdleTimeMs = in.readLong(); - mControllerEnergyUsed = in.readLong(); - mUidTraffic = in.createTypedArrayList(UidTraffic.CREATOR); - } - - /** @hide */ - @Override - public String toString() { - return "BluetoothActivityEnergyInfo{" - + " mTimestamp=" + mTimestamp - + " mBluetoothStackState=" + mBluetoothStackState - + " mControllerTxTimeMs=" + mControllerTxTimeMs - + " mControllerRxTimeMs=" + mControllerRxTimeMs - + " mControllerIdleTimeMs=" + mControllerIdleTimeMs - + " mControllerEnergyUsed=" + mControllerEnergyUsed - + " mUidTraffic=" + mUidTraffic - + " }"; - } - - public static final @NonNull Parcelable.Creator<BluetoothActivityEnergyInfo> CREATOR = - new Parcelable.Creator<BluetoothActivityEnergyInfo>() { - public BluetoothActivityEnergyInfo createFromParcel(Parcel in) { - return new BluetoothActivityEnergyInfo(in); - } - - public BluetoothActivityEnergyInfo[] newArray(int size) { - return new BluetoothActivityEnergyInfo[size]; - } - }; - - /** @hide */ - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeLong(mTimestamp); - out.writeInt(mBluetoothStackState); - out.writeLong(mControllerTxTimeMs); - out.writeLong(mControllerRxTimeMs); - out.writeLong(mControllerIdleTimeMs); - out.writeLong(mControllerEnergyUsed); - out.writeTypedList(mUidTraffic); - } - - /** @hide */ - @Override - public int describeContents() { - return 0; - } - - /** - * Get the Bluetooth stack state associated with the energy info. - * - * @return one of {@link #BluetoothStackState} states - */ - @BluetoothStackState - public int getBluetoothStackState() { - return mBluetoothStackState; - } - - /** - * @return tx time in ms - */ - public long getControllerTxTimeMillis() { - return mControllerTxTimeMs; - } - - /** - * @return rx time in ms - */ - public long getControllerRxTimeMillis() { - return mControllerRxTimeMs; - } - - /** - * @return idle time in ms - */ - public long getControllerIdleTimeMillis() { - return mControllerIdleTimeMs; - } - - /** - * Get the product of current (mA), voltage (V), and time (ms). - * - * @return energy used - */ - public long getControllerEnergyUsed() { - return mControllerEnergyUsed; - } - - /** - * @return timestamp (real time elapsed in milliseconds since boot) of record creation - */ - public @ElapsedRealtimeLong long getTimestampMillis() { - return mTimestamp; - } - - /** - * Get the {@link List} of each application {@link android.bluetooth.UidTraffic}. - * - * @return current {@link List} of {@link android.bluetooth.UidTraffic} - */ - public @NonNull List<UidTraffic> getUidTraffic() { - if (mUidTraffic == null) { - return Collections.emptyList(); - } - return mUidTraffic; - } - - /** @hide */ - public void setUidTraffic(List<UidTraffic> traffic) { - mUidTraffic = traffic; - } - - /** - * @return true if the record Tx time, Rx time, and Idle time are more than 0. - */ - public boolean isValid() { - return ((mControllerTxTimeMs >= 0) && (mControllerRxTimeMs >= 0) - && (mControllerIdleTimeMs >= 0)); - } -} diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java deleted file mode 100644 index 856a8e1f8199..000000000000 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ /dev/null @@ -1,4367 +0,0 @@ -/* - * Copyright 2009-2016 The Android Open Source Project - * Copyright 2015 Samsung LSI - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static java.util.Objects.requireNonNull; - -import android.annotation.CallbackExecutor; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.app.PropertyInvalidatedCache; -import android.bluetooth.BluetoothDevice.Transport; -import android.bluetooth.BluetoothProfile.ConnectionPolicy; -import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresBluetoothLocationPermission; -import android.bluetooth.annotations.RequiresBluetoothScanPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.bluetooth.le.BluetoothLeAdvertiser; -import android.bluetooth.le.BluetoothLeScanner; -import android.bluetooth.le.PeriodicAdvertisingManager; -import android.bluetooth.le.ScanCallback; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanRecord; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Binder; -import android.os.Build; -import android.os.IBinder; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ServiceManager; -import android.sysprop.BluetoothProperties; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.annotations.GuardedBy; - -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.WeakHashMap; -import java.util.concurrent.Executor; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * Represents the local device Bluetooth adapter. The {@link BluetoothAdapter} - * lets you perform fundamental Bluetooth tasks, such as initiate - * device discovery, query a list of bonded (paired) devices, - * instantiate a {@link BluetoothDevice} using a known MAC address, and create - * a {@link BluetoothServerSocket} to listen for connection requests from other - * devices, and start a scan for Bluetooth LE devices. - * - * <p>To get a {@link BluetoothAdapter} representing the local Bluetooth - * adapter, call the {@link BluetoothManager#getAdapter} function on {@link BluetoothManager}. - * On JELLY_BEAN_MR1 and below you will need to use the static {@link #getDefaultAdapter} - * method instead. - * </p><p> - * Fundamentally, this is your starting point for all - * Bluetooth actions. Once you have the local adapter, you can get a set of - * {@link BluetoothDevice} objects representing all paired devices with - * {@link #getBondedDevices()}; start device discovery with - * {@link #startDiscovery()}; or create a {@link BluetoothServerSocket} to - * listen for incoming RFComm connection requests with {@link - * #listenUsingRfcommWithServiceRecord(String, UUID)}; listen for incoming L2CAP Connection-oriented - * Channels (CoC) connection requests with {@link #listenUsingL2capChannel()}; or start a scan for - * Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}. - * </p> - * <p>This class is thread safe.</p> - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p> - * For more information about using Bluetooth, read the <a href= - * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer - * guide. - * </p> - * </div> - * - * {@see BluetoothDevice} - * {@see BluetoothServerSocket} - */ -public final class BluetoothAdapter { - private static final String TAG = "BluetoothAdapter"; - private static final String DESCRIPTOR = "android.bluetooth.BluetoothAdapter"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Default MAC address reported to a client that does not have the - * android.permission.LOCAL_MAC_ADDRESS permission. - * - * @hide - */ - public static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00"; - - /** - * Sentinel error value for this class. Guaranteed to not equal any other - * integer constant in this class. Provided as a convenience for functions - * that require a sentinel error value, for example: - * <p><code>Intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - * BluetoothAdapter.ERROR)</code> - */ - public static final int ERROR = Integer.MIN_VALUE; - - /** - * Broadcast Action: The state of the local Bluetooth adapter has been - * changed. - * <p>For example, Bluetooth has been turned on or off. - * <p>Always contains the extra fields {@link #EXTRA_STATE} and {@link - * #EXTRA_PREVIOUS_STATE} containing the new and old states - * respectively. - */ - @RequiresLegacyBluetoothPermission - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED"; - - /** - * Used as an int extra field in {@link #ACTION_STATE_CHANGED} - * intents to request the current power state. Possible values are: - * {@link #STATE_OFF}, - * {@link #STATE_TURNING_ON}, - * {@link #STATE_ON}, - * {@link #STATE_TURNING_OFF}, - */ - public static final String EXTRA_STATE = "android.bluetooth.adapter.extra.STATE"; - /** - * Used as an int extra field in {@link #ACTION_STATE_CHANGED} - * intents to request the previous power state. Possible values are: - * {@link #STATE_OFF}, - * {@link #STATE_TURNING_ON}, - * {@link #STATE_ON}, - * {@link #STATE_TURNING_OFF} - */ - public static final String EXTRA_PREVIOUS_STATE = - "android.bluetooth.adapter.extra.PREVIOUS_STATE"; - - /** @hide */ - @IntDef(prefix = { "STATE_" }, value = { - STATE_OFF, - STATE_TURNING_ON, - STATE_ON, - STATE_TURNING_OFF, - STATE_BLE_TURNING_ON, - STATE_BLE_ON, - STATE_BLE_TURNING_OFF - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AdapterState {} - - /** - * Indicates the local Bluetooth adapter is off. - */ - public static final int STATE_OFF = 10; - /** - * Indicates the local Bluetooth adapter is turning on. However local - * clients should wait for {@link #STATE_ON} before attempting to - * use the adapter. - */ - public static final int STATE_TURNING_ON = 11; - /** - * Indicates the local Bluetooth adapter is on, and ready for use. - */ - public static final int STATE_ON = 12; - /** - * Indicates the local Bluetooth adapter is turning off. Local clients - * should immediately attempt graceful disconnection of any remote links. - */ - public static final int STATE_TURNING_OFF = 13; - - /** - * Indicates the local Bluetooth adapter is turning Bluetooth LE mode on. - * - * @hide - */ - public static final int STATE_BLE_TURNING_ON = 14; - - /** - * Indicates the local Bluetooth adapter is in LE only mode. - * - * @hide - */ - public static final int STATE_BLE_ON = 15; - - /** - * Indicates the local Bluetooth adapter is turning off LE only mode. - * - * @hide - */ - public static final int STATE_BLE_TURNING_OFF = 16; - - /** - * UUID of the GATT Read Characteristics for LE_PSM value. - * - * @hide - */ - public static final UUID LE_PSM_CHARACTERISTIC_UUID = - UUID.fromString("2d410339-82b6-42aa-b34e-e2e01df8cc1a"); - - /** - * Human-readable string helper for AdapterState - * - * @hide - */ - public static String nameForState(@AdapterState int state) { - switch (state) { - case STATE_OFF: - return "OFF"; - case STATE_TURNING_ON: - return "TURNING_ON"; - case STATE_ON: - return "ON"; - case STATE_TURNING_OFF: - return "TURNING_OFF"; - case STATE_BLE_TURNING_ON: - return "BLE_TURNING_ON"; - case STATE_BLE_ON: - return "BLE_ON"; - case STATE_BLE_TURNING_OFF: - return "BLE_TURNING_OFF"; - default: - return "?!?!? (" + state + ")"; - } - } - - /** - * Activity Action: Show a system activity that requests discoverable mode. - * This activity will also request the user to turn on Bluetooth if it - * is not currently enabled. - * <p>Discoverable mode is equivalent to {@link - * #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. It allows remote devices to see - * this Bluetooth adapter when they perform a discovery. - * <p>For privacy, Android is not discoverable by default. - * <p>The sender of this Intent can optionally use extra field {@link - * #EXTRA_DISCOVERABLE_DURATION} to request the duration of - * discoverability. Currently the default duration is 120 seconds, and - * maximum duration is capped at 300 seconds for each request. - * <p>Notification of the result of this activity is posted using the - * {@link android.app.Activity#onActivityResult} callback. The - * <code>resultCode</code> - * will be the duration (in seconds) of discoverability or - * {@link android.app.Activity#RESULT_CANCELED} if the user rejected - * discoverability or an error has occurred. - * <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED} - * for global notification whenever the scan mode changes. For example, an - * application can be notified when the device has ended discoverability. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String - ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"; - - /** - * Used as an optional int extra field in {@link - * #ACTION_REQUEST_DISCOVERABLE} intents to request a specific duration - * for discoverability in seconds. The current default is 120 seconds, and - * requests over 300 seconds will be capped. These values could change. - */ - public static final String EXTRA_DISCOVERABLE_DURATION = - "android.bluetooth.adapter.extra.DISCOVERABLE_DURATION"; - - /** - * Activity Action: Show a system activity that allows the user to turn on - * Bluetooth. - * <p>This system activity will return once Bluetooth has completed turning - * on, or the user has decided not to turn Bluetooth on. - * <p>Notification of the result of this activity is posted using the - * {@link android.app.Activity#onActivityResult} callback. The - * <code>resultCode</code> - * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been - * turned on or {@link android.app.Activity#RESULT_CANCELED} if the user - * has rejected the request or an error has occurred. - * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED} - * for global notification whenever Bluetooth is turned on or off. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String - ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE"; - - /** - * Activity Action: Show a system activity that allows the user to turn off - * Bluetooth. This is used only if permission review is enabled which is for - * apps targeting API less than 23 require a permission review before any of - * the app's components can run. - * <p>This system activity will return once Bluetooth has completed turning - * off, or the user has decided not to turn Bluetooth off. - * <p>Notification of the result of this activity is posted using the - * {@link android.app.Activity#onActivityResult} callback. The - * <code>resultCode</code> - * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been - * turned off or {@link android.app.Activity#RESULT_CANCELED} if the user - * has rejected the request or an error has occurred. - * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED} - * for global notification whenever Bluetooth is turned on or off. - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String - ACTION_REQUEST_DISABLE = "android.bluetooth.adapter.action.REQUEST_DISABLE"; - - /** - * Activity Action: Show a system activity that allows user to enable BLE scans even when - * Bluetooth is turned off.<p> - * - * Notification of result of this activity is posted using - * {@link android.app.Activity#onActivityResult}. The <code>resultCode</code> will be - * {@link android.app.Activity#RESULT_OK} if BLE scan always available setting is turned on or - * {@link android.app.Activity#RESULT_CANCELED} if the user has rejected the request or an - * error occurred. - * - * @hide - */ - @SystemApi - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = - "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; - - /** - * Broadcast Action: Indicates the Bluetooth scan mode of the local Adapter - * has changed. - * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link - * #EXTRA_PREVIOUS_SCAN_MODE} containing the new and old scan modes - * respectively. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED"; - - /** - * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED} - * intents to request the current scan mode. Possible values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, - */ - public static final String EXTRA_SCAN_MODE = "android.bluetooth.adapter.extra.SCAN_MODE"; - /** - * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED} - * intents to request the previous scan mode. Possible values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, - */ - public static final String EXTRA_PREVIOUS_SCAN_MODE = - "android.bluetooth.adapter.extra.PREVIOUS_SCAN_MODE"; - - /** @hide */ - @IntDef(prefix = { "SCAN_" }, value = { - SCAN_MODE_NONE, - SCAN_MODE_CONNECTABLE, - SCAN_MODE_CONNECTABLE_DISCOVERABLE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ScanMode {} - - /** @hide */ - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ScanModeStatusCode {} - - /** - * Indicates that both inquiry scan and page scan are disabled on the local - * Bluetooth adapter. Therefore this device is neither discoverable - * nor connectable from remote Bluetooth devices. - */ - public static final int SCAN_MODE_NONE = 20; - /** - * Indicates that inquiry scan is disabled, but page scan is enabled on the - * local Bluetooth adapter. Therefore this device is not discoverable from - * remote Bluetooth devices, but is connectable from remote devices that - * have previously discovered this device. - */ - public static final int SCAN_MODE_CONNECTABLE = 21; - /** - * Indicates that both inquiry scan and page scan are enabled on the local - * Bluetooth adapter. Therefore this device is both discoverable and - * connectable from remote Bluetooth devices. - */ - public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23; - - /** - * Device only has a display. - * - * @hide - */ - public static final int IO_CAPABILITY_OUT = 0; - - /** - * Device has a display and the ability to input Yes/No. - * - * @hide - */ - public static final int IO_CAPABILITY_IO = 1; - - /** - * Device only has a keyboard for entry but no display. - * - * @hide - */ - public static final int IO_CAPABILITY_IN = 2; - - /** - * Device has no Input or Output capability. - * - * @hide - */ - public static final int IO_CAPABILITY_NONE = 3; - - /** - * Device has a display and a full keyboard. - * - * @hide - */ - public static final int IO_CAPABILITY_KBDISP = 4; - - /** - * Maximum range value for Input/Output capabilities. - * - * <p>This should be updated when adding a new Input/Output capability. Other code - * like validation depends on this being accurate. - * - * @hide - */ - public static final int IO_CAPABILITY_MAX = 5; - - /** - * The Input/Output capability of the device is unknown. - * - * @hide - */ - public static final int IO_CAPABILITY_UNKNOWN = 255; - - /** @hide */ - @IntDef({IO_CAPABILITY_OUT, IO_CAPABILITY_IO, IO_CAPABILITY_IN, IO_CAPABILITY_NONE, - IO_CAPABILITY_KBDISP}) - @Retention(RetentionPolicy.SOURCE) - public @interface IoCapability {} - - /** @hide */ - @IntDef(prefix = "ACTIVE_DEVICE_", value = {ACTIVE_DEVICE_AUDIO, - ACTIVE_DEVICE_PHONE_CALL, ACTIVE_DEVICE_ALL}) - @Retention(RetentionPolicy.SOURCE) - public @interface ActiveDeviceUse {} - - /** - * Use the specified device for audio (a2dp and hearing aid profile) - * - * @hide - */ - @SystemApi - public static final int ACTIVE_DEVICE_AUDIO = 0; - - /** - * Use the specified device for phone calls (headset profile and hearing - * aid profile) - * - * @hide - */ - @SystemApi - public static final int ACTIVE_DEVICE_PHONE_CALL = 1; - - /** - * Use the specified device for a2dp, hearing aid profile, and headset profile - * - * @hide - */ - @SystemApi - public static final int ACTIVE_DEVICE_ALL = 2; - - /** @hide */ - @IntDef({BluetoothProfile.HEADSET, BluetoothProfile.A2DP, - BluetoothProfile.HEARING_AID}) - @Retention(RetentionPolicy.SOURCE) - public @interface ActiveDeviceProfile {} - - /** - * Broadcast Action: The local Bluetooth adapter has started the remote - * device discovery process. - * <p>This usually involves an inquiry scan of about 12 seconds, followed - * by a page scan of each new device to retrieve its Bluetooth name. - * <p>Register for {@link BluetoothDevice#ACTION_FOUND} to be notified as - * remote Bluetooth devices are found. - * <p>Device discovery is a heavyweight procedure. New connections to - * remote Bluetooth devices should not be attempted while discovery is in - * progress, and existing connections will experience limited bandwidth - * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing - * discovery. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED"; - /** - * Broadcast Action: The local Bluetooth adapter has finished the device - * discovery process. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED"; - - /** - * Broadcast Action: The local Bluetooth adapter has changed its friendly - * Bluetooth name. - * <p>This name is visible to remote Bluetooth devices. - * <p>Always contains the extra field {@link #EXTRA_LOCAL_NAME} containing - * the name. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED"; - /** - * Used as a String extra field in {@link #ACTION_LOCAL_NAME_CHANGED} - * intents to request the local Bluetooth name. - */ - public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME"; - - /** - * Intent used to broadcast the change in connection state of the local - * Bluetooth adapter to a profile of the remote device. When the adapter is - * not connected to any profiles of any remote devices and it attempts a - * connection to a profile this intent will be sent. Once connected, this intent - * will not be sent for any more connection attempts to any profiles of any - * remote device. When the adapter disconnects from the last profile its - * connected to of any remote device, this intent will be sent. - * - * <p> This intent is useful for applications that are only concerned about - * whether the local adapter is connected to any profile of any device and - * are not really concerned about which profile. For example, an application - * which displays an icon to display whether Bluetooth is connected or not - * can use this intent. - * - * <p>This intent will have 3 extras: - * {@link #EXTRA_CONNECTION_STATE} - The current connection state. - * {@link #EXTRA_PREVIOUS_CONNECTION_STATE}- The previous connection state. - * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. - * - * {@link #EXTRA_CONNECTION_STATE} or {@link #EXTRA_PREVIOUS_CONNECTION_STATE} - * can be any of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED"; - - /** - * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED} - * - * This extra represents the current connection state. - */ - public static final String EXTRA_CONNECTION_STATE = - "android.bluetooth.adapter.extra.CONNECTION_STATE"; - - /** - * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED} - * - * This extra represents the previous connection state. - */ - public static final String EXTRA_PREVIOUS_CONNECTION_STATE = - "android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE"; - - /** - * Broadcast Action: The Bluetooth adapter state has changed in LE only mode. - * - * @hide - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @SystemApi public static final String ACTION_BLE_STATE_CHANGED = - "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; - - /** - * Intent used to broadcast the change in the Bluetooth address - * of the local Bluetooth adapter. - * <p>Always contains the extra field {@link - * #EXTRA_BLUETOOTH_ADDRESS} containing the Bluetooth address. - * - * Note: only system level processes are allowed to send this - * defined broadcast. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BLUETOOTH_ADDRESS_CHANGED = - "android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED"; - - /** - * Used as a String extra field in {@link - * #ACTION_BLUETOOTH_ADDRESS_CHANGED} intent to store the local - * Bluetooth address. - * - * @hide - */ - public static final String EXTRA_BLUETOOTH_ADDRESS = - "android.bluetooth.adapter.extra.BLUETOOTH_ADDRESS"; - - /** - * Broadcast Action: The notifys Bluetooth ACL connected event. This will be - * by BLE Always on enabled application to know the ACL_CONNECTED event - * when Bluetooth state in STATE_BLE_ON. This denotes GATT connection - * as Bluetooth LE is the only feature available in STATE_BLE_ON - * - * This is counterpart of {@link BluetoothDevice#ACTION_ACL_CONNECTED} which - * works in Bluetooth state STATE_ON - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BLE_ACL_CONNECTED = - "android.bluetooth.adapter.action.BLE_ACL_CONNECTED"; - - /** - * Broadcast Action: The notifys Bluetooth ACL connected event. This will be - * by BLE Always on enabled application to know the ACL_DISCONNECTED event - * when Bluetooth state in STATE_BLE_ON. This denotes GATT disconnection as Bluetooth - * LE is the only feature available in STATE_BLE_ON - * - * This is counterpart of {@link BluetoothDevice#ACTION_ACL_DISCONNECTED} which - * works in Bluetooth state STATE_ON - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BLE_ACL_DISCONNECTED = - "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED"; - - /** The profile is in disconnected state */ - public static final int STATE_DISCONNECTED = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED; - /** The profile is in connecting state */ - public static final int STATE_CONNECTING = BluetoothProtoEnums.CONNECTION_STATE_CONNECTING; - /** The profile is in connected state */ - public static final int STATE_CONNECTED = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED; - /** The profile is in disconnecting state */ - public static final int STATE_DISCONNECTING = - BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING; - - /** @hide */ - public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; - private final IBinder mToken; - - - /** - * When creating a ServerSocket using listenUsingRfcommOn() or - * listenUsingL2capOn() use SOCKET_CHANNEL_AUTO_STATIC to create - * a ServerSocket that auto assigns a channel number to the first - * bluetooth socket. - * The channel number assigned to this first Bluetooth Socket will - * be stored in the ServerSocket, and reused for subsequent Bluetooth - * sockets. - * - * @hide - */ - public static final int SOCKET_CHANNEL_AUTO_STATIC_NO_SDP = -2; - - - private static final int ADDRESS_LENGTH = 17; - - /** - * Lazily initialized singleton. Guaranteed final after first object - * constructed. - */ - private static BluetoothAdapter sAdapter; - - private BluetoothLeScanner mBluetoothLeScanner; - private BluetoothLeAdvertiser mBluetoothLeAdvertiser; - private PeriodicAdvertisingManager mPeriodicAdvertisingManager; - - private final IBluetoothManager mManagerService; - private final AttributionSource mAttributionSource; - - // Yeah, keeping both mService and sService isn't pretty, but it's too late - // in the current release for a major refactoring, so we leave them both - // intact until this can be cleaned up in a future release - - @UnsupportedAppUsage - @GuardedBy("mServiceLock") - private IBluetooth mService; - private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock(); - - @GuardedBy("sServiceLock") - private static boolean sServiceRegistered; - @GuardedBy("sServiceLock") - private static IBluetooth sService; - private static final Object sServiceLock = new Object(); - - private final Object mLock = new Object(); - private final Map<LeScanCallback, ScanCallback> mLeScanClients; - private final Map<BluetoothDevice, List<Pair<OnMetadataChangedListener, Executor>>> - mMetadataListeners = new HashMap<>(); - private final Map<BluetoothConnectionCallback, Executor> - mBluetoothConnectionCallbackExecutorMap = new HashMap<>(); - - /** - * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener - * implementation. - */ - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothMetadataListener mBluetoothMetadataListener = - new IBluetoothMetadataListener.Stub() { - @Override - public void onMetadataChanged(BluetoothDevice device, int key, byte[] value) { - Attributable.setAttributionSource(device, mAttributionSource); - synchronized (mMetadataListeners) { - if (mMetadataListeners.containsKey(device)) { - List<Pair<OnMetadataChangedListener, Executor>> list = - mMetadataListeners.get(device); - for (Pair<OnMetadataChangedListener, Executor> pair : list) { - OnMetadataChangedListener listener = pair.first; - Executor executor = pair.second; - executor.execute(() -> { - listener.onMetadataChanged(device, key, value); - }); - } - } - } - return; - } - }; - - /** - * Get a handle to the default local Bluetooth adapter. - * <p> - * Currently Android only supports one Bluetooth adapter, but the API could - * be extended to support more. This will always return the default adapter. - * </p> - * - * @return the default local adapter, or null if Bluetooth is not supported - * on this hardware platform - * @deprecated this method will continue to work, but developers are - * strongly encouraged to migrate to using - * {@link BluetoothManager#getAdapter()}, since that approach - * enables support for {@link Context#createAttributionContext}. - */ - @Deprecated - @RequiresNoPermission - public static synchronized BluetoothAdapter getDefaultAdapter() { - if (sAdapter == null) { - sAdapter = createAdapter(AttributionSource.myAttributionSource()); - } - return sAdapter; - } - - /** {@hide} */ - public static BluetoothAdapter createAdapter(AttributionSource attributionSource) { - IBinder binder = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE); - if (binder != null) { - return new BluetoothAdapter(IBluetoothManager.Stub.asInterface(binder), - attributionSource); - } else { - Log.e(TAG, "Bluetooth binder is null"); - return null; - } - } - - /** - * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance. - */ - BluetoothAdapter(IBluetoothManager managerService, AttributionSource attributionSource) { - mManagerService = Objects.requireNonNull(managerService); - mAttributionSource = Objects.requireNonNull(attributionSource); - synchronized (mServiceLock.writeLock()) { - mService = getBluetoothService(mManagerCallback); - } - mLeScanClients = new HashMap<LeScanCallback, ScanCallback>(); - mToken = new Binder(DESCRIPTOR); - } - - /** - * Get a {@link BluetoothDevice} object for the given Bluetooth hardware - * address. - * <p>Valid Bluetooth hardware addresses must be upper case, in a format - * such as "00:11:22:33:AA:BB". The helper {@link #checkBluetoothAddress} is - * available to validate a Bluetooth address. - * <p>A {@link BluetoothDevice} will always be returned for a valid - * hardware address, even if this adapter has never seen that device. - * - * @param address valid Bluetooth MAC address - * @throws IllegalArgumentException if address is invalid - */ - @RequiresNoPermission - public BluetoothDevice getRemoteDevice(String address) { - final BluetoothDevice res = new BluetoothDevice(address); - res.setAttributionSource(mAttributionSource); - return res; - } - - /** - * Get a {@link BluetoothDevice} object for the given Bluetooth hardware - * address. - * <p>Valid Bluetooth hardware addresses must be 6 bytes. This method - * expects the address in network byte order (MSB first). - * <p>A {@link BluetoothDevice} will always be returned for a valid - * hardware address, even if this adapter has never seen that device. - * - * @param address Bluetooth MAC address (6 bytes) - * @throws IllegalArgumentException if address is invalid - */ - @RequiresNoPermission - public BluetoothDevice getRemoteDevice(byte[] address) { - if (address == null || address.length != 6) { - throw new IllegalArgumentException("Bluetooth address must have 6 bytes"); - } - final BluetoothDevice res = new BluetoothDevice( - String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], - address[2], address[3], address[4], address[5])); - res.setAttributionSource(mAttributionSource); - return res; - } - - /** - * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations. - * Will return null if Bluetooth is turned off or if Bluetooth LE Advertising is not - * supported on this device. - * <p> - * Use {@link #isMultipleAdvertisementSupported()} to check whether LE Advertising is supported - * on this device before calling this method. - */ - @RequiresNoPermission - public BluetoothLeAdvertiser getBluetoothLeAdvertiser() { - if (!getLeAccess()) { - return null; - } - synchronized (mLock) { - if (mBluetoothLeAdvertiser == null) { - mBluetoothLeAdvertiser = new BluetoothLeAdvertiser(this); - } - return mBluetoothLeAdvertiser; - } - } - - /** - * Returns a {@link PeriodicAdvertisingManager} object for Bluetooth LE Periodic Advertising - * operations. Will return null if Bluetooth is turned off or if Bluetooth LE Periodic - * Advertising is not supported on this device. - * <p> - * Use {@link #isLePeriodicAdvertisingSupported()} to check whether LE Periodic Advertising is - * supported on this device before calling this method. - * - * @hide - */ - @RequiresNoPermission - public PeriodicAdvertisingManager getPeriodicAdvertisingManager() { - if (!getLeAccess()) { - return null; - } - - if (!isLePeriodicAdvertisingSupported()) { - return null; - } - - synchronized (mLock) { - if (mPeriodicAdvertisingManager == null) { - mPeriodicAdvertisingManager = new PeriodicAdvertisingManager(this); - } - return mPeriodicAdvertisingManager; - } - } - - /** - * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations. - */ - @RequiresNoPermission - public BluetoothLeScanner getBluetoothLeScanner() { - if (!getLeAccess()) { - return null; - } - synchronized (mLock) { - if (mBluetoothLeScanner == null) { - mBluetoothLeScanner = new BluetoothLeScanner(this); - } - return mBluetoothLeScanner; - } - } - - /** - * Return true if Bluetooth is currently enabled and ready for use. - * <p>Equivalent to: - * <code>getBluetoothState() == STATE_ON</code> - * - * @return true if the local adapter is turned on - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isEnabled() { - return getState() == BluetoothAdapter.STATE_ON; - } - - /** - * Return true if Bluetooth LE(Always BLE On feature) is currently - * enabled and ready for use - * <p>This returns true if current state is either STATE_ON or STATE_BLE_ON - * - * @return true if the local Bluetooth LE adapter is turned on - * @hide - */ - @SystemApi - @RequiresNoPermission - public boolean isLeEnabled() { - final int state = getLeState(); - if (DBG) { - Log.d(TAG, "isLeEnabled(): " + BluetoothAdapter.nameForState(state)); - } - return (state == BluetoothAdapter.STATE_ON - || state == BluetoothAdapter.STATE_BLE_ON - || state == BluetoothAdapter.STATE_TURNING_ON - || state == BluetoothAdapter.STATE_TURNING_OFF); - } - - /** - * Turns off Bluetooth LE which was earlier turned on by calling enableBLE(). - * - * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition - * to STATE_OFF and completely shut-down Bluetooth - * - * <p> If the Adapter state is STATE_ON, This would unregister the existance of - * special Bluetooth LE application and hence the further turning off of Bluetooth - * from UI would ensure the complete turn-off of Bluetooth rather than staying back - * BLE only state - * - * <p>This is an asynchronous call: it will return immediately, and - * clients should listen for {@link #ACTION_BLE_STATE_CHANGED} - * to be notified of subsequent adapter state changes If this call returns - * true, then the adapter state will immediately transition from {@link - * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time - * later transition to either {@link #STATE_BLE_ON} or {@link - * #STATE_OFF} based on the existance of the further Always BLE ON enabled applications - * If this call returns false then there was an - * immediate problem that will prevent the QAdapter from being turned off - - * such as the QAadapter already being turned off. - * - * @return true to indicate success, or false on immediate error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disableBLE() { - if (!isBleScanAlwaysAvailable()) { - return false; - } - try { - return mManagerService.disableBle(mAttributionSource, mToken); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Applications who want to only use Bluetooth Low Energy (BLE) can call enableBLE. - * - * enableBLE registers the existence of an app using only LE functions. - * - * enableBLE may enable Bluetooth to an LE only mode so that an app can use - * LE related features (BluetoothGatt or BluetoothGattServer classes) - * - * If the user disables Bluetooth while an app is registered to use LE only features, - * Bluetooth will remain on in LE only mode for the app. - * - * When Bluetooth is in LE only mode, it is not shown as ON to the UI. - * - * <p>This is an asynchronous call: it returns immediately, and - * clients should listen for {@link #ACTION_BLE_STATE_CHANGED} - * to be notified of adapter state changes. - * - * If this call returns * true, then the adapter state is either in a mode where - * LE is available, or will transition from {@link #STATE_OFF} to {@link #STATE_BLE_TURNING_ON}, - * and some time later transition to either {@link #STATE_OFF} or {@link #STATE_BLE_ON}. - * - * If this call returns false then there was an immediate problem that prevents the - * adapter from being turned on - such as Airplane mode. - * - * {@link #ACTION_BLE_STATE_CHANGED} returns the Bluetooth Adapter's various - * states, It includes all the classic Bluetooth Adapter states along with - * internal BLE only states - * - * @return true to indicate Bluetooth LE will be available, or false on immediate error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean enableBLE() { - if (!isBleScanAlwaysAvailable()) { - return false; - } - try { - return mManagerService.enableBle(mAttributionSource, mToken); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - - return false; - } - - private static final String BLUETOOTH_GET_STATE_CACHE_PROPERTY = "cache_key.bluetooth.get_state"; - - private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache = - new PropertyInvalidatedCache<Void, Integer>( - 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Integer recompute(Void query) { - try { - return mService.getState(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - }; - - /** @hide */ - @RequiresNoPermission - public void disableBluetoothGetStateCache() { - mBluetoothGetStateCache.disableLocal(); - } - - /** @hide */ - public static void invalidateBluetoothGetStateCache() { - PropertyInvalidatedCache.invalidateCache(BLUETOOTH_GET_STATE_CACHE_PROPERTY); - } - - /** - * Fetch the current bluetooth state. If the service is down, return - * OFF. - */ - @AdapterState - private int getStateInternal() { - int state = BluetoothAdapter.STATE_OFF; - try { - mServiceLock.readLock().lock(); - if (mService != null) { - state = mBluetoothGetStateCache.query(null); - } - } catch (RuntimeException e) { - if (e.getCause() instanceof RemoteException) { - Log.e(TAG, "", e.getCause()); - } else { - throw e; - } - } finally { - mServiceLock.readLock().unlock(); - } - return state; - } - - /** - * Get the current state of the local Bluetooth adapter. - * <p>Possible return values are - * {@link #STATE_OFF}, - * {@link #STATE_TURNING_ON}, - * {@link #STATE_ON}, - * {@link #STATE_TURNING_OFF}. - * - * @return current state of Bluetooth adapter - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - @AdapterState - public int getState() { - int state = getStateInternal(); - - // Consider all internal states as OFF - if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON - || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) { - if (VDBG) { - Log.d(TAG, "Consider " + BluetoothAdapter.nameForState(state) + " state as OFF"); - } - state = BluetoothAdapter.STATE_OFF; - } - if (VDBG) { - Log.d(TAG, "" + hashCode() + ": getState(). Returning " + BluetoothAdapter.nameForState( - state)); - } - return state; - } - - /** - * Get the current state of the local Bluetooth adapter - * <p>This returns current internal state of Adapter including LE ON/OFF - * - * <p>Possible return values are - * {@link #STATE_OFF}, - * {@link #STATE_BLE_TURNING_ON}, - * {@link #STATE_BLE_ON}, - * {@link #STATE_TURNING_ON}, - * {@link #STATE_ON}, - * {@link #STATE_TURNING_OFF}, - * {@link #STATE_BLE_TURNING_OFF}. - * - * @return current state of Bluetooth adapter - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - @AdapterState - @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine " - + "whether you can use BLE & BT classic.") - public int getLeState() { - int state = getStateInternal(); - - if (VDBG) { - Log.d(TAG, "getLeState() returning " + BluetoothAdapter.nameForState(state)); - } - return state; - } - - boolean getLeAccess() { - if (getLeState() == STATE_ON) { - return true; - } else if (getLeState() == STATE_BLE_ON) { - return true; // TODO: FILTER SYSTEM APPS HERE <-- - } - - return false; - } - - /** - * Turn on the local Bluetooth adapter—do not use without explicit - * user action to turn on Bluetooth. - * <p>This powers on the underlying Bluetooth hardware, and starts all - * Bluetooth system services. - * <p class="caution"><strong>Bluetooth should never be enabled without - * direct user consent</strong>. If you want to turn on Bluetooth in order - * to create a wireless connection, you should use the {@link - * #ACTION_REQUEST_ENABLE} Intent, which will raise a dialog that requests - * user permission to turn on Bluetooth. The {@link #enable()} method is - * provided only for applications that include a user interface for changing - * system settings, such as a "power manager" app.</p> - * <p>This is an asynchronous call: it will return immediately, and - * clients should listen for {@link #ACTION_STATE_CHANGED} - * to be notified of subsequent adapter state changes. If this call returns - * true, then the adapter state will immediately transition from {@link - * #STATE_OFF} to {@link #STATE_TURNING_ON}, and some time - * later transition to either {@link #STATE_OFF} or {@link - * #STATE_ON}. If this call returns false then there was an - * immediate problem that will prevent the adapter from being turned on - - * such as Airplane mode, or the adapter is already turned on. - * - * @return true to indicate adapter startup has begun, or false on immediate error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean enable() { - if (isEnabled()) { - if (DBG) { - Log.d(TAG, "enable(): BT already enabled!"); - } - return true; - } - try { - return mManagerService.enable(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Turn off the local Bluetooth adapter—do not use without explicit - * user action to turn off Bluetooth. - * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth - * system services, and powers down the underlying Bluetooth hardware. - * <p class="caution"><strong>Bluetooth should never be disabled without - * direct user consent</strong>. The {@link #disable()} method is - * provided only for applications that include a user interface for changing - * system settings, such as a "power manager" app.</p> - * <p>This is an asynchronous call: it will return immediately, and - * clients should listen for {@link #ACTION_STATE_CHANGED} - * to be notified of subsequent adapter state changes. If this call returns - * true, then the adapter state will immediately transition from {@link - * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time - * later transition to either {@link #STATE_OFF} or {@link - * #STATE_ON}. If this call returns false then there was an - * immediate problem that will prevent the adapter from being turned off - - * such as the adapter already being turned off. - * - * @return true to indicate adapter shutdown has begun, or false on immediate error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disable() { - try { - return mManagerService.disable(mAttributionSource, true); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Turn off the local Bluetooth adapter and don't persist the setting. - * - * @param persist Indicate whether the off state should be persisted following the next reboot - * @return true to indicate adapter shutdown has begun, or false on immediate error - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disable(boolean persist) { - - try { - return mManagerService.disable(mAttributionSource, persist); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Returns the hardware address of the local Bluetooth adapter. - * <p>For example, "00:11:22:AA:BB:CC". - * - * @return Bluetooth hardware address as string - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.LOCAL_MAC_ADDRESS, - }) - public String getAddress() { - try { - return mManagerService.getAddress(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Get the friendly Bluetooth name of the local Bluetooth adapter. - * <p>This name is visible to remote Bluetooth devices. - * - * @return the Bluetooth name, or null on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public String getName() { - try { - return mManagerService.getName(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** {@hide} */ - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public int getNameLengthForAdvertise() { - try { - return mService.getNameLengthForAdvertise(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return -1; - } - - /** - * Factory reset bluetooth settings. - * - * @return true to indicate that the config file was successfully cleared - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean factoryReset() { - try { - mServiceLock.readLock().lock(); - if (mService != null && mService.factoryReset(mAttributionSource) - && mManagerService != null - && mManagerService.onFactoryReset(mAttributionSource)) { - return true; - } - Log.e(TAG, "factoryReset(): Setting persist.bluetooth.factoryreset to retry later"); - BluetoothProperties.factory_reset(true); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Get the UUIDs supported by the local Bluetooth adapter. - * - * @return the UUIDs supported by the local Bluetooth Adapter. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @Nullable ParcelUuid[] getUuids() { - if (getState() != STATE_ON) { - return null; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getUuids(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return null; - } - - /** - * Set the friendly Bluetooth name of the local Bluetooth adapter. - * <p>This name is visible to remote Bluetooth devices. - * <p>Valid Bluetooth names are a maximum of 248 bytes using UTF-8 - * encoding, although many remote devices can only display the first - * 40 characters, and some may be limited to just 20. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @param name a valid Bluetooth name - * @return true if the name was set, false otherwise - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setName(String name) { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.setName(name, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Returns the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth - * adapter. - * - * @return {@link BluetoothClass} Bluetooth CoD of local Bluetooth device. - * - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothClass getBluetoothClass() { - if (getState() != STATE_ON) { - return null; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getBluetoothClass(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return null; - } - - /** - * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth - * adapter. - * - * <p>Note: This value persists across system reboot. - * - * @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to. - * @return true if successful, false if unsuccessful. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setBluetoothClass(BluetoothClass bluetoothClass) { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.setBluetoothClass(bluetoothClass, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Returns the Input/Output capability of the device for classic Bluetooth. - * - * @return Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT}, - * {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, {@link #IO_CAPABILITY_NONE}, - * {@link #IO_CAPABILITY_KBDISP} or {@link #IO_CAPABILITY_UNKNOWN}. - * - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @IoCapability - public int getIoCapability() { - if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; - try { - mServiceLock.readLock().lock(); - if (mService != null) return mService.getIoCapability(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage(), e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; - } - - /** - * Sets the Input/Output capability of the device for classic Bluetooth. - * - * <p>Changing the Input/Output capability of a device only takes effect on restarting the - * Bluetooth stack. You would need to restart the stack using {@link BluetoothAdapter#disable()} - * and {@link BluetoothAdapter#enable()} to see the changes. - * - * @param capability Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT}, - * {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, - * {@link #IO_CAPABILITY_NONE} or {@link #IO_CAPABILITY_KBDISP}. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setIoCapability(@IoCapability int capability) { - if (getState() != STATE_ON) return false; - try { - mServiceLock.readLock().lock(); - if (mService != null) return mService.setIoCapability(capability, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage(), e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Returns the Input/Output capability of the device for BLE operations. - * - * @return Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT}, - * {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, {@link #IO_CAPABILITY_NONE}, - * {@link #IO_CAPABILITY_KBDISP} or {@link #IO_CAPABILITY_UNKNOWN}. - * - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @IoCapability - public int getLeIoCapability() { - if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; - try { - mServiceLock.readLock().lock(); - if (mService != null) return mService.getLeIoCapability(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage(), e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; - } - - /** - * Sets the Input/Output capability of the device for BLE operations. - * - * <p>Changing the Input/Output capability of a device only takes effect on restarting the - * Bluetooth stack. You would need to restart the stack using {@link BluetoothAdapter#disable()} - * and {@link BluetoothAdapter#enable()} to see the changes. - * - * @param capability Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT}, - * {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, - * {@link #IO_CAPABILITY_NONE} or {@link #IO_CAPABILITY_KBDISP}. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setLeIoCapability(@IoCapability int capability) { - if (getState() != STATE_ON) return false; - try { - mServiceLock.readLock().lock(); - if (mService != null) return mService.setLeIoCapability(capability, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage(), e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Get the current Bluetooth scan mode of the local Bluetooth adapter. - * <p>The Bluetooth scan mode determines if the local adapter is - * connectable and/or discoverable from remote Bluetooth devices. - * <p>Possible values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return {@link #SCAN_MODE_NONE}. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @return scan mode - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @ScanMode - public int getScanMode() { - if (getState() != STATE_ON) { - return SCAN_MODE_NONE; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getScanMode(mAttributionSource); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return SCAN_MODE_NONE; - } - - /** - * Set the local Bluetooth adapter connectablility and discoverability. - * <p>If the scan mode is set to {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, - * it will change to {@link #SCAN_MODE_CONNECTABLE} after the discoverable timeout. - * The discoverable timeout can be set with {@link #setDiscoverableTimeout} and - * checked with {@link #getDiscoverableTimeout}. By default, the timeout is usually - * 120 seconds on phones which is enough for a remote device to initiate and complete - * its discovery process. - * <p>Applications cannot set the scan mode. They should use - * {@link #ACTION_REQUEST_DISCOVERABLE} instead. - * - * @param mode represents the desired state of the local device scan mode - * - * @return status code indicating whether the scan mode was successfully set - * @hide - */ - @SystemApi - @RequiresBluetoothScanPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - @ScanModeStatusCode - public int setScanMode(@ScanMode int mode) { - if (getState() != STATE_ON) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.setScanMode(mode, mAttributionSource); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Get the timeout duration of the {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. - * - * @return the duration of the discoverable timeout or null if an error has occurred - */ - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public @Nullable Duration getDiscoverableTimeout() { - if (getState() != STATE_ON) { - return null; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - long timeout = mService.getDiscoverableTimeout(mAttributionSource); - return (timeout == -1) ? null : Duration.ofSeconds(timeout); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return null; - } - - /** - * Set the total time the Bluetooth local adapter will stay discoverable when - * {@link #setScanMode} is called with {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE} mode. - * After this timeout, the scan mode will fallback to {@link #SCAN_MODE_CONNECTABLE}. - * <p>If <code>timeout</code> is set to 0, no timeout will occur and the scan mode will - * be persisted until a subsequent call to {@link #setScanMode}. - * - * @param timeout represents the total duration the local Bluetooth adapter will remain - * discoverable, or no timeout if set to 0 - * @return whether the timeout was successfully set - * @throws IllegalArgumentException if <code>timeout</code> duration in seconds is more - * than {@link Integer#MAX_VALUE} - * @hide - */ - @SystemApi - @RequiresBluetoothScanPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - @ScanModeStatusCode - public int setDiscoverableTimeout(@NonNull Duration timeout) { - if (getState() != STATE_ON) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - if (timeout.toSeconds() > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Timeout in seconds must be less or equal to " - + Integer.MAX_VALUE); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.setDiscoverableTimeout(timeout.toSeconds(), mAttributionSource); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Get the end time of the latest remote device discovery process. - * - * @return the latest time that the bluetooth adapter was/will be in discovery mode, in - * milliseconds since the epoch. This time can be in the future if {@link #startDiscovery()} has - * been called recently. - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public long getDiscoveryEndMillis() { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getDiscoveryEndMillis(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return -1; - } - - /** - * Start the remote device discovery process. - * <p>The discovery process usually involves an inquiry scan of about 12 - * seconds, followed by a page scan of each new device to retrieve its - * Bluetooth name. - * <p>This is an asynchronous call, it will return immediately. Register - * for {@link #ACTION_DISCOVERY_STARTED} and {@link - * #ACTION_DISCOVERY_FINISHED} intents to determine exactly when the - * discovery starts and completes. Register for {@link - * BluetoothDevice#ACTION_FOUND} to be notified as remote Bluetooth devices - * are found. - * <p>Device discovery is a heavyweight procedure. New connections to - * remote Bluetooth devices should not be attempted while discovery is in - * progress, and existing connections will experience limited bandwidth - * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing - * discovery. Discovery is not managed by the Activity, - * but is run as a system service, so an application should always call - * {@link BluetoothAdapter#cancelDiscovery()} even if it - * did not directly request a discovery, just to be sure. - * <p>Device discovery will only find remote devices that are currently - * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are - * not discoverable by default, and need to be entered into a special mode. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, wait for {@link #ACTION_STATE_CHANGED} - * with {@link #STATE_ON} to get the updated value. - * <p>If a device is currently bonding, this request will be queued and executed once that - * device has finished bonding. If a request is already queued, this request will be ignored. - * - * @return true on success, false on error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean startDiscovery() { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.startDiscovery(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Cancel the current device discovery process. - * <p>Because discovery is a heavyweight procedure for the Bluetooth - * adapter, this method should always be called before attempting to connect - * to a remote device with {@link - * android.bluetooth.BluetoothSocket#connect()}. Discovery is not managed by - * the Activity, but is run as a system service, so an application should - * always call cancel discovery even if it did not directly request a - * discovery, just to be sure. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @return true on success, false on error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean cancelDiscovery() { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.cancelDiscovery(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if the local Bluetooth adapter is currently in the device - * discovery process. - * <p>Device discovery is a heavyweight procedure. New connections to - * remote Bluetooth devices should not be attempted while discovery is in - * progress, and existing connections will experience limited bandwidth - * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing - * discovery. - * <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED} - * or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery - * starts or completes. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @return true if discovering - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean isDiscovering() { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isDiscovering(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Removes the active device for the grouping of @ActiveDeviceUse specified - * - * @param profiles represents the purpose for which we are setting this as the active device. - * Possible values are: - * {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO}, - * {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL}, - * {@link BluetoothAdapter#ACTIVE_DEVICE_ALL} - * @return false on immediate error, true otherwise - * @throws IllegalArgumentException if device is null or profiles is not one of - * {@link ActiveDeviceUse} - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean removeActiveDevice(@ActiveDeviceUse int profiles) { - if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL - && profiles != ACTIVE_DEVICE_ALL) { - Log.e(TAG, "Invalid profiles param value in removeActiveDevice"); - throw new IllegalArgumentException("Profiles must be one of " - + "BluetoothAdapter.ACTIVE_DEVICE_AUDIO, " - + "BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL, or " - + "BluetoothAdapter.ACTIVE_DEVICE_ALL"); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - if (DBG) Log.d(TAG, "removeActiveDevice, profiles: " + profiles); - return mService.removeActiveDevice(profiles, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - - return false; - } - - /** - * Sets device as the active devices for the profiles passed into the function - * - * @param device is the remote bluetooth device - * @param profiles represents the purpose for which we are setting this as the active device. - * Possible values are: - * {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO}, - * {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL}, - * {@link BluetoothAdapter#ACTIVE_DEVICE_ALL} - * @return false on immediate error, true otherwise - * @throws IllegalArgumentException if device is null or profiles is not one of - * {@link ActiveDeviceUse} - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean setActiveDevice(@NonNull BluetoothDevice device, - @ActiveDeviceUse int profiles) { - if (device == null) { - Log.e(TAG, "setActiveDevice: Null device passed as parameter"); - throw new IllegalArgumentException("device cannot be null"); - } - if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL - && profiles != ACTIVE_DEVICE_ALL) { - Log.e(TAG, "Invalid profiles param value in setActiveDevice"); - throw new IllegalArgumentException("Profiles must be one of " - + "BluetoothAdapter.ACTIVE_DEVICE_AUDIO, " - + "BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL, or " - + "BluetoothAdapter.ACTIVE_DEVICE_ALL"); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - if (DBG) { - Log.d(TAG, "setActiveDevice, device: " + device + ", profiles: " + profiles); - } - return mService.setActiveDevice(device, profiles, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - - return false; - } - - /** - * Get the active devices for the BluetoothProfile specified - * - * @param profile is the profile from which we want the active devices. - * Possible values are: - * {@link BluetoothProfile#HEADSET}, - * {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEARING_AID} - * {@link BluetoothProfile#LE_AUDIO} - * @return A list of active bluetooth devices - * @throws IllegalArgumentException If profile is not one of {@link ActiveDeviceProfile} - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @NonNull List<BluetoothDevice> getActiveDevices(@ActiveDeviceProfile int profile) { - if (profile != BluetoothProfile.HEADSET - && profile != BluetoothProfile.A2DP - && profile != BluetoothProfile.HEARING_AID - && profile != BluetoothProfile.LE_AUDIO) { - Log.e(TAG, "Invalid profile param value in getActiveDevices"); - throw new IllegalArgumentException("Profiles must be one of " - + "BluetoothProfile.A2DP, " - + "BluetoothProfile.HEARING_AID, or" - + "BluetoothProfile.HEARING_AID" - + "BluetoothProfile.LE_AUDIO"); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - if (DBG) { - Log.d(TAG, "getActiveDevices(profile= " - + BluetoothProfile.getProfileName(profile) + ")"); - } - return mService.getActiveDevices(profile, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - - return new ArrayList<>(); - } - - /** - * Return true if the multi advertisement is supported by the chipset - * - * @return true if Multiple Advertisement feature is supported - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isMultipleAdvertisementSupported() { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isMultiAdvertisementSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isMultipleAdvertisementSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Returns {@code true} if BLE scan is always available, {@code false} otherwise. <p> - * - * If this returns {@code true}, application can issue {@link BluetoothLeScanner#startScan} and - * fetch scan results even when Bluetooth is turned off.<p> - * - * To change this setting, use {@link #ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE}. - * - * @hide - */ - @SystemApi - @RequiresNoPermission - public boolean isBleScanAlwaysAvailable() { - try { - return mManagerService.isBleScanAlwaysAvailable(); - } catch (RemoteException e) { - Log.e(TAG, "remote exception when calling isBleScanAlwaysAvailable", e); - return false; - } - } - - private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY = - "cache_key.bluetooth.is_offloaded_filtering_supported"; - private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache = - new PropertyInvalidatedCache<Void, Boolean>( - 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Boolean recompute(Void query) { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isOffloadedFilteringSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - - } - }; - - /** @hide */ - @RequiresNoPermission - public void disableIsOffloadedFilteringSupportedCache() { - mBluetoothFilteringCache.disableLocal(); - } - - /** @hide */ - public static void invalidateIsOffloadedFilteringSupportedCache() { - PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY); - } - - /** - * Return true if offloaded filters are supported - * - * @return true if chipset supports on-chip filtering - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isOffloadedFilteringSupported() { - if (!getLeAccess()) { - return false; - } - return mBluetoothFilteringCache.query(null); - } - - /** - * Return true if offloaded scan batching is supported - * - * @return true if chipset supports on-chip scan batching - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isOffloadedScanBatchingSupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isOffloadedScanBatchingSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isOffloadedScanBatchingSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if LE 2M PHY feature is supported. - * - * @return true if chipset supports LE 2M PHY feature - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isLe2MPhySupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLe2MPhySupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isExtendedAdvertisingSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if LE Coded PHY feature is supported. - * - * @return true if chipset supports LE Coded PHY feature - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isLeCodedPhySupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLeCodedPhySupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isLeCodedPhySupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if LE Extended Advertising feature is supported. - * - * @return true if chipset supports LE Extended Advertising feature - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isLeExtendedAdvertisingSupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLeExtendedAdvertisingSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isLeExtendedAdvertisingSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if LE Periodic Advertising feature is supported. - * - * @return true if chipset supports LE Periodic Advertising feature - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isLePeriodicAdvertisingSupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLePeriodicAdvertisingSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isLePeriodicAdvertisingSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_FEATURE_NOT_SUPPORTED, - }) - public @interface LeFeatureReturnValues {} - - /** - * Returns {@link BluetoothStatusCodes#SUCCESS} if the LE audio feature is - * supported, returns {@link BluetoothStatusCodes#ERROR_FEATURE_NOT_SUPPORTED} if - * the feature is not supported or an error code. - * - * @return whether the LE audio is supported - */ - @RequiresNoPermission - public @LeFeatureReturnValues int isLeAudioSupported() { - if (!getLeAccess()) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLeAudioSupported(); - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Returns {@link BluetoothStatusCodes#SUCCESS} if LE Periodic Advertising Sync Transfer Sender - * feature is supported, returns {@link BluetoothStatusCodes#ERROR_FEATURE_NOT_SUPPORTED} if the - * feature is not supported or an error code - * - * @return whether the chipset supports the LE Periodic Advertising Sync Transfer Sender feature - */ - @RequiresNoPermission - public @LeFeatureReturnValues int isLePeriodicAdvertisingSyncTransferSenderSupported() { - if (!getLeAccess()) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLePeriodicAdvertisingSyncTransferSenderSupported(); - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Return the maximum LE advertising data length in bytes, - * if LE Extended Advertising feature is supported, 0 otherwise. - * - * @return the maximum LE advertising data length. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public int getLeMaximumAdvertisingDataLength() { - if (!getLeAccess()) { - return 0; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getLeMaximumAdvertisingDataLength(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get getLeMaximumAdvertisingDataLength, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return 0; - } - - /** - * Return true if Hearing Aid Profile is supported. - * - * @return true if phone supports Hearing Aid Profile - */ - @RequiresNoPermission - private boolean isHearingAidProfileSupported() { - try { - return mManagerService.isHearingAidProfileSupported(); - } catch (RemoteException e) { - Log.e(TAG, "remote exception when calling isHearingAidProfileSupported", e); - return false; - } - } - - /** - * Get the maximum number of connected audio devices. - * - * @return the maximum number of connected audio devices - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getMaxConnectedAudioDevices() { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getMaxConnectedAudioDevices(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get getMaxConnectedAudioDevices, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return 1; - } - - /** - * Return true if hardware has entries available for matching beacons - * - * @return true if there are hw entries available for matching beacons - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isHardwareTrackingFiltersAvailable() { - if (!getLeAccess()) { - return false; - } - try { - IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); - if (iGatt == null) { - // BLE is not supported - return false; - } - return (iGatt.numHwTrackFiltersAvailable(mAttributionSource) != 0); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Request the record of {@link BluetoothActivityEnergyInfo} object that - * has the activity and energy info. This can be used to ascertain what - * the controller has been up to, since the last sample. - * - * A null value for the activity info object may be sent if the bluetooth service is - * unreachable or the device does not support reporting such information. - * - * @param result The callback to which to send the activity info. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public void requestControllerActivityEnergyInfo(ResultReceiver result) { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - mService.requestActivityInfo(result, mAttributionSource); - result = null; - } - } catch (RemoteException e) { - Log.e(TAG, "getControllerActivityEnergyInfoCallback: " + e); - } finally { - mServiceLock.readLock().unlock(); - if (result != null) { - // Only send an immediate result if we failed. - result.send(0, null); - } - } - } - - /** - * Fetches a list of the most recently connected bluetooth devices ordered by how recently they - * were connected with most recently first and least recently last - * - * @return {@link List} of bonded {@link BluetoothDevice} ordered by how recently they were - * connected - * - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getMostRecentlyConnectedDevices() { - if (getState() != STATE_ON) { - return new ArrayList<>(); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return Attributable.setAttributionSource( - mService.getMostRecentlyConnectedDevices(mAttributionSource), - mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return new ArrayList<>(); - } - - /** - * Return the set of {@link BluetoothDevice} objects that are bonded - * (paired) to the local adapter. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return an empty set. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @return unmodifiable set of {@link BluetoothDevice}, or null on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public Set<BluetoothDevice> getBondedDevices() { - if (getState() != STATE_ON) { - return toDeviceSet(Arrays.asList()); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return toDeviceSet(Attributable.setAttributionSource( - Arrays.asList(mService.getBondedDevices(mAttributionSource)), - mAttributionSource)); - } - return toDeviceSet(Arrays.asList()); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return null; - } - - /** - * Gets the currently supported profiles by the adapter. - * - * <p> This can be used to check whether a profile is supported before attempting - * to connect to its respective proxy. - * - * @return a list of integers indicating the ids of supported profiles as defined in {@link - * BluetoothProfile}. - * @hide - */ - @RequiresNoPermission - public @NonNull List<Integer> getSupportedProfiles() { - final ArrayList<Integer> supportedProfiles = new ArrayList<Integer>(); - - try { - synchronized (mManagerCallback) { - if (mService != null) { - final long supportedProfilesBitMask = mService.getSupportedProfiles(); - - for (int i = 0; i <= BluetoothProfile.MAX_PROFILE_ID; i++) { - if ((supportedProfilesBitMask & (1 << i)) != 0) { - supportedProfiles.add(i); - } - } - } else { - // Bluetooth is disabled. Just fill in known supported Profiles - if (isHearingAidProfileSupported()) { - supportedProfiles.add(BluetoothProfile.HEARING_AID); - } - } - } - } catch (RemoteException e) { - Log.e(TAG, "getSupportedProfiles:", e); - } - return supportedProfiles; - } - - private static final String BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY = - "cache_key.bluetooth.get_adapter_connection_state"; - private final PropertyInvalidatedCache<Void, Integer> - mBluetoothGetAdapterConnectionStateCache = - new PropertyInvalidatedCache<Void, Integer> ( - 8, BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY) { - /** - * This method must not be called when mService is null. - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Integer recompute(Void query) { - try { - return mService.getAdapterConnectionState(); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - }; - - /** @hide */ - @RequiresNoPermission - public void disableGetAdapterConnectionStateCache() { - mBluetoothGetAdapterConnectionStateCache.disableLocal(); - } - - /** @hide */ - public static void invalidateGetAdapterConnectionStateCache() { - PropertyInvalidatedCache.invalidateCache( - BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY); - } - - /** - * Get the current connection state of the local Bluetooth adapter. - * This can be used to check whether the local Bluetooth adapter is connected - * to any profile of any other remote Bluetooth Device. - * - * <p> Use this function along with {@link #ACTION_CONNECTION_STATE_CHANGED} - * intent to get the connection state of the adapter. - * - * @return One of {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTED}, {@link - * #STATE_CONNECTING} or {@link #STATE_DISCONNECTED} - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public int getConnectionState() { - if (getState() != STATE_ON) { - return BluetoothAdapter.STATE_DISCONNECTED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mBluetoothGetAdapterConnectionStateCache.query(null); - } - } catch (RuntimeException e) { - if (e.getCause() instanceof RemoteException) { - Log.e(TAG, "getConnectionState:", e.getCause()); - } else { - throw e; - } - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothAdapter.STATE_DISCONNECTED; - } - - private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY = - "cache_key.bluetooth.get_profile_connection_state"; - private final PropertyInvalidatedCache<Integer, Integer> - mGetProfileConnectionStateCache = - new PropertyInvalidatedCache<Integer, Integer>( - 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Integer recompute(Integer query) { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getProfileConnectionState(query); - } - } catch (RemoteException e) { - Log.e(TAG, "getProfileConnectionState:", e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothProfile.STATE_DISCONNECTED; - } - @Override - public String queryToString(Integer query) { - return String.format("getProfileConnectionState(profile=\"%d\")", - query); - } - }; - - /** @hide */ - @RequiresNoPermission - public void disableGetProfileConnectionStateCache() { - mGetProfileConnectionStateCache.disableLocal(); - } - - /** @hide */ - public static void invalidateGetProfileConnectionStateCache() { - PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY); - } - - /** - * Get the current connection state of a profile. - * This function can be used to check whether the local Bluetooth adapter - * is connected to any remote device for a specific profile. - * Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}. - * - * <p> Return value can be one of - * {@link BluetoothProfile#STATE_DISCONNECTED}, - * {@link BluetoothProfile#STATE_CONNECTING}, - * {@link BluetoothProfile#STATE_CONNECTED}, - * {@link BluetoothProfile#STATE_DISCONNECTING} - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public int getProfileConnectionState(int profile) { - if (getState() != STATE_ON) { - return BluetoothProfile.STATE_DISCONNECTED; - } - return mGetProfileConnectionStateCache.query(new Integer(profile)); - } - - /** - * Create a listening, secure RFCOMM Bluetooth socket. - * <p>A remote device connecting to this socket will be authenticated and - * communication on this socket will be encrypted. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>Valid RFCOMM channels are in range 1 to 30. - * - * @param channel RFCOMM channel to listen on - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException { - return listenUsingRfcommOn(channel, false, false); - } - - /** - * Create a listening, secure RFCOMM Bluetooth socket. - * <p>A remote device connecting to this socket will be authenticated and - * communication on this socket will be encrypted. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>Valid RFCOMM channels are in range 1 to 30. - * <p>To auto assign a channel without creating a SDP record use - * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as channel number. - * - * @param channel RFCOMM channel to listen on - * @param mitm enforce person-in-the-middle protection for authentication. - * @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2 - * connections. - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm, - boolean min16DigitPin) throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, true, true, channel, mitm, - min16DigitPin); - int errno = socket.mSocket.bindListen(); - if (channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - socket.setChannel(socket.mSocket.getPort()); - } - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - } - - /** - * Create a listening, secure RFCOMM Bluetooth socket with Service Record. - * <p>A remote device connecting to this socket will be authenticated and - * communication on this socket will be encrypted. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>The system will assign an unused RFCOMM channel to listen on. - * <p>The system will also register a Service Discovery - * Protocol (SDP) record with the local SDP server containing the specified - * UUID, service name, and auto-assigned channel. Remote Bluetooth devices - * can use the same UUID to query our SDP server and discover which channel - * to connect to. This SDP record will be removed when this socket is - * closed, or if this application closes unexpectedly. - * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to - * connect to this socket from another device using the same {@link UUID}. - * - * @param name service name for SDP record - * @param uuid uuid for SDP record - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid) - throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, true, true); - } - - /** - * Create a listening, insecure RFCOMM Bluetooth socket with Service Record. - * <p>The link key is not required to be authenticated, i.e the communication may be - * vulnerable to Person In the Middle attacks. For Bluetooth 2.1 devices, - * the link will be encrypted, as encryption is mandatory. - * For legacy devices (pre Bluetooth 2.1 devices) the link will not - * be encrypted. Use {@link #listenUsingRfcommWithServiceRecord}, if an - * encrypted and authenticated communication channel is desired. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>The system will assign an unused RFCOMM channel to listen on. - * <p>The system will also register a Service Discovery - * Protocol (SDP) record with the local SDP server containing the specified - * UUID, service name, and auto-assigned channel. Remote Bluetooth devices - * can use the same UUID to query our SDP server and discover which channel - * to connect to. This SDP record will be removed when this socket is - * closed, or if this application closes unexpectedly. - * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to - * connect to this socket from another device using the same {@link UUID}. - * - * @param name service name for SDP record - * @param uuid uuid for SDP record - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid) - throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, false, false); - } - - /** - * Create a listening, encrypted, - * RFCOMM Bluetooth socket with Service Record. - * <p>The link will be encrypted, but the link key is not required to be authenticated - * i.e the communication is vulnerable to Person In the Middle attacks. Use - * {@link #listenUsingRfcommWithServiceRecord}, to ensure an authenticated link key. - * <p> Use this socket if authentication of link key is not possible. - * For example, for Bluetooth 2.1 devices, if any of the devices does not have - * an input and output capability or just has the ability to display a numeric key, - * a secure socket connection is not possible and this socket can be used. - * Use {@link #listenUsingInsecureRfcommWithServiceRecord}, if encryption is not required. - * For Bluetooth 2.1 devices, the link will be encrypted, as encryption is mandatory. - * For more details, refer to the Security Model section 5.2 (vol 3) of - * Bluetooth Core Specification version 2.1 + EDR. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>The system will assign an unused RFCOMM channel to listen on. - * <p>The system will also register a Service Discovery - * Protocol (SDP) record with the local SDP server containing the specified - * UUID, service name, and auto-assigned channel. Remote Bluetooth devices - * can use the same UUID to query our SDP server and discover which channel - * to connect to. This SDP record will be removed when this socket is - * closed, or if this application closes unexpectedly. - * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to - * connect to this socket from another device using the same {@link UUID}. - * - * @param name service name for SDP record - * @param uuid uuid for SDP record - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord(String name, UUID uuid) - throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, false, true); - } - - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid, - boolean auth, boolean encrypt) throws IOException { - BluetoothServerSocket socket; - socket = new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, auth, encrypt, - new ParcelUuid(uuid)); - socket.setServiceName(name); - int errno = socket.mSocket.bindListen(); - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - } - - /** - * Construct an unencrypted, unauthenticated, RFCOMM server socket. - * Call #accept to retrieve connections to this socket. - * - * @return An RFCOMM BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, false, port); - int errno = socket.mSocket.bindListen(); - if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - socket.setChannel(socket.mSocket.getPort()); - } - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - } - - /** - * Construct an encrypted, authenticated, L2CAP server socket. - * Call #accept to retrieve connections to this socket. - * <p>To auto assign a port without creating a SDP record use - * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number. - * - * @param port the PSM to listen on - * @param mitm enforce person-in-the-middle protection for authentication. - * @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2 - * connections. - * @return An L2CAP BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingL2capOn(int port, boolean mitm, boolean min16DigitPin) - throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP, true, true, port, mitm, - min16DigitPin); - int errno = socket.mSocket.bindListen(); - if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - int assignedChannel = socket.mSocket.getPort(); - if (DBG) Log.d(TAG, "listenUsingL2capOn: set assigned channel to " + assignedChannel); - socket.setChannel(assignedChannel); - } - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - } - - /** - * Construct an encrypted, authenticated, L2CAP server socket. - * Call #accept to retrieve connections to this socket. - * <p>To auto assign a port without creating a SDP record use - * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number. - * - * @param port the PSM to listen on - * @return An L2CAP BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException { - return listenUsingL2capOn(port, false, false); - } - - /** - * Construct an insecure L2CAP server socket. - * Call #accept to retrieve connections to this socket. - * <p>To auto assign a port without creating a SDP record use - * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number. - * - * @param port the PSM to listen on - * @return An L2CAP BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException { - Log.d(TAG, "listenUsingInsecureL2capOn: port=" + port); - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP, false, false, port, false, - false); - int errno = socket.mSocket.bindListen(); - if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - int assignedChannel = socket.mSocket.getPort(); - if (DBG) { - Log.d(TAG, "listenUsingInsecureL2capOn: set assigned channel to " - + assignedChannel); - } - socket.setChannel(assignedChannel); - } - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - - } - - /** - * Read the local Out of Band Pairing Data - * - * @return Pair<byte[], byte[]> of Hash and Randomizer - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public Pair<byte[], byte[]> readOutOfBandData() { - return null; - } - - /** - * Get the profile proxy object associated with the profile. - * - * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link - * BluetoothProfile#GATT_SERVER}. Clients must implement {@link - * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the - * proxy object. - * - * @param context Context of the application - * @param listener The service Listener for connection callbacks. - * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET}, - * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link - * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}. - * @return true on success, false on error - */ - @SuppressLint({ - "AndroidFrameworkRequiresPermission", - "AndroidFrameworkBluetoothPermission" - }) - public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, - int profile) { - if (context == null || listener == null) { - return false; - } - - if (profile == BluetoothProfile.HEADSET) { - BluetoothHeadset headset = new BluetoothHeadset(context, listener, this); - return true; - } else if (profile == BluetoothProfile.A2DP) { - BluetoothA2dp a2dp = new BluetoothA2dp(context, listener, this); - return true; - } else if (profile == BluetoothProfile.A2DP_SINK) { - BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener, this); - return true; - } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) { - BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HID_HOST) { - BluetoothHidHost iDev = new BluetoothHidHost(context, listener, this); - return true; - } else if (profile == BluetoothProfile.PAN) { - BluetoothPan pan = new BluetoothPan(context, listener, this); - return true; - } else if (profile == BluetoothProfile.PBAP) { - BluetoothPbap pbap = new BluetoothPbap(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HEALTH) { - Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated"); - return false; - } else if (profile == BluetoothProfile.MAP) { - BluetoothMap map = new BluetoothMap(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HEADSET_CLIENT) { - BluetoothHeadsetClient headsetClient = - new BluetoothHeadsetClient(context, listener, this); - return true; - } else if (profile == BluetoothProfile.SAP) { - BluetoothSap sap = new BluetoothSap(context, listener, this); - return true; - } else if (profile == BluetoothProfile.PBAP_CLIENT) { - BluetoothPbapClient pbapClient = new BluetoothPbapClient(context, listener, this); - return true; - } else if (profile == BluetoothProfile.MAP_CLIENT) { - BluetoothMapClient mapClient = new BluetoothMapClient(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HID_DEVICE) { - BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HEARING_AID) { - if (isHearingAidProfileSupported()) { - BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener, this); - return true; - } - return false; - } else if (profile == BluetoothProfile.LE_AUDIO) { - BluetoothLeAudio leAudio = new BluetoothLeAudio(context, listener, this); - return true; - } else if (profile == BluetoothProfile.VOLUME_CONTROL) { - BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this); - return true; - } else if (profile == BluetoothProfile.CSIP_SET_COORDINATOR) { - BluetoothCsipSetCoordinator csipSetCoordinator = - new BluetoothCsipSetCoordinator(context, listener, this); - return true; - } else { - return false; - } - } - - /** - * Close the connection of the profile proxy to the Service. - * - * <p> Clients should call this when they are no longer using - * the proxy obtained from {@link #getProfileProxy}. - * Profile can be one of {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#A2DP} - * - * @param profile - * @param proxy Profile proxy object - */ - @SuppressLint({ - "AndroidFrameworkRequiresPermission", - "AndroidFrameworkBluetoothPermission" - }) - public void closeProfileProxy(int profile, BluetoothProfile proxy) { - if (proxy == null) { - return; - } - - switch (profile) { - case BluetoothProfile.HEADSET: - BluetoothHeadset headset = (BluetoothHeadset) proxy; - headset.close(); - break; - case BluetoothProfile.A2DP: - BluetoothA2dp a2dp = (BluetoothA2dp) proxy; - a2dp.close(); - break; - case BluetoothProfile.A2DP_SINK: - BluetoothA2dpSink a2dpSink = (BluetoothA2dpSink) proxy; - a2dpSink.close(); - break; - case BluetoothProfile.AVRCP_CONTROLLER: - BluetoothAvrcpController avrcp = (BluetoothAvrcpController) proxy; - avrcp.close(); - break; - case BluetoothProfile.HID_HOST: - BluetoothHidHost iDev = (BluetoothHidHost) proxy; - iDev.close(); - break; - case BluetoothProfile.PAN: - BluetoothPan pan = (BluetoothPan) proxy; - pan.close(); - break; - case BluetoothProfile.PBAP: - BluetoothPbap pbap = (BluetoothPbap) proxy; - pbap.close(); - break; - case BluetoothProfile.GATT: - BluetoothGatt gatt = (BluetoothGatt) proxy; - gatt.close(); - break; - case BluetoothProfile.GATT_SERVER: - BluetoothGattServer gattServer = (BluetoothGattServer) proxy; - gattServer.close(); - break; - case BluetoothProfile.MAP: - BluetoothMap map = (BluetoothMap) proxy; - map.close(); - break; - case BluetoothProfile.HEADSET_CLIENT: - BluetoothHeadsetClient headsetClient = (BluetoothHeadsetClient) proxy; - headsetClient.close(); - break; - case BluetoothProfile.SAP: - BluetoothSap sap = (BluetoothSap) proxy; - sap.close(); - break; - case BluetoothProfile.PBAP_CLIENT: - BluetoothPbapClient pbapClient = (BluetoothPbapClient) proxy; - pbapClient.close(); - break; - case BluetoothProfile.MAP_CLIENT: - BluetoothMapClient mapClient = (BluetoothMapClient) proxy; - mapClient.close(); - break; - case BluetoothProfile.HID_DEVICE: - BluetoothHidDevice hidDevice = (BluetoothHidDevice) proxy; - hidDevice.close(); - break; - case BluetoothProfile.HEARING_AID: - BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy; - hearingAid.close(); - break; - case BluetoothProfile.LE_AUDIO: - BluetoothLeAudio leAudio = (BluetoothLeAudio) proxy; - leAudio.close(); - break; - case BluetoothProfile.VOLUME_CONTROL: - BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy; - vcs.close(); - break; - case BluetoothProfile.CSIP_SET_COORDINATOR: - BluetoothCsipSetCoordinator csipSetCoordinator = - (BluetoothCsipSetCoordinator) proxy; - csipSetCoordinator.close(); - break; - } - } - - private static final IBluetoothManagerCallback sManagerCallback = - new IBluetoothManagerCallback.Stub() { - public void onBluetoothServiceUp(IBluetooth bluetoothService) { - if (DBG) { - Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService); - } - - synchronized (sServiceLock) { - sService = bluetoothService; - for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) { - try { - if (cb != null) { - cb.onBluetoothServiceUp(bluetoothService); - } else { - Log.d(TAG, "onBluetoothServiceUp: cb is null!"); - } - } catch (Exception e) { - Log.e(TAG, "", e); - } - } - } - } - - public void onBluetoothServiceDown() { - if (DBG) { - Log.d(TAG, "onBluetoothServiceDown"); - } - - synchronized (sServiceLock) { - sService = null; - for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) { - try { - if (cb != null) { - cb.onBluetoothServiceDown(); - } else { - Log.d(TAG, "onBluetoothServiceDown: cb is null!"); - } - } catch (Exception e) { - Log.e(TAG, "", e); - } - } - } - } - - public void onBrEdrDown() { - if (VDBG) { - Log.i(TAG, "onBrEdrDown"); - } - - synchronized (sServiceLock) { - for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) { - try { - if (cb != null) { - cb.onBrEdrDown(); - } else { - Log.d(TAG, "onBrEdrDown: cb is null!"); - } - } catch (Exception e) { - Log.e(TAG, "", e); - } - } - } - } - }; - - private final IBluetoothManagerCallback mManagerCallback = - new IBluetoothManagerCallback.Stub() { - public void onBluetoothServiceUp(IBluetooth bluetoothService) { - synchronized (mServiceLock.writeLock()) { - mService = bluetoothService; - } - synchronized (mMetadataListeners) { - mMetadataListeners.forEach((device, pair) -> { - try { - mService.registerMetadataListener(mBluetoothMetadataListener, - device, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register metadata listener", e); - } - }); - } - synchronized (mBluetoothConnectionCallbackExecutorMap) { - if (!mBluetoothConnectionCallbackExecutorMap.isEmpty()) { - try { - mService.registerBluetoothConnectionCallback(mConnectionCallback, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "onBluetoothServiceUp: Failed to register bluetooth" - + "connection callback", e); - } - } - } - } - - public void onBluetoothServiceDown() { - synchronized (mServiceLock.writeLock()) { - mService = null; - if (mLeScanClients != null) { - mLeScanClients.clear(); - } - if (mBluetoothLeAdvertiser != null) { - mBluetoothLeAdvertiser.cleanup(); - } - if (mBluetoothLeScanner != null) { - mBluetoothLeScanner.cleanup(); - } - } - } - - public void onBrEdrDown() { - } - }; - - /** - * Enable the Bluetooth Adapter, but don't auto-connect devices - * and don't persist state. Only for use by system applications. - * - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean enableNoAutoConnect() { - if (isEnabled()) { - if (DBG) { - Log.d(TAG, "enableNoAutoConnect(): BT already enabled!"); - } - return true; - } - try { - return mManagerService.enableNoAutoConnect(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_ANOTHER_ACTIVE_OOB_REQUEST, - }) - public @interface OobError {} - - /** - * Provides callback methods for receiving {@link OobData} from the host stack, as well as an - * error interface in order to allow the caller to determine next steps based on the {@code - * ErrorCode}. - * - * @hide - */ - @SystemApi - public interface OobDataCallback { - /** - * Handles the {@link OobData} received from the host stack. - * - * @param transport - whether the {@link OobData} is generated for LE or Classic. - * @param oobData - data generated in the host stack(LE) or controller (Classic) - */ - void onOobData(@Transport int transport, @NonNull OobData oobData); - - /** - * Provides feedback when things don't go as expected. - * - * @param errorCode - the code describing the type of error that occurred. - */ - void onError(@OobError int errorCode); - } - - /** - * Wraps an AIDL interface around an {@link OobDataCallback} interface. - * - * @see {@link IBluetoothOobDataCallback} for interface definition. - * - * @hide - */ - public class WrappedOobDataCallback extends IBluetoothOobDataCallback.Stub { - private final OobDataCallback mCallback; - private final Executor mExecutor; - - /** - * @param callback - object to receive {@link OobData} must be a non null argument - * - * @throws NullPointerException if the callback is null. - */ - WrappedOobDataCallback(@NonNull OobDataCallback callback, - @NonNull @CallbackExecutor Executor executor) { - requireNonNull(callback); - requireNonNull(executor); - mCallback = callback; - mExecutor = executor; - } - /** - * Wrapper function to relay to the {@link OobDataCallback#onOobData} - * - * @param transport - whether the {@link OobData} is generated for LE or Classic. - * @param oobData - data generated in the host stack(LE) or controller (Classic) - * - * @hide - */ - public void onOobData(@Transport int transport, @NonNull OobData oobData) { - mExecutor.execute(new Runnable() { - public void run() { - mCallback.onOobData(transport, oobData); - } - }); - } - /** - * Wrapper function to relay to the {@link OobDataCallback#onError} - * - * @param errorCode - the code descibing the type of error that occurred. - * - * @hide - */ - public void onError(@OobError int errorCode) { - mExecutor.execute(new Runnable() { - public void run() { - mCallback.onError(errorCode); - } - }); - } - } - - /** - * Fetches a secret data value that can be used for a secure and simple pairing experience. - * - * <p>This is the Local Out of Band data the comes from the - * - * <p>This secret is the local Out of Band data. This data is used to securely and quickly - * pair two devices with minimal user interaction. - * - * <p>For example, this secret can be transferred to a remote device out of band (meaning any - * other way besides using bluetooth). Once the remote device finds this device using the - * information given in the data, such as the PUBLIC ADDRESS, the remote device could then - * connect to this device using this secret when the pairing sequenece asks for the secret. - * This device will respond by automatically accepting the pairing due to the secret being so - * trustworthy. - * - * @param transport - provide type of transport (e.g. LE or Classic). - * @param callback - target object to receive the {@link OobData} value. - * - * @throws NullPointerException if callback is null. - * @throws IllegalArgumentException if the transport is not valid. - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public void generateLocalOobData(@Transport int transport, - @NonNull @CallbackExecutor Executor executor, @NonNull OobDataCallback callback) { - if (transport != BluetoothDevice.TRANSPORT_BREDR && transport - != BluetoothDevice.TRANSPORT_LE) { - throw new IllegalArgumentException("Invalid transport '" + transport + "'!"); - } - requireNonNull(callback); - if (!isEnabled()) { - Log.w(TAG, "generateLocalOobData(): Adapter isn't enabled!"); - callback.onError(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); - } else { - try { - mService.generateLocalOobData(transport, new WrappedOobDataCallback(callback, - executor), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - } - - /** - * Enable control of the Bluetooth Adapter for a single application. - * - * <p>Some applications need to use Bluetooth for short periods of time to - * transfer data but don't want all the associated implications like - * automatic connection to headsets etc. - * - * <p> Multiple applications can call this. This is reference counted and - * Bluetooth disabled only when no one else is using it. There will be no UI - * shown to the user while bluetooth is being enabled. Any user action will - * override this call. For example, if user wants Bluetooth on and the last - * user of this API wanted to disable Bluetooth, Bluetooth will not be - * turned off. - * - * <p> This API is only meant to be used by internal applications. Third - * party applications but use {@link #enable} and {@link #disable} APIs. - * - * <p> If this API returns true, it means the callback will be called. - * The callback will be called with the current state of Bluetooth. - * If the state is not what was requested, an internal error would be the - * reason. If Bluetooth is already on and if this function is called to turn - * it on, the api will return true and a callback will be called. - * - * @param on True for on, false for off. - * @param callback The callback to notify changes to the state. - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean changeApplicationBluetoothState(boolean on, - BluetoothStateChangeCallback callback) { - return false; - } - - /** - * @hide - */ - public interface BluetoothStateChangeCallback { - /** - * @hide - */ - void onBluetoothStateChange(boolean on); - } - - /** - * @hide - */ - public class StateChangeCallbackWrapper extends IBluetoothStateChangeCallback.Stub { - private BluetoothStateChangeCallback mCallback; - - StateChangeCallbackWrapper(BluetoothStateChangeCallback callback) { - mCallback = callback; - } - - @Override - public void onBluetoothStateChange(boolean on) { - mCallback.onBluetoothStateChange(on); - } - } - - private Set<BluetoothDevice> toDeviceSet(List<BluetoothDevice> devices) { - Set<BluetoothDevice> deviceSet = new HashSet<BluetoothDevice>(devices); - return Collections.unmodifiableSet(deviceSet); - } - - protected void finalize() throws Throwable { - try { - removeServiceStateCallback(mManagerCallback); - } finally { - super.finalize(); - } - } - - /** - * Validate a String Bluetooth address, such as "00:43:A8:23:10:F0" - * <p>Alphabetic characters must be uppercase to be valid. - * - * @param address Bluetooth address as string - * @return true if the address is valid, false otherwise - */ - public static boolean checkBluetoothAddress(String address) { - if (address == null || address.length() != ADDRESS_LENGTH) { - return false; - } - for (int i = 0; i < ADDRESS_LENGTH; i++) { - char c = address.charAt(i); - switch (i % 3) { - case 0: - case 1: - if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { - // hex character, OK - break; - } - return false; - case 2: - if (c == ':') { - break; // OK - } - return false; - } - } - return true; - } - - /** - * Determines whether a String Bluetooth address, such as "F0:43:A8:23:10:00" - * is a RANDOM STATIC address. - * - * RANDOM STATIC: (addr & 0xC0) == 0xC0 - * RANDOM RESOLVABLE: (addr & 0xC0) == 0x40 - * RANDOM non-RESOLVABLE: (addr & 0xC0) == 0x00 - * - * @param address Bluetooth address as string - * @return true if the 2 Most Significant Bits of the address equals 0xC0. - * - * @hide - */ - public static boolean isAddressRandomStatic(@NonNull String address) { - requireNonNull(address); - return checkBluetoothAddress(address) - && (Integer.parseInt(address.split(":")[0], 16) & 0xC0) == 0xC0; - } - - /** {@hide} */ - @UnsupportedAppUsage - @RequiresNoPermission - public IBluetoothManager getBluetoothManager() { - return mManagerService; - } - - /** {@hide} */ - @RequiresNoPermission - public AttributionSource getAttributionSource() { - return mAttributionSource; - } - - @GuardedBy("sServiceLock") - private static final WeakHashMap<IBluetoothManagerCallback, Void> sProxyServiceStateCallbacks = - new WeakHashMap<>(); - - /*package*/ IBluetooth getBluetoothService() { - synchronized (sServiceLock) { - if (sProxyServiceStateCallbacks.isEmpty()) { - throw new IllegalStateException( - "Anonymous service access requires at least one lifecycle in process"); - } - return sService; - } - } - - @UnsupportedAppUsage - /*package*/ IBluetooth getBluetoothService(IBluetoothManagerCallback cb) { - Objects.requireNonNull(cb); - synchronized (sServiceLock) { - sProxyServiceStateCallbacks.put(cb, null); - registerOrUnregisterAdapterLocked(); - return sService; - } - } - - /*package*/ void removeServiceStateCallback(IBluetoothManagerCallback cb) { - Objects.requireNonNull(cb); - synchronized (sServiceLock) { - sProxyServiceStateCallbacks.remove(cb); - registerOrUnregisterAdapterLocked(); - } - } - - /** - * Handle registering (or unregistering) a single process-wide - * {@link IBluetoothManagerCallback} based on the presence of local - * {@link #sProxyServiceStateCallbacks} clients. - */ - @GuardedBy("sServiceLock") - private void registerOrUnregisterAdapterLocked() { - final boolean isRegistered = sServiceRegistered; - final boolean wantRegistered = !sProxyServiceStateCallbacks.isEmpty(); - - if (isRegistered != wantRegistered) { - if (wantRegistered) { - try { - sService = mManagerService.registerAdapter(sManagerCallback); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - try { - mManagerService.unregisterAdapter(sManagerCallback); - sService = null; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - sServiceRegistered = wantRegistered; - } - } - - /** - * Callback interface used to deliver LE scan results. - * - * @see #startLeScan(LeScanCallback) - * @see #startLeScan(UUID[], LeScanCallback) - */ - public interface LeScanCallback { - /** - * Callback reporting an LE device found during a device scan initiated - * by the {@link BluetoothAdapter#startLeScan} function. - * - * @param device Identifies the remote device - * @param rssi The RSSI value for the remote device as reported by the Bluetooth hardware. 0 - * if no RSSI value is available. - * @param scanRecord The content of the advertisement record offered by the remote device. - */ - void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord); - } - - /** - * Register a callback to receive events whenever the bluetooth stack goes down and back up, - * e.g. in the event the bluetooth is turned off/on via settings. - * - * If the bluetooth stack is currently up, there will not be an initial callback call. - * You can use the return value as an indication of this being the case. - * - * Callbacks will be delivered on a binder thread. - * - * @return whether bluetooth is already up currently - * - * @hide - */ - public boolean registerServiceLifecycleCallback(ServiceLifecycleCallback callback) { - return getBluetoothService(callback.mRemote) != null; - } - - /** - * Unregister a callback registered via {@link #registerServiceLifecycleCallback} - * - * @hide - */ - public void unregisterServiceLifecycleCallback(ServiceLifecycleCallback callback) { - removeServiceStateCallback(callback.mRemote); - } - - /** - * A callback for {@link #registerServiceLifecycleCallback} - * - * @hide - */ - public abstract static class ServiceLifecycleCallback { - - /** Called when the bluetooth stack is up */ - public abstract void onBluetoothServiceUp(); - - /** Called when the bluetooth stack is down */ - public abstract void onBluetoothServiceDown(); - - IBluetoothManagerCallback mRemote = new IBluetoothManagerCallback.Stub() { - @Override - public void onBluetoothServiceUp(IBluetooth bluetoothService) { - ServiceLifecycleCallback.this.onBluetoothServiceUp(); - } - - @Override - public void onBluetoothServiceDown() { - ServiceLifecycleCallback.this.onBluetoothServiceDown(); - } - - @Override - public void onBrEdrDown() {} - }; - } - - /** - * Starts a scan for Bluetooth LE devices. - * - * <p>Results of the scan are reported using the - * {@link LeScanCallback#onLeScan} callback. - * - * @param callback the callback LE scan results are delivered - * @return true, if the scan was started successfully - * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)} - * instead. - */ - @Deprecated - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean startLeScan(LeScanCallback callback) { - return startLeScan(null, callback); - } - - /** - * Starts a scan for Bluetooth LE devices, looking for devices that - * advertise given services. - * - * <p>Devices which advertise all specified services are reported using the - * {@link LeScanCallback#onLeScan} callback. - * - * @param serviceUuids Array of services to look for - * @param callback the callback LE scan results are delivered - * @return true, if the scan was started successfully - * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)} - * instead. - */ - @Deprecated - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) { - if (DBG) { - Log.d(TAG, "startLeScan(): " + Arrays.toString(serviceUuids)); - } - if (callback == null) { - if (DBG) { - Log.e(TAG, "startLeScan: null callback"); - } - return false; - } - BluetoothLeScanner scanner = getBluetoothLeScanner(); - if (scanner == null) { - if (DBG) { - Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); - } - return false; - } - - synchronized (mLeScanClients) { - if (mLeScanClients.containsKey(callback)) { - if (DBG) { - Log.e(TAG, "LE Scan has already started"); - } - return false; - } - - try { - IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); - if (iGatt == null) { - // BLE is not supported - return false; - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - ScanCallback scanCallback = new ScanCallback() { - @Override - public void onScanResult(int callbackType, ScanResult result) { - if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) { - // Should not happen. - Log.e(TAG, "LE Scan has already started"); - return; - } - ScanRecord scanRecord = result.getScanRecord(); - if (scanRecord == null) { - return; - } - if (serviceUuids != null) { - List<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); - for (UUID uuid : serviceUuids) { - uuids.add(new ParcelUuid(uuid)); - } - List<ParcelUuid> scanServiceUuids = scanRecord.getServiceUuids(); - if (scanServiceUuids == null || !scanServiceUuids.containsAll(uuids)) { - if (DBG) { - Log.d(TAG, "uuids does not match"); - } - return; - } - } - callback.onLeScan(result.getDevice(), result.getRssi(), - scanRecord.getBytes()); - } - }; - ScanSettings settings = new ScanSettings.Builder().setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES) - .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) - .build(); - - List<ScanFilter> filters = new ArrayList<ScanFilter>(); - if (serviceUuids != null && serviceUuids.length > 0) { - // Note scan filter does not support matching an UUID array so we put one - // UUID to hardware and match the whole array in callback. - ScanFilter filter = - new ScanFilter.Builder().setServiceUuid(new ParcelUuid(serviceUuids[0])) - .build(); - filters.add(filter); - } - scanner.startScan(filters, settings, scanCallback); - - mLeScanClients.put(callback, scanCallback); - return true; - - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - return false; - } - - /** - * Stops an ongoing Bluetooth LE device scan. - * - * @param callback used to identify which scan to stop must be the same handle used to start the - * scan - * @deprecated Use {@link BluetoothLeScanner#stopScan(ScanCallback)} instead. - */ - @Deprecated - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void stopLeScan(LeScanCallback callback) { - if (DBG) { - Log.d(TAG, "stopLeScan()"); - } - BluetoothLeScanner scanner = getBluetoothLeScanner(); - if (scanner == null) { - return; - } - synchronized (mLeScanClients) { - ScanCallback scanCallback = mLeScanClients.remove(callback); - if (scanCallback == null) { - if (DBG) { - Log.d(TAG, "scan not started yet"); - } - return; - } - scanner.stopScan(scanCallback); - } - } - - /** - * Create a secure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and - * assign a dynamic protocol/service multiplexer (PSM) value. This socket can be used to listen - * for incoming connections. The supported Bluetooth transport is LE only. - * <p>A remote device connecting to this socket will be authenticated and communication on this - * socket will be encrypted. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening - * {@link BluetoothServerSocket}. - * <p>The system will assign a dynamic PSM value. This PSM value can be read from the {@link - * BluetoothServerSocket#getPsm()} and this value will be released when this server socket is - * closed, Bluetooth is turned off, or the application exits unexpectedly. - * <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is - * defined and performed by the application. - * <p>Use {@link BluetoothDevice#createL2capChannel(int)} to connect to this server - * socket from another Android device that is given the PSM value. - * - * @return an L2CAP CoC BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or unable to start this CoC - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull BluetoothServerSocket listenUsingL2capChannel() - throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, true, true, - SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false); - int errno = socket.mSocket.bindListen(); - if (errno != 0) { - throw new IOException("Error: " + errno); - } - - int assignedPsm = socket.mSocket.getPort(); - if (assignedPsm == 0) { - throw new IOException("Error: Unable to assign PSM value"); - } - if (DBG) { - Log.d(TAG, "listenUsingL2capChannel: set assigned PSM to " - + assignedPsm); - } - socket.setChannel(assignedPsm); - - return socket; - } - - /** - * Create an insecure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and - * assign a dynamic PSM value. This socket can be used to listen for incoming connections. The - * supported Bluetooth transport is LE only. - * <p>The link key is not required to be authenticated, i.e the communication may be vulnerable - * to person-in-the-middle attacks. Use {@link #listenUsingL2capChannel}, if an encrypted and - * authenticated communication channel is desired. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening - * {@link BluetoothServerSocket}. - * <p>The system will assign a dynamic protocol/service multiplexer (PSM) value. This PSM value - * can be read from the {@link BluetoothServerSocket#getPsm()} and this value will be released - * when this server socket is closed, Bluetooth is turned off, or the application exits - * unexpectedly. - * <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is - * defined and performed by the application. - * <p>Use {@link BluetoothDevice#createInsecureL2capChannel(int)} to connect to this server - * socket from another Android device that is given the PSM value. - * - * @return an L2CAP CoC BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or unable to start this CoC - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull BluetoothServerSocket listenUsingInsecureL2capChannel() - throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, false, false, - SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false); - int errno = socket.mSocket.bindListen(); - if (errno != 0) { - throw new IOException("Error: " + errno); - } - - int assignedPsm = socket.mSocket.getPort(); - if (assignedPsm == 0) { - throw new IOException("Error: Unable to assign PSM value"); - } - if (DBG) { - Log.d(TAG, "listenUsingInsecureL2capChannel: set assigned PSM to " - + assignedPsm); - } - socket.setChannel(assignedPsm); - - return socket; - } - - /** - * Register a {@link #OnMetadataChangedListener} to receive update about metadata - * changes for this {@link BluetoothDevice}. - * Registration must be done when Bluetooth is ON and will last until - * {@link #removeOnMetadataChangedListener(BluetoothDevice)} is called, even when Bluetooth - * restarted in the middle. - * All input parameters should not be null or {@link NullPointerException} will be triggered. - * The same {@link BluetoothDevice} and {@link #OnMetadataChangedListener} pair can only be - * registered once, double registration would cause {@link IllegalArgumentException}. - * - * @param device {@link BluetoothDevice} that will be registered - * @param executor the executor for listener callback - * @param listener {@link #OnMetadataChangedListener} that will receive asynchronous callbacks - * @return true on success, false on error - * @throws NullPointerException If one of {@code listener}, {@code device} or {@code executor} - * is null. - * @throws IllegalArgumentException The same {@link #OnMetadataChangedListener} and - * {@link BluetoothDevice} are registered twice. - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean addOnMetadataChangedListener(@NonNull BluetoothDevice device, - @NonNull Executor executor, @NonNull OnMetadataChangedListener listener) { - if (DBG) Log.d(TAG, "addOnMetadataChangedListener()"); - - final IBluetooth service = mService; - if (service == null) { - Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener"); - return false; - } - if (listener == null) { - throw new NullPointerException("listener is null"); - } - if (device == null) { - throw new NullPointerException("device is null"); - } - if (executor == null) { - throw new NullPointerException("executor is null"); - } - - synchronized (mMetadataListeners) { - List<Pair<OnMetadataChangedListener, Executor>> listenerList = - mMetadataListeners.get(device); - if (listenerList == null) { - // Create new listener/executor list for registeration - listenerList = new ArrayList<>(); - mMetadataListeners.put(device, listenerList); - } else { - // Check whether this device was already registed by the lisenter - if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) { - throw new IllegalArgumentException("listener was already regestered" - + " for the device"); - } - } - - Pair<OnMetadataChangedListener, Executor> listenerPair = new Pair(listener, executor); - listenerList.add(listenerPair); - - boolean ret = false; - try { - ret = service.registerMetadataListener(mBluetoothMetadataListener, device, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "registerMetadataListener fail", e); - } finally { - if (!ret) { - // Remove listener registered earlier when fail. - listenerList.remove(listenerPair); - if (listenerList.isEmpty()) { - // Remove the device if its listener list is empty - mMetadataListeners.remove(device); - } - } - } - return ret; - } - } - - /** - * Unregister a {@link #OnMetadataChangedListener} from a registered {@link BluetoothDevice}. - * Unregistration can be done when Bluetooth is either ON or OFF. - * {@link #addOnMetadataChangedListener(OnMetadataChangedListener, BluetoothDevice, Executor)} - * must be called before unregisteration. - * - * @param device {@link BluetoothDevice} that will be unregistered. It - * should not be null or {@link NullPointerException} will be triggered. - * @param listener {@link OnMetadataChangedListener} that will be unregistered. It - * should not be null or {@link NullPointerException} will be triggered. - * @return true on success, false on error - * @throws NullPointerException If {@code listener} or {@code device} is null. - * @throws IllegalArgumentException If {@code device} has not been registered before. - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean removeOnMetadataChangedListener(@NonNull BluetoothDevice device, - @NonNull OnMetadataChangedListener listener) { - if (DBG) Log.d(TAG, "removeOnMetadataChangedListener()"); - if (device == null) { - throw new NullPointerException("device is null"); - } - if (listener == null) { - throw new NullPointerException("listener is null"); - } - - synchronized (mMetadataListeners) { - if (!mMetadataListeners.containsKey(device)) { - throw new IllegalArgumentException("device was not registered"); - } - // Remove issued listener from the registered device - mMetadataListeners.get(device).removeIf((pair) -> (pair.first.equals(listener))); - - if (mMetadataListeners.get(device).isEmpty()) { - // Unregister to Bluetooth service if all listeners are removed from - // the registered device - mMetadataListeners.remove(device); - final IBluetooth service = mService; - if (service == null) { - // Bluetooth is OFF, do nothing to Bluetooth service. - return true; - } - try { - return service.unregisterMetadataListener(device, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "unregisterMetadataListener fail", e); - return false; - } - } - } - return true; - } - - /** - * This interface is used to implement {@link BluetoothAdapter} metadata listener. - * @hide - */ - @SystemApi - public interface OnMetadataChangedListener { - /** - * Callback triggered if the metadata of {@link BluetoothDevice} registered in - * {@link #addOnMetadataChangedListener}. - * - * @param device changed {@link BluetoothDevice}. - * @param key changed metadata key, one of BluetoothDevice.METADATA_*. - * @param value the new value of metadata as byte array. - */ - void onMetadataChanged(@NonNull BluetoothDevice device, int key, - @Nullable byte[] value); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothConnectionCallback mConnectionCallback = - new IBluetoothConnectionCallback.Stub() { - @Override - public void onDeviceConnected(BluetoothDevice device) { - Attributable.setAttributionSource(device, mAttributionSource); - for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry: - mBluetoothConnectionCallbackExecutorMap.entrySet()) { - BluetoothConnectionCallback callback = callbackExecutorEntry.getKey(); - Executor executor = callbackExecutorEntry.getValue(); - executor.execute(() -> callback.onDeviceConnected(device)); - } - } - - @Override - public void onDeviceDisconnected(BluetoothDevice device, int hciReason) { - Attributable.setAttributionSource(device, mAttributionSource); - for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry: - mBluetoothConnectionCallbackExecutorMap.entrySet()) { - BluetoothConnectionCallback callback = callbackExecutorEntry.getKey(); - Executor executor = callbackExecutorEntry.getValue(); - executor.execute(() -> callback.onDeviceDisconnected(device, hciReason)); - } - } - }; - - /** - * Registers the BluetoothConnectionCallback to receive callback events when a bluetooth device - * (classic or low energy) is connected or disconnected. - * - * @param executor is the callback executor - * @param callback is the connection callback you wish to register - * @return true if the callback was registered successfully, false otherwise - * @throws IllegalArgumentException if the callback is already registered - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean registerBluetoothConnectionCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull BluetoothConnectionCallback callback) { - if (DBG) Log.d(TAG, "registerBluetoothConnectionCallback()"); - if (callback == null) { - return false; - } - - synchronized (mBluetoothConnectionCallbackExecutorMap) { - // If the callback map is empty, we register the service-to-app callback - if (mBluetoothConnectionCallbackExecutorMap.isEmpty()) { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - if (!mService.registerBluetoothConnectionCallback(mConnectionCallback, - mAttributionSource)) { - return false; - } - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - mBluetoothConnectionCallbackExecutorMap.remove(callback); - } finally { - mServiceLock.readLock().unlock(); - } - } - - // Adds the passed in callback to our map of callbacks to executors - if (mBluetoothConnectionCallbackExecutorMap.containsKey(callback)) { - throw new IllegalArgumentException("This callback has already been registered"); - } - mBluetoothConnectionCallbackExecutorMap.put(callback, executor); - } - - return true; - } - - /** - * Unregisters the BluetoothConnectionCallback that was previously registered by the application - * - * @param callback is the connection callback you wish to unregister - * @return true if the callback was unregistered successfully, false otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean unregisterBluetoothConnectionCallback( - @NonNull BluetoothConnectionCallback callback) { - if (DBG) Log.d(TAG, "unregisterBluetoothConnectionCallback()"); - if (callback == null) { - return false; - } - - synchronized (mBluetoothConnectionCallbackExecutorMap) { - if (mBluetoothConnectionCallbackExecutorMap.remove(callback) != null) { - return false; - } - } - - if (!mBluetoothConnectionCallbackExecutorMap.isEmpty()) { - return true; - } - - // If the callback map is empty, we unregister the service-to-app callback - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.unregisterBluetoothConnectionCallback(mConnectionCallback, - mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - - return false; - } - - /** - * This abstract class is used to implement callbacks for when a bluetooth classic or Bluetooth - * Low Energy (BLE) device is either connected or disconnected. - * - * @hide - */ - public abstract static class BluetoothConnectionCallback { - /** - * Callback triggered when a bluetooth device (classic or BLE) is connected - * @param device is the connected bluetooth device - */ - public void onDeviceConnected(BluetoothDevice device) {} - - /** - * Callback triggered when a bluetooth device (classic or BLE) is disconnected - * @param device is the disconnected bluetooth device - * @param reason is the disconnect reason - */ - public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {} - - /** - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "REASON_" }, value = { - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_TIMEOUT, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SECURITY, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SYSTEM_POLICY, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS}) - public @interface DisconnectReason {} - - /** - * Returns human-readable strings corresponding to {@link DisconnectReason}. - */ - public static String disconnectReasonText(@DisconnectReason int reason) { - switch (reason) { - case BluetoothStatusCodes.ERROR_UNKNOWN: - return "Reason unknown"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST: - return "Local request"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST: - return "Remote request"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL: - return "Local error"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE: - return "Remote error"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_TIMEOUT: - return "Timeout"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SECURITY: - return "Security"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SYSTEM_POLICY: - return "System policy"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED: - return "Resource constrained"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS: - return "Connection already exists"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS: - return "Bad parameters"; - default: - return "Unrecognized disconnect reason: " + reason; - } - } - } - - /** - * Converts old constant of priority to the new for connection policy - * - * @param priority is the priority to convert to connection policy - * @return the equivalent connection policy constant to the priority - * - * @hide - */ - public static @ConnectionPolicy int priorityToConnectionPolicy(int priority) { - switch(priority) { - case BluetoothProfile.PRIORITY_AUTO_CONNECT: - return BluetoothProfile.CONNECTION_POLICY_ALLOWED; - case BluetoothProfile.PRIORITY_ON: - return BluetoothProfile.CONNECTION_POLICY_ALLOWED; - case BluetoothProfile.PRIORITY_OFF: - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - case BluetoothProfile.PRIORITY_UNDEFINED: - return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; - default: - Log.e(TAG, "setPriority: Invalid priority: " + priority); - return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; - } - } - - /** - * Converts new constant of connection policy to the old for priority - * - * @param connectionPolicy is the connection policy to convert to priority - * @return the equivalent priority constant to the connectionPolicy - * - * @hide - */ - public static int connectionPolicyToPriority(@ConnectionPolicy int connectionPolicy) { - switch(connectionPolicy) { - case BluetoothProfile.CONNECTION_POLICY_ALLOWED: - return BluetoothProfile.PRIORITY_ON; - case BluetoothProfile.CONNECTION_POLICY_FORBIDDEN: - return BluetoothProfile.PRIORITY_OFF; - case BluetoothProfile.CONNECTION_POLICY_UNKNOWN: - return BluetoothProfile.PRIORITY_UNDEFINED; - } - return BluetoothProfile.PRIORITY_UNDEFINED; - } -} diff --git a/core/java/android/bluetooth/BluetoothAssignedNumbers.java b/core/java/android/bluetooth/BluetoothAssignedNumbers.java deleted file mode 100644 index 41a34e061845..000000000000 --- a/core/java/android/bluetooth/BluetoothAssignedNumbers.java +++ /dev/null @@ -1,1171 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -/** - * Bluetooth Assigned Numbers. - * <p> - * For now we only include Company ID values. - * - * @see <a href="https://www.bluetooth.org/technical/assignednumbers/identifiers.htm"> The Official - * Bluetooth SIG Member Website | Company Identifiers</a> - */ -public class BluetoothAssignedNumbers { - - // Bluetooth SIG Company ID values - /* - * Ericsson Technology Licensing. - */ - public static final int ERICSSON_TECHNOLOGY = 0x0000; - - /* - * Nokia Mobile Phones. - */ - public static final int NOKIA_MOBILE_PHONES = 0x0001; - - /* - * Intel Corp. - */ - public static final int INTEL = 0x0002; - - /* - * IBM Corp. - */ - public static final int IBM = 0x0003; - - /* - * Toshiba Corp. - */ - public static final int TOSHIBA = 0x0004; - - /* - * 3Com. - */ - public static final int THREECOM = 0x0005; - - /* - * Microsoft. - */ - public static final int MICROSOFT = 0x0006; - - /* - * Lucent. - */ - public static final int LUCENT = 0x0007; - - /* - * Motorola. - */ - public static final int MOTOROLA = 0x0008; - - /* - * Infineon Technologies AG. - */ - public static final int INFINEON_TECHNOLOGIES = 0x0009; - - /* - * Cambridge Silicon Radio. - */ - public static final int CAMBRIDGE_SILICON_RADIO = 0x000A; - - /* - * Silicon Wave. - */ - public static final int SILICON_WAVE = 0x000B; - - /* - * Digianswer A/S. - */ - public static final int DIGIANSWER = 0x000C; - - /* - * Texas Instruments Inc. - */ - public static final int TEXAS_INSTRUMENTS = 0x000D; - - /* - * Parthus Technologies Inc. - */ - public static final int PARTHUS_TECHNOLOGIES = 0x000E; - - /* - * Broadcom Corporation. - */ - public static final int BROADCOM = 0x000F; - - /* - * Mitel Semiconductor. - */ - public static final int MITEL_SEMICONDUCTOR = 0x0010; - - /* - * Widcomm, Inc. - */ - public static final int WIDCOMM = 0x0011; - - /* - * Zeevo, Inc. - */ - public static final int ZEEVO = 0x0012; - - /* - * Atmel Corporation. - */ - public static final int ATMEL = 0x0013; - - /* - * Mitsubishi Electric Corporation. - */ - public static final int MITSUBISHI_ELECTRIC = 0x0014; - - /* - * RTX Telecom A/S. - */ - public static final int RTX_TELECOM = 0x0015; - - /* - * KC Technology Inc. - */ - public static final int KC_TECHNOLOGY = 0x0016; - - /* - * Newlogic. - */ - public static final int NEWLOGIC = 0x0017; - - /* - * Transilica, Inc. - */ - public static final int TRANSILICA = 0x0018; - - /* - * Rohde & Schwarz GmbH & Co. KG. - */ - public static final int ROHDE_AND_SCHWARZ = 0x0019; - - /* - * TTPCom Limited. - */ - public static final int TTPCOM = 0x001A; - - /* - * Signia Technologies, Inc. - */ - public static final int SIGNIA_TECHNOLOGIES = 0x001B; - - /* - * Conexant Systems Inc. - */ - public static final int CONEXANT_SYSTEMS = 0x001C; - - /* - * Qualcomm. - */ - public static final int QUALCOMM = 0x001D; - - /* - * Inventel. - */ - public static final int INVENTEL = 0x001E; - - /* - * AVM Berlin. - */ - public static final int AVM_BERLIN = 0x001F; - - /* - * BandSpeed, Inc. - */ - public static final int BANDSPEED = 0x0020; - - /* - * Mansella Ltd. - */ - public static final int MANSELLA = 0x0021; - - /* - * NEC Corporation. - */ - public static final int NEC = 0x0022; - - /* - * WavePlus Technology Co., Ltd. - */ - public static final int WAVEPLUS_TECHNOLOGY = 0x0023; - - /* - * Alcatel. - */ - public static final int ALCATEL = 0x0024; - - /* - * Philips Semiconductors. - */ - public static final int PHILIPS_SEMICONDUCTORS = 0x0025; - - /* - * C Technologies. - */ - public static final int C_TECHNOLOGIES = 0x0026; - - /* - * Open Interface. - */ - public static final int OPEN_INTERFACE = 0x0027; - - /* - * R F Micro Devices. - */ - public static final int RF_MICRO_DEVICES = 0x0028; - - /* - * Hitachi Ltd. - */ - public static final int HITACHI = 0x0029; - - /* - * Symbol Technologies, Inc. - */ - public static final int SYMBOL_TECHNOLOGIES = 0x002A; - - /* - * Tenovis. - */ - public static final int TENOVIS = 0x002B; - - /* - * Macronix International Co. Ltd. - */ - public static final int MACRONIX = 0x002C; - - /* - * GCT Semiconductor. - */ - public static final int GCT_SEMICONDUCTOR = 0x002D; - - /* - * Norwood Systems. - */ - public static final int NORWOOD_SYSTEMS = 0x002E; - - /* - * MewTel Technology Inc. - */ - public static final int MEWTEL_TECHNOLOGY = 0x002F; - - /* - * ST Microelectronics. - */ - public static final int ST_MICROELECTRONICS = 0x0030; - - /* - * Synopsys. - */ - public static final int SYNOPSYS = 0x0031; - - /* - * Red-M (Communications) Ltd. - */ - public static final int RED_M = 0x0032; - - /* - * Commil Ltd. - */ - public static final int COMMIL = 0x0033; - - /* - * Computer Access Technology Corporation (CATC). - */ - public static final int CATC = 0x0034; - - /* - * Eclipse (HQ Espana) S.L. - */ - public static final int ECLIPSE = 0x0035; - - /* - * Renesas Technology Corp. - */ - public static final int RENESAS_TECHNOLOGY = 0x0036; - - /* - * Mobilian Corporation. - */ - public static final int MOBILIAN_CORPORATION = 0x0037; - - /* - * Terax. - */ - public static final int TERAX = 0x0038; - - /* - * Integrated System Solution Corp. - */ - public static final int INTEGRATED_SYSTEM_SOLUTION = 0x0039; - - /* - * Matsushita Electric Industrial Co., Ltd. - */ - public static final int MATSUSHITA_ELECTRIC = 0x003A; - - /* - * Gennum Corporation. - */ - public static final int GENNUM = 0x003B; - - /* - * Research In Motion. - */ - public static final int RESEARCH_IN_MOTION = 0x003C; - - /* - * IPextreme, Inc. - */ - public static final int IPEXTREME = 0x003D; - - /* - * Systems and Chips, Inc. - */ - public static final int SYSTEMS_AND_CHIPS = 0x003E; - - /* - * Bluetooth SIG, Inc. - */ - public static final int BLUETOOTH_SIG = 0x003F; - - /* - * Seiko Epson Corporation. - */ - public static final int SEIKO_EPSON = 0x0040; - - /* - * Integrated Silicon Solution Taiwan, Inc. - */ - public static final int INTEGRATED_SILICON_SOLUTION = 0x0041; - - /* - * CONWISE Technology Corporation Ltd. - */ - public static final int CONWISE_TECHNOLOGY = 0x0042; - - /* - * PARROT SA. - */ - public static final int PARROT = 0x0043; - - /* - * Socket Mobile. - */ - public static final int SOCKET_MOBILE = 0x0044; - - /* - * Atheros Communications, Inc. - */ - public static final int ATHEROS_COMMUNICATIONS = 0x0045; - - /* - * MediaTek, Inc. - */ - public static final int MEDIATEK = 0x0046; - - /* - * Bluegiga. - */ - public static final int BLUEGIGA = 0x0047; - - /* - * Marvell Technology Group Ltd. - */ - public static final int MARVELL = 0x0048; - - /* - * 3DSP Corporation. - */ - public static final int THREE_DSP = 0x0049; - - /* - * Accel Semiconductor Ltd. - */ - public static final int ACCEL_SEMICONDUCTOR = 0x004A; - - /* - * Continental Automotive Systems. - */ - public static final int CONTINENTAL_AUTOMOTIVE = 0x004B; - - /* - * Apple, Inc. - */ - public static final int APPLE = 0x004C; - - /* - * Staccato Communications, Inc. - */ - public static final int STACCATO_COMMUNICATIONS = 0x004D; - - /* - * Avago Technologies. - */ - public static final int AVAGO = 0x004E; - - /* - * APT Licensing Ltd. - */ - public static final int APT_LICENSING = 0x004F; - - /* - * SiRF Technology, Inc. - */ - public static final int SIRF_TECHNOLOGY = 0x0050; - - /* - * Tzero Technologies, Inc. - */ - public static final int TZERO_TECHNOLOGIES = 0x0051; - - /* - * J&M Corporation. - */ - public static final int J_AND_M = 0x0052; - - /* - * Free2move AB. - */ - public static final int FREE2MOVE = 0x0053; - - /* - * 3DiJoy Corporation. - */ - public static final int THREE_DIJOY = 0x0054; - - /* - * Plantronics, Inc. - */ - public static final int PLANTRONICS = 0x0055; - - /* - * Sony Ericsson Mobile Communications. - */ - public static final int SONY_ERICSSON = 0x0056; - - /* - * Harman International Industries, Inc. - */ - public static final int HARMAN_INTERNATIONAL = 0x0057; - - /* - * Vizio, Inc. - */ - public static final int VIZIO = 0x0058; - - /* - * Nordic Semiconductor ASA. - */ - public static final int NORDIC_SEMICONDUCTOR = 0x0059; - - /* - * EM Microelectronic-Marin SA. - */ - public static final int EM_MICROELECTRONIC_MARIN = 0x005A; - - /* - * Ralink Technology Corporation. - */ - public static final int RALINK_TECHNOLOGY = 0x005B; - - /* - * Belkin International, Inc. - */ - public static final int BELKIN_INTERNATIONAL = 0x005C; - - /* - * Realtek Semiconductor Corporation. - */ - public static final int REALTEK_SEMICONDUCTOR = 0x005D; - - /* - * Stonestreet One, LLC. - */ - public static final int STONESTREET_ONE = 0x005E; - - /* - * Wicentric, Inc. - */ - public static final int WICENTRIC = 0x005F; - - /* - * RivieraWaves S.A.S. - */ - public static final int RIVIERAWAVES = 0x0060; - - /* - * RDA Microelectronics. - */ - public static final int RDA_MICROELECTRONICS = 0x0061; - - /* - * Gibson Guitars. - */ - public static final int GIBSON_GUITARS = 0x0062; - - /* - * MiCommand Inc. - */ - public static final int MICOMMAND = 0x0063; - - /* - * Band XI International, LLC. - */ - public static final int BAND_XI_INTERNATIONAL = 0x0064; - - /* - * Hewlett-Packard Company. - */ - public static final int HEWLETT_PACKARD = 0x0065; - - /* - * 9Solutions Oy. - */ - public static final int NINE_SOLUTIONS = 0x0066; - - /* - * GN Netcom A/S. - */ - public static final int GN_NETCOM = 0x0067; - - /* - * General Motors. - */ - public static final int GENERAL_MOTORS = 0x0068; - - /* - * A&D Engineering, Inc. - */ - public static final int A_AND_D_ENGINEERING = 0x0069; - - /* - * MindTree Ltd. - */ - public static final int MINDTREE = 0x006A; - - /* - * Polar Electro OY. - */ - public static final int POLAR_ELECTRO = 0x006B; - - /* - * Beautiful Enterprise Co., Ltd. - */ - public static final int BEAUTIFUL_ENTERPRISE = 0x006C; - - /* - * BriarTek, Inc. - */ - public static final int BRIARTEK = 0x006D; - - /* - * Summit Data Communications, Inc. - */ - public static final int SUMMIT_DATA_COMMUNICATIONS = 0x006E; - - /* - * Sound ID. - */ - public static final int SOUND_ID = 0x006F; - - /* - * Monster, LLC. - */ - public static final int MONSTER = 0x0070; - - /* - * connectBlue AB. - */ - public static final int CONNECTBLUE = 0x0071; - - /* - * ShangHai Super Smart Electronics Co. Ltd. - */ - public static final int SHANGHAI_SUPER_SMART_ELECTRONICS = 0x0072; - - /* - * Group Sense Ltd. - */ - public static final int GROUP_SENSE = 0x0073; - - /* - * Zomm, LLC. - */ - public static final int ZOMM = 0x0074; - - /* - * Samsung Electronics Co. Ltd. - */ - public static final int SAMSUNG_ELECTRONICS = 0x0075; - - /* - * Creative Technology Ltd. - */ - public static final int CREATIVE_TECHNOLOGY = 0x0076; - - /* - * Laird Technologies. - */ - public static final int LAIRD_TECHNOLOGIES = 0x0077; - - /* - * Nike, Inc. - */ - public static final int NIKE = 0x0078; - - /* - * lesswire AG. - */ - public static final int LESSWIRE = 0x0079; - - /* - * MStar Semiconductor, Inc. - */ - public static final int MSTAR_SEMICONDUCTOR = 0x007A; - - /* - * Hanlynn Technologies. - */ - public static final int HANLYNN_TECHNOLOGIES = 0x007B; - - /* - * A & R Cambridge. - */ - public static final int A_AND_R_CAMBRIDGE = 0x007C; - - /* - * Seers Technology Co. Ltd. - */ - public static final int SEERS_TECHNOLOGY = 0x007D; - - /* - * Sports Tracking Technologies Ltd. - */ - public static final int SPORTS_TRACKING_TECHNOLOGIES = 0x007E; - - /* - * Autonet Mobile. - */ - public static final int AUTONET_MOBILE = 0x007F; - - /* - * DeLorme Publishing Company, Inc. - */ - public static final int DELORME_PUBLISHING_COMPANY = 0x0080; - - /* - * WuXi Vimicro. - */ - public static final int WUXI_VIMICRO = 0x0081; - - /* - * Sennheiser Communications A/S. - */ - public static final int SENNHEISER_COMMUNICATIONS = 0x0082; - - /* - * TimeKeeping Systems, Inc. - */ - public static final int TIMEKEEPING_SYSTEMS = 0x0083; - - /* - * Ludus Helsinki Ltd. - */ - public static final int LUDUS_HELSINKI = 0x0084; - - /* - * BlueRadios, Inc. - */ - public static final int BLUERADIOS = 0x0085; - - /* - * equinox AG. - */ - public static final int EQUINOX_AG = 0x0086; - - /* - * Garmin International, Inc. - */ - public static final int GARMIN_INTERNATIONAL = 0x0087; - - /* - * Ecotest. - */ - public static final int ECOTEST = 0x0088; - - /* - * GN ReSound A/S. - */ - public static final int GN_RESOUND = 0x0089; - - /* - * Jawbone. - */ - public static final int JAWBONE = 0x008A; - - /* - * Topcorn Positioning Systems, LLC. - */ - public static final int TOPCORN_POSITIONING_SYSTEMS = 0x008B; - - /* - * Qualcomm Labs, Inc. - */ - public static final int QUALCOMM_LABS = 0x008C; - - /* - * Zscan Software. - */ - public static final int ZSCAN_SOFTWARE = 0x008D; - - /* - * Quintic Corp. - */ - public static final int QUINTIC = 0x008E; - - /* - * Stollman E+V GmbH. - */ - public static final int STOLLMAN_E_PLUS_V = 0x008F; - - /* - * Funai Electric Co., Ltd. - */ - public static final int FUNAI_ELECTRIC = 0x0090; - - /* - * Advanced PANMOBIL Systems GmbH & Co. KG. - */ - public static final int ADVANCED_PANMOBIL_SYSTEMS = 0x0091; - - /* - * ThinkOptics, Inc. - */ - public static final int THINKOPTICS = 0x0092; - - /* - * Universal Electronics, Inc. - */ - public static final int UNIVERSAL_ELECTRONICS = 0x0093; - - /* - * Airoha Technology Corp. - */ - public static final int AIROHA_TECHNOLOGY = 0x0094; - - /* - * NEC Lighting, Ltd. - */ - public static final int NEC_LIGHTING = 0x0095; - - /* - * ODM Technology, Inc. - */ - public static final int ODM_TECHNOLOGY = 0x0096; - - /* - * Bluetrek Technologies Limited. - */ - public static final int BLUETREK_TECHNOLOGIES = 0x0097; - - /* - * zer01.tv GmbH. - */ - public static final int ZER01_TV = 0x0098; - - /* - * i.Tech Dynamic Global Distribution Ltd. - */ - public static final int I_TECH_DYNAMIC_GLOBAL_DISTRIBUTION = 0x0099; - - /* - * Alpwise. - */ - public static final int ALPWISE = 0x009A; - - /* - * Jiangsu Toppower Automotive Electronics Co., Ltd. - */ - public static final int JIANGSU_TOPPOWER_AUTOMOTIVE_ELECTRONICS = 0x009B; - - /* - * Colorfy, Inc. - */ - public static final int COLORFY = 0x009C; - - /* - * Geoforce Inc. - */ - public static final int GEOFORCE = 0x009D; - - /* - * Bose Corporation. - */ - public static final int BOSE = 0x009E; - - /* - * Suunto Oy. - */ - public static final int SUUNTO = 0x009F; - - /* - * Kensington Computer Products Group. - */ - public static final int KENSINGTON_COMPUTER_PRODUCTS_GROUP = 0x00A0; - - /* - * SR-Medizinelektronik. - */ - public static final int SR_MEDIZINELEKTRONIK = 0x00A1; - - /* - * Vertu Corporation Limited. - */ - public static final int VERTU = 0x00A2; - - /* - * Meta Watch Ltd. - */ - public static final int META_WATCH = 0x00A3; - - /* - * LINAK A/S. - */ - public static final int LINAK = 0x00A4; - - /* - * OTL Dynamics LLC. - */ - public static final int OTL_DYNAMICS = 0x00A5; - - /* - * Panda Ocean Inc. - */ - public static final int PANDA_OCEAN = 0x00A6; - - /* - * Visteon Corporation. - */ - public static final int VISTEON = 0x00A7; - - /* - * ARP Devices Limited. - */ - public static final int ARP_DEVICES = 0x00A8; - - /* - * Magneti Marelli S.p.A. - */ - public static final int MAGNETI_MARELLI = 0x00A9; - - /* - * CAEN RFID srl. - */ - public static final int CAEN_RFID = 0x00AA; - - /* - * Ingenieur-Systemgruppe Zahn GmbH. - */ - public static final int INGENIEUR_SYSTEMGRUPPE_ZAHN = 0x00AB; - - /* - * Green Throttle Games. - */ - public static final int GREEN_THROTTLE_GAMES = 0x00AC; - - /* - * Peter Systemtechnik GmbH. - */ - public static final int PETER_SYSTEMTECHNIK = 0x00AD; - - /* - * Omegawave Oy. - */ - public static final int OMEGAWAVE = 0x00AE; - - /* - * Cinetix. - */ - public static final int CINETIX = 0x00AF; - - /* - * Passif Semiconductor Corp. - */ - public static final int PASSIF_SEMICONDUCTOR = 0x00B0; - - /* - * Saris Cycling Group, Inc. - */ - public static final int SARIS_CYCLING_GROUP = 0x00B1; - - /* - * Bekey A/S. - */ - public static final int BEKEY = 0x00B2; - - /* - * Clarinox Technologies Pty. Ltd. - */ - public static final int CLARINOX_TECHNOLOGIES = 0x00B3; - - /* - * BDE Technology Co., Ltd. - */ - public static final int BDE_TECHNOLOGY = 0x00B4; - - /* - * Swirl Networks. - */ - public static final int SWIRL_NETWORKS = 0x00B5; - - /* - * Meso international. - */ - public static final int MESO_INTERNATIONAL = 0x00B6; - - /* - * TreLab Ltd. - */ - public static final int TRELAB = 0x00B7; - - /* - * Qualcomm Innovation Center, Inc. (QuIC). - */ - public static final int QUALCOMM_INNOVATION_CENTER = 0x00B8; - - /* - * Johnson Controls, Inc. - */ - public static final int JOHNSON_CONTROLS = 0x00B9; - - /* - * Starkey Laboratories Inc. - */ - public static final int STARKEY_LABORATORIES = 0x00BA; - - /* - * S-Power Electronics Limited. - */ - public static final int S_POWER_ELECTRONICS = 0x00BB; - - /* - * Ace Sensor Inc. - */ - public static final int ACE_SENSOR = 0x00BC; - - /* - * Aplix Corporation. - */ - public static final int APLIX = 0x00BD; - - /* - * AAMP of America. - */ - public static final int AAMP_OF_AMERICA = 0x00BE; - - /* - * Stalmart Technology Limited. - */ - public static final int STALMART_TECHNOLOGY = 0x00BF; - - /* - * AMICCOM Electronics Corporation. - */ - public static final int AMICCOM_ELECTRONICS = 0x00C0; - - /* - * Shenzhen Excelsecu Data Technology Co.,Ltd. - */ - public static final int SHENZHEN_EXCELSECU_DATA_TECHNOLOGY = 0x00C1; - - /* - * Geneq Inc. - */ - public static final int GENEQ = 0x00C2; - - /* - * adidas AG. - */ - public static final int ADIDAS = 0x00C3; - - /* - * LG Electronics. - */ - public static final int LG_ELECTRONICS = 0x00C4; - - /* - * Onset Computer Corporation. - */ - public static final int ONSET_COMPUTER = 0x00C5; - - /* - * Selfly BV. - */ - public static final int SELFLY = 0x00C6; - - /* - * Quuppa Oy. - */ - public static final int QUUPPA = 0x00C7; - - /* - * GeLo Inc. - */ - public static final int GELO = 0x00C8; - - /* - * Evluma. - */ - public static final int EVLUMA = 0x00C9; - - /* - * MC10. - */ - public static final int MC10 = 0x00CA; - - /* - * Binauric SE. - */ - public static final int BINAURIC = 0x00CB; - - /* - * Beats Electronics. - */ - public static final int BEATS_ELECTRONICS = 0x00CC; - - /* - * Microchip Technology Inc. - */ - public static final int MICROCHIP_TECHNOLOGY = 0x00CD; - - /* - * Elgato Systems GmbH. - */ - public static final int ELGATO_SYSTEMS = 0x00CE; - - /* - * ARCHOS SA. - */ - public static final int ARCHOS = 0x00CF; - - /* - * Dexcom, Inc. - */ - public static final int DEXCOM = 0x00D0; - - /* - * Polar Electro Europe B.V. - */ - public static final int POLAR_ELECTRO_EUROPE = 0x00D1; - - /* - * Dialog Semiconductor B.V. - */ - public static final int DIALOG_SEMICONDUCTOR = 0x00D2; - - /* - * Taixingbang Technology (HK) Co,. LTD. - */ - public static final int TAIXINGBANG_TECHNOLOGY = 0x00D3; - - /* - * Kawantech. - */ - public static final int KAWANTECH = 0x00D4; - - /* - * Austco Communication Systems. - */ - public static final int AUSTCO_COMMUNICATION_SYSTEMS = 0x00D5; - - /* - * Timex Group USA, Inc. - */ - public static final int TIMEX_GROUP_USA = 0x00D6; - - /* - * Qualcomm Technologies, Inc. - */ - public static final int QUALCOMM_TECHNOLOGIES = 0x00D7; - - /* - * Qualcomm Connected Experiences, Inc. - */ - public static final int QUALCOMM_CONNECTED_EXPERIENCES = 0x00D8; - - /* - * Voyetra Turtle Beach. - */ - public static final int VOYETRA_TURTLE_BEACH = 0x00D9; - - /* - * txtr GmbH. - */ - public static final int TXTR = 0x00DA; - - /* - * Biosentronics. - */ - public static final int BIOSENTRONICS = 0x00DB; - - /* - * Procter & Gamble. - */ - public static final int PROCTER_AND_GAMBLE = 0x00DC; - - /* - * Hosiden Corporation. - */ - public static final int HOSIDEN = 0x00DD; - - /* - * Muzik LLC. - */ - public static final int MUZIK = 0x00DE; - - /* - * Misfit Wearables Corp. - */ - public static final int MISFIT_WEARABLES = 0x00DF; - - /* - * Google. - */ - public static final int GOOGLE = 0x00E0; - - /* - * Danlers Ltd. - */ - public static final int DANLERS = 0x00E1; - - /* - * Semilink Inc. - */ - public static final int SEMILINK = 0x00E2; - - /* - * You can't instantiate one of these. - */ - private BluetoothAssignedNumbers() { - } - -} diff --git a/core/java/android/bluetooth/BluetoothAudioConfig.java b/core/java/android/bluetooth/BluetoothAudioConfig.java deleted file mode 100644 index 4c8b8c11fbc2..000000000000 --- a/core/java/android/bluetooth/BluetoothAudioConfig.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Represents the audio configuration for a Bluetooth A2DP source device. - * - * {@see BluetoothA2dpSink} - * - * {@hide} - */ -public final class BluetoothAudioConfig implements Parcelable { - - private final int mSampleRate; - private final int mChannelConfig; - private final int mAudioFormat; - - public BluetoothAudioConfig(int sampleRate, int channelConfig, int audioFormat) { - mSampleRate = sampleRate; - mChannelConfig = channelConfig; - mAudioFormat = audioFormat; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothAudioConfig) { - BluetoothAudioConfig bac = (BluetoothAudioConfig) o; - return (bac.mSampleRate == mSampleRate && bac.mChannelConfig == mChannelConfig - && bac.mAudioFormat == mAudioFormat); - } - return false; - } - - @Override - public int hashCode() { - return mSampleRate | (mChannelConfig << 24) | (mAudioFormat << 28); - } - - @Override - public String toString() { - return "{mSampleRate:" + mSampleRate + ",mChannelConfig:" + mChannelConfig - + ",mAudioFormat:" + mAudioFormat + "}"; - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothAudioConfig> CREATOR = - new Parcelable.Creator<BluetoothAudioConfig>() { - public BluetoothAudioConfig createFromParcel(Parcel in) { - int sampleRate = in.readInt(); - int channelConfig = in.readInt(); - int audioFormat = in.readInt(); - return new BluetoothAudioConfig(sampleRate, channelConfig, audioFormat); - } - - public BluetoothAudioConfig[] newArray(int size) { - return new BluetoothAudioConfig[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mSampleRate); - out.writeInt(mChannelConfig); - out.writeInt(mAudioFormat); - } - - /** - * Returns the sample rate in samples per second - * - * @return sample rate - */ - public int getSampleRate() { - return mSampleRate; - } - - /** - * Returns the channel configuration (either {@link android.media.AudioFormat#CHANNEL_IN_MONO} - * or {@link android.media.AudioFormat#CHANNEL_IN_STEREO}) - * - * @return channel configuration - */ - public int getChannelConfig() { - return mChannelConfig; - } - - /** - * Returns the channel audio format (either {@link android.media.AudioFormat#ENCODING_PCM_16BIT} - * or {@link android.media.AudioFormat#ENCODING_PCM_8BIT} - * - * @return audio format - */ - public int getAudioFormat() { - return mAudioFormat; - } -} diff --git a/core/java/android/bluetooth/BluetoothAvrcp.java b/core/java/android/bluetooth/BluetoothAvrcp.java deleted file mode 100644 index 1a4c75906482..000000000000 --- a/core/java/android/bluetooth/BluetoothAvrcp.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -/** - * This class contains constants for Bluetooth AVRCP profile. - * - * {@hide} - */ -public final class BluetoothAvrcp { - - /* - * State flags for Passthrough commands - */ - public static final int PASSTHROUGH_STATE_PRESS = 0; - public static final int PASSTHROUGH_STATE_RELEASE = 1; - - /* - * Operation IDs for Passthrough commands - */ - public static final int PASSTHROUGH_ID_SELECT = 0x00; /* select */ - public static final int PASSTHROUGH_ID_UP = 0x01; /* up */ - public static final int PASSTHROUGH_ID_DOWN = 0x02; /* down */ - public static final int PASSTHROUGH_ID_LEFT = 0x03; /* left */ - public static final int PASSTHROUGH_ID_RIGHT = 0x04; /* right */ - public static final int PASSTHROUGH_ID_RIGHT_UP = 0x05; /* right-up */ - public static final int PASSTHROUGH_ID_RIGHT_DOWN = 0x06; /* right-down */ - public static final int PASSTHROUGH_ID_LEFT_UP = 0x07; /* left-up */ - public static final int PASSTHROUGH_ID_LEFT_DOWN = 0x08; /* left-down */ - public static final int PASSTHROUGH_ID_ROOT_MENU = 0x09; /* root menu */ - public static final int PASSTHROUGH_ID_SETUP_MENU = 0x0A; /* setup menu */ - public static final int PASSTHROUGH_ID_CONT_MENU = 0x0B; /* contents menu */ - public static final int PASSTHROUGH_ID_FAV_MENU = 0x0C; /* favorite menu */ - public static final int PASSTHROUGH_ID_EXIT = 0x0D; /* exit */ - public static final int PASSTHROUGH_ID_0 = 0x20; /* 0 */ - public static final int PASSTHROUGH_ID_1 = 0x21; /* 1 */ - public static final int PASSTHROUGH_ID_2 = 0x22; /* 2 */ - public static final int PASSTHROUGH_ID_3 = 0x23; /* 3 */ - public static final int PASSTHROUGH_ID_4 = 0x24; /* 4 */ - public static final int PASSTHROUGH_ID_5 = 0x25; /* 5 */ - public static final int PASSTHROUGH_ID_6 = 0x26; /* 6 */ - public static final int PASSTHROUGH_ID_7 = 0x27; /* 7 */ - public static final int PASSTHROUGH_ID_8 = 0x28; /* 8 */ - public static final int PASSTHROUGH_ID_9 = 0x29; /* 9 */ - public static final int PASSTHROUGH_ID_DOT = 0x2A; /* dot */ - public static final int PASSTHROUGH_ID_ENTER = 0x2B; /* enter */ - public static final int PASSTHROUGH_ID_CLEAR = 0x2C; /* clear */ - public static final int PASSTHROUGH_ID_CHAN_UP = 0x30; /* channel up */ - public static final int PASSTHROUGH_ID_CHAN_DOWN = 0x31; /* channel down */ - public static final int PASSTHROUGH_ID_PREV_CHAN = 0x32; /* previous channel */ - public static final int PASSTHROUGH_ID_SOUND_SEL = 0x33; /* sound select */ - public static final int PASSTHROUGH_ID_INPUT_SEL = 0x34; /* input select */ - public static final int PASSTHROUGH_ID_DISP_INFO = 0x35; /* display information */ - public static final int PASSTHROUGH_ID_HELP = 0x36; /* help */ - public static final int PASSTHROUGH_ID_PAGE_UP = 0x37; /* page up */ - public static final int PASSTHROUGH_ID_PAGE_DOWN = 0x38; /* page down */ - public static final int PASSTHROUGH_ID_POWER = 0x40; /* power */ - public static final int PASSTHROUGH_ID_VOL_UP = 0x41; /* volume up */ - public static final int PASSTHROUGH_ID_VOL_DOWN = 0x42; /* volume down */ - public static final int PASSTHROUGH_ID_MUTE = 0x43; /* mute */ - public static final int PASSTHROUGH_ID_PLAY = 0x44; /* play */ - public static final int PASSTHROUGH_ID_STOP = 0x45; /* stop */ - public static final int PASSTHROUGH_ID_PAUSE = 0x46; /* pause */ - public static final int PASSTHROUGH_ID_RECORD = 0x47; /* record */ - public static final int PASSTHROUGH_ID_REWIND = 0x48; /* rewind */ - public static final int PASSTHROUGH_ID_FAST_FOR = 0x49; /* fast forward */ - public static final int PASSTHROUGH_ID_EJECT = 0x4A; /* eject */ - public static final int PASSTHROUGH_ID_FORWARD = 0x4B; /* forward */ - public static final int PASSTHROUGH_ID_BACKWARD = 0x4C; /* backward */ - public static final int PASSTHROUGH_ID_ANGLE = 0x50; /* angle */ - public static final int PASSTHROUGH_ID_SUBPICT = 0x51; /* subpicture */ - public static final int PASSTHROUGH_ID_F1 = 0x71; /* F1 */ - public static final int PASSTHROUGH_ID_F2 = 0x72; /* F2 */ - public static final int PASSTHROUGH_ID_F3 = 0x73; /* F3 */ - public static final int PASSTHROUGH_ID_F4 = 0x74; /* F4 */ - public static final int PASSTHROUGH_ID_F5 = 0x75; /* F5 */ - public static final int PASSTHROUGH_ID_VENDOR = 0x7E; /* vendor unique */ - public static final int PASSTHROUGH_KEYPRESSED_RELEASE = 0x80; -} diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java deleted file mode 100644 index 81fc3e11e9e1..000000000000 --- a/core/java/android/bluetooth/BluetoothAvrcpController.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently - * supports player information, playback support and track metadata. - * - * <p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothAvrcpController proxy object. - * - * {@hide} - */ -public final class BluetoothAvrcpController implements BluetoothProfile { - private static final String TAG = "BluetoothAvrcpController"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the AVRCP Controller - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the change in player application setting state on AVRCP AG. - * - * <p>This intent will have the following extras: - * <ul> - * <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the - * most recent player setting. </li> - * </ul> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PLAYER_SETTING = - "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING"; - - public static final String EXTRA_PLAYER_SETTING = - "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING"; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothAvrcpController> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.AVRCP_CONTROLLER, - "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) { - @Override - public IBluetoothAvrcpController getServiceInterface(IBinder service) { - return IBluetoothAvrcpController.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothAvrcpController proxy object for interacting with the local - * Bluetooth AVRCP service. - */ - /* package */ BluetoothAvrcpController(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /*package*/ void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothAvrcpController getService() { - return mProfileConnector.getService(); - } - - @Override - public void finalize() { - close(); - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothAvrcpController service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothAvrcpController service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothAvrcpController service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Gets the player application settings. - * - * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { - if (DBG) Log.d(TAG, "getPlayerSettings"); - BluetoothAvrcpPlayerSettings settings = null; - final IBluetoothAvrcpController service = getService(); - final BluetoothAvrcpPlayerSettings defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv = - new SynchronousResultReceiver(); - service.getPlayerSettings(device, mAttributionSource, recv); - settings = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sets the player app setting for current player. - * returns true in case setting is supported by remote, false otherwise - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { - if (DBG) Log.d(TAG, "setPlayerApplicationSetting"); - final IBluetoothAvrcpController service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Group Navigation Command to Remote. - * possible keycode values: next_grp, previous_grp defined above - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { - Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " - + keyState); - final IBluetoothAvrcpController service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - return; - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java deleted file mode 100644 index 30aea1abf73c..000000000000 --- a/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.util.HashMap; -import java.util.Map; - -/** - * Class used to identify settings associated with the player on AG. - * - * {@hide} - */ -public final class BluetoothAvrcpPlayerSettings implements Parcelable { - public static final String TAG = "BluetoothAvrcpPlayerSettings"; - - /** - * Equalizer setting. - */ - public static final int SETTING_EQUALIZER = 0x01; - - /** - * Repeat setting. - */ - public static final int SETTING_REPEAT = 0x02; - - /** - * Shuffle setting. - */ - public static final int SETTING_SHUFFLE = 0x04; - - /** - * Scan mode setting. - */ - public static final int SETTING_SCAN = 0x08; - - /** - * Invalid state. - * - * Used for returning error codes. - */ - public static final int STATE_INVALID = -1; - - /** - * OFF state. - * - * Denotes a general OFF state. Applies to all settings. - */ - public static final int STATE_OFF = 0x00; - - /** - * ON state. - * - * Applies to {@link SETTING_EQUALIZER}. - */ - public static final int STATE_ON = 0x01; - - /** - * Single track repeat. - * - * Applies only to {@link SETTING_REPEAT}. - */ - public static final int STATE_SINGLE_TRACK = 0x02; - - /** - * All track repeat/shuffle. - * - * Applies to {@link #SETTING_REPEAT}, {@link #SETTING_SHUFFLE} and {@link #SETTING_SCAN}. - */ - public static final int STATE_ALL_TRACK = 0x03; - - /** - * Group repeat/shuffle. - * - * Applies to {@link #SETTING_REPEAT}, {@link #SETTING_SHUFFLE} and {@link #SETTING_SCAN}. - */ - public static final int STATE_GROUP = 0x04; - - /** - * List of supported settings ORed. - */ - private int mSettings; - - /** - * Hash map of current capability values. - */ - private Map<Integer, Integer> mSettingsValue = new HashMap<Integer, Integer>(); - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mSettings); - out.writeInt(mSettingsValue.size()); - for (int k : mSettingsValue.keySet()) { - out.writeInt(k); - out.writeInt(mSettingsValue.get(k)); - } - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothAvrcpPlayerSettings> CREATOR = - new Parcelable.Creator<BluetoothAvrcpPlayerSettings>() { - public BluetoothAvrcpPlayerSettings createFromParcel(Parcel in) { - return new BluetoothAvrcpPlayerSettings(in); - } - - public BluetoothAvrcpPlayerSettings[] newArray(int size) { - return new BluetoothAvrcpPlayerSettings[size]; - } - }; - - private BluetoothAvrcpPlayerSettings(Parcel in) { - mSettings = in.readInt(); - int numSettings = in.readInt(); - for (int i = 0; i < numSettings; i++) { - mSettingsValue.put(in.readInt(), in.readInt()); - } - } - - /** - * Create a new player settings object. - * - * @param settings a ORed value of SETTINGS_* defined above. - */ - public BluetoothAvrcpPlayerSettings(int settings) { - mSettings = settings; - } - - /** - * Get the supported settings. - * - * @return int ORed value of supported settings. - */ - public int getSettings() { - return mSettings; - } - - /** - * Add a setting value. - * - * The setting must be part of possible settings in {@link getSettings()}. - * - * @param setting setting config. - * @param value value for the setting. - * @throws IllegalStateException if the setting is not supported. - */ - public void addSettingValue(int setting, int value) { - if ((setting & mSettings) == 0) { - Log.e(TAG, "Setting not supported: " + setting + " " + mSettings); - throw new IllegalStateException("Setting not supported: " + setting); - } - mSettingsValue.put(setting, value); - } - - /** - * Get a setting value. - * - * The setting must be part of possible settings in {@link getSettings()}. - * - * @param setting setting config. - * @return value value for the setting. - * @throws IllegalStateException if the setting is not supported. - */ - public int getSettingValue(int setting) { - if ((setting & mSettings) == 0) { - Log.e(TAG, "Setting not supported: " + setting + " " + mSettings); - throw new IllegalStateException("Setting not supported: " + setting); - } - Integer i = mSettingsValue.get(setting); - if (i == null) return -1; - return i; - } -} diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java deleted file mode 100755 index a3c45d0276ca..000000000000 --- a/core/java/android/bluetooth/BluetoothClass.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.annotation.TestApi; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -/** - * Represents a Bluetooth class, which describes general characteristics - * and capabilities of a device. For example, a Bluetooth class will - * specify the general device type such as a phone, a computer, or - * headset, and whether it's capable of services such as audio or telephony. - * - * <p>Every Bluetooth class is composed of zero or more service classes, and - * exactly one device class. The device class is further broken down into major - * and minor device class components. - * - * <p>{@link BluetoothClass} is useful as a hint to roughly describe a device - * (for example to show an icon in the UI), but does not reliably describe which - * Bluetooth profiles or services are actually supported by a device. Accurate - * service discovery is done through SDP requests, which are automatically - * performed when creating an RFCOMM socket with {@link - * BluetoothDevice#createRfcommSocketToServiceRecord} and {@link - * BluetoothAdapter#listenUsingRfcommWithServiceRecord}</p> - * - * <p>Use {@link BluetoothDevice#getBluetoothClass} to retrieve the class for - * a remote device. - * - * <!-- - * The Bluetooth class is a 32 bit field. The format of these bits is defined at - * http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm - * (login required). This class contains that 32 bit field, and provides - * constants and methods to determine which Service Class(es) and Device Class - * are encoded in that field. - * --> - */ -public final class BluetoothClass implements Parcelable { - /** - * Legacy error value. Applications should use null instead. - * - * @hide - */ - public static final int ERROR = 0xFF000000; - - private final int mClass; - - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public BluetoothClass(int classInt) { - mClass = classInt; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothClass) { - return mClass == ((BluetoothClass) o).mClass; - } - return false; - } - - @Override - public int hashCode() { - return mClass; - } - - @Override - public String toString() { - return Integer.toHexString(mClass); - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothClass> CREATOR = - new Parcelable.Creator<BluetoothClass>() { - public BluetoothClass createFromParcel(Parcel in) { - return new BluetoothClass(in.readInt()); - } - - public BluetoothClass[] newArray(int size) { - return new BluetoothClass[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mClass); - } - - /** - * Defines all service class constants. - * <p>Each {@link BluetoothClass} encodes zero or more service classes. - */ - public static final class Service { - private static final int BITMASK = 0xFFE000; - - public static final int LIMITED_DISCOVERABILITY = 0x002000; - public static final int LE_AUDIO = 0x004000; - public static final int POSITIONING = 0x010000; - public static final int NETWORKING = 0x020000; - public static final int RENDER = 0x040000; - public static final int CAPTURE = 0x080000; - public static final int OBJECT_TRANSFER = 0x100000; - public static final int AUDIO = 0x200000; - public static final int TELEPHONY = 0x400000; - public static final int INFORMATION = 0x800000; - } - - /** - * Return true if the specified service class is supported by this - * {@link BluetoothClass}. - * <p>Valid service classes are the public constants in - * {@link BluetoothClass.Service}. For example, {@link - * BluetoothClass.Service#AUDIO}. - * - * @param service valid service class - * @return true if the service class is supported - */ - public boolean hasService(int service) { - return ((mClass & Service.BITMASK & service) != 0); - } - - /** - * Defines all device class constants. - * <p>Each {@link BluetoothClass} encodes exactly one device class, with - * major and minor components. - * <p>The constants in {@link - * BluetoothClass.Device} represent a combination of major and minor - * device components (the complete device class). The constants in {@link - * BluetoothClass.Device.Major} represent only major device classes. - * <p>See {@link BluetoothClass.Service} for service class constants. - */ - public static class Device { - private static final int BITMASK = 0x1FFC; - - /** - * Defines all major device class constants. - * <p>See {@link BluetoothClass.Device} for minor classes. - */ - public static class Major { - private static final int BITMASK = 0x1F00; - - public static final int MISC = 0x0000; - public static final int COMPUTER = 0x0100; - public static final int PHONE = 0x0200; - public static final int NETWORKING = 0x0300; - public static final int AUDIO_VIDEO = 0x0400; - public static final int PERIPHERAL = 0x0500; - public static final int IMAGING = 0x0600; - public static final int WEARABLE = 0x0700; - public static final int TOY = 0x0800; - public static final int HEALTH = 0x0900; - public static final int UNCATEGORIZED = 0x1F00; - } - - // Devices in the COMPUTER major class - public static final int COMPUTER_UNCATEGORIZED = 0x0100; - public static final int COMPUTER_DESKTOP = 0x0104; - public static final int COMPUTER_SERVER = 0x0108; - public static final int COMPUTER_LAPTOP = 0x010C; - public static final int COMPUTER_HANDHELD_PC_PDA = 0x0110; - public static final int COMPUTER_PALM_SIZE_PC_PDA = 0x0114; - public static final int COMPUTER_WEARABLE = 0x0118; - - // Devices in the PHONE major class - public static final int PHONE_UNCATEGORIZED = 0x0200; - public static final int PHONE_CELLULAR = 0x0204; - public static final int PHONE_CORDLESS = 0x0208; - public static final int PHONE_SMART = 0x020C; - public static final int PHONE_MODEM_OR_GATEWAY = 0x0210; - public static final int PHONE_ISDN = 0x0214; - - // Minor classes for the AUDIO_VIDEO major class - public static final int AUDIO_VIDEO_UNCATEGORIZED = 0x0400; - public static final int AUDIO_VIDEO_WEARABLE_HEADSET = 0x0404; - public static final int AUDIO_VIDEO_HANDSFREE = 0x0408; - //public static final int AUDIO_VIDEO_RESERVED = 0x040C; - public static final int AUDIO_VIDEO_MICROPHONE = 0x0410; - public static final int AUDIO_VIDEO_LOUDSPEAKER = 0x0414; - public static final int AUDIO_VIDEO_HEADPHONES = 0x0418; - public static final int AUDIO_VIDEO_PORTABLE_AUDIO = 0x041C; - public static final int AUDIO_VIDEO_CAR_AUDIO = 0x0420; - public static final int AUDIO_VIDEO_SET_TOP_BOX = 0x0424; - public static final int AUDIO_VIDEO_HIFI_AUDIO = 0x0428; - public static final int AUDIO_VIDEO_VCR = 0x042C; - public static final int AUDIO_VIDEO_VIDEO_CAMERA = 0x0430; - public static final int AUDIO_VIDEO_CAMCORDER = 0x0434; - public static final int AUDIO_VIDEO_VIDEO_MONITOR = 0x0438; - public static final int AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x043C; - public static final int AUDIO_VIDEO_VIDEO_CONFERENCING = 0x0440; - //public static final int AUDIO_VIDEO_RESERVED = 0x0444; - public static final int AUDIO_VIDEO_VIDEO_GAMING_TOY = 0x0448; - - // Devices in the WEARABLE major class - public static final int WEARABLE_UNCATEGORIZED = 0x0700; - public static final int WEARABLE_WRIST_WATCH = 0x0704; - public static final int WEARABLE_PAGER = 0x0708; - public static final int WEARABLE_JACKET = 0x070C; - public static final int WEARABLE_HELMET = 0x0710; - public static final int WEARABLE_GLASSES = 0x0714; - - // Devices in the TOY major class - public static final int TOY_UNCATEGORIZED = 0x0800; - public static final int TOY_ROBOT = 0x0804; - public static final int TOY_VEHICLE = 0x0808; - public static final int TOY_DOLL_ACTION_FIGURE = 0x080C; - public static final int TOY_CONTROLLER = 0x0810; - public static final int TOY_GAME = 0x0814; - - // Devices in the HEALTH major class - public static final int HEALTH_UNCATEGORIZED = 0x0900; - public static final int HEALTH_BLOOD_PRESSURE = 0x0904; - public static final int HEALTH_THERMOMETER = 0x0908; - public static final int HEALTH_WEIGHING = 0x090C; - public static final int HEALTH_GLUCOSE = 0x0910; - public static final int HEALTH_PULSE_OXIMETER = 0x0914; - public static final int HEALTH_PULSE_RATE = 0x0918; - public static final int HEALTH_DATA_DISPLAY = 0x091C; - - // Devices in PERIPHERAL major class - /** - * @hide - */ - public static final int PERIPHERAL_NON_KEYBOARD_NON_POINTING = 0x0500; - /** - * @hide - */ - public static final int PERIPHERAL_KEYBOARD = 0x0540; - /** - * @hide - */ - public static final int PERIPHERAL_POINTING = 0x0580; - /** - * @hide - */ - public static final int PERIPHERAL_KEYBOARD_POINTING = 0x05C0; - } - - /** - * Return the major device class component of this {@link BluetoothClass}. - * <p>Values returned from this function can be compared with the - * public constants in {@link BluetoothClass.Device.Major} to determine - * which major class is encoded in this Bluetooth class. - * - * @return major device class component - */ - public int getMajorDeviceClass() { - return (mClass & Device.Major.BITMASK); - } - - /** - * Return the (major and minor) device class component of this - * {@link BluetoothClass}. - * <p>Values returned from this function can be compared with the - * public constants in {@link BluetoothClass.Device} to determine which - * device class is encoded in this Bluetooth class. - * - * @return device class component - */ - public int getDeviceClass() { - return (mClass & Device.BITMASK); - } - - /** - * Return the Bluetooth Class of Device (CoD) value including the - * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and - * minor device fields. - * - * <p>This value is an integer representation of Bluetooth CoD as in - * Bluetooth specification. - * - * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a> - * - * @hide - */ - @TestApi - public int getClassOfDevice() { - return mClass; - } - - /** - * Return the Bluetooth Class of Device (CoD) value including the - * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and - * minor device fields. - * - * <p>This value is a byte array representation of Bluetooth CoD as in - * Bluetooth specification. - * - * <p>Bluetooth COD information is 3 bytes, but stored as an int. Hence the - * MSB is useless and needs to be thrown away. The lower 3 bytes are - * converted into a byte array MSB to LSB. Hence, using BIG_ENDIAN. - * - * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a> - * - * @hide - */ - public byte[] getClassOfDeviceBytes() { - byte[] bytes = ByteBuffer.allocate(4) - .order(ByteOrder.BIG_ENDIAN) - .putInt(mClass) - .array(); - - // Discard the top byte - return Arrays.copyOfRange(bytes, 1, bytes.length); - } - - public static final int PROFILE_HEADSET = 0; - - public static final int PROFILE_A2DP = 1; - - /** @hide */ - @SystemApi - public static final int PROFILE_OPP = 2; - - public static final int PROFILE_HID = 3; - - /** @hide */ - @SystemApi - public static final int PROFILE_PANU = 4; - - /** @hide */ - @SystemApi - public static final int PROFILE_NAP = 5; - - /** @hide */ - @SystemApi - public static final int PROFILE_A2DP_SINK = 6; - - /** - * Check class bits for possible bluetooth profile support. - * This is a simple heuristic that tries to guess if a device with the - * given class bits might support specified profile. It is not accurate for all - * devices. It tries to err on the side of false positives. - * - * @param profile the profile to be checked - * @return whether this device supports specified profile - */ - public boolean doesClassMatch(int profile) { - if (profile == PROFILE_A2DP) { - if (hasService(Service.RENDER)) { - return true; - } - // By the A2DP spec, sinks must indicate the RENDER service. - // However we found some that do not (Chordette). So lets also - // match on some other class bits. - switch (getDeviceClass()) { - case Device.AUDIO_VIDEO_HIFI_AUDIO: - case Device.AUDIO_VIDEO_HEADPHONES: - case Device.AUDIO_VIDEO_LOUDSPEAKER: - case Device.AUDIO_VIDEO_CAR_AUDIO: - return true; - default: - return false; - } - } else if (profile == PROFILE_A2DP_SINK) { - if (hasService(Service.CAPTURE)) { - return true; - } - // By the A2DP spec, srcs must indicate the CAPTURE service. - // However if some device that do not, we try to - // match on some other class bits. - switch (getDeviceClass()) { - case Device.AUDIO_VIDEO_HIFI_AUDIO: - case Device.AUDIO_VIDEO_SET_TOP_BOX: - case Device.AUDIO_VIDEO_VCR: - return true; - default: - return false; - } - } else if (profile == PROFILE_HEADSET) { - // The render service class is required by the spec for HFP, so is a - // pretty good signal - if (hasService(Service.RENDER)) { - return true; - } - // Just in case they forgot the render service class - switch (getDeviceClass()) { - case Device.AUDIO_VIDEO_HANDSFREE: - case Device.AUDIO_VIDEO_WEARABLE_HEADSET: - case Device.AUDIO_VIDEO_CAR_AUDIO: - return true; - default: - return false; - } - } else if (profile == PROFILE_OPP) { - if (hasService(Service.OBJECT_TRANSFER)) { - return true; - } - - switch (getDeviceClass()) { - case Device.COMPUTER_UNCATEGORIZED: - case Device.COMPUTER_DESKTOP: - case Device.COMPUTER_SERVER: - case Device.COMPUTER_LAPTOP: - case Device.COMPUTER_HANDHELD_PC_PDA: - case Device.COMPUTER_PALM_SIZE_PC_PDA: - case Device.COMPUTER_WEARABLE: - case Device.PHONE_UNCATEGORIZED: - case Device.PHONE_CELLULAR: - case Device.PHONE_CORDLESS: - case Device.PHONE_SMART: - case Device.PHONE_MODEM_OR_GATEWAY: - case Device.PHONE_ISDN: - return true; - default: - return false; - } - } else if (profile == PROFILE_HID) { - return getMajorDeviceClass() == Device.Major.PERIPHERAL; - } else if (profile == PROFILE_PANU || profile == PROFILE_NAP) { - // No good way to distinguish between the two, based on class bits. - if (hasService(Service.NETWORKING)) { - return true; - } - return getMajorDeviceClass() == Device.Major.NETWORKING; - } else { - return false; - } - } -} diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java deleted file mode 100644 index 9a4151adffc7..000000000000 --- a/core/java/android/bluetooth/BluetoothCodecConfig.java +++ /dev/null @@ -1,807 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * Represents the codec configuration for a Bluetooth A2DP source device. - * <p>Contains the source codec type, the codec priority, the codec sample - * rate, the codec bits per sample, and the codec channel mode. - * <p>The source codec type values are the same as those supported by the - * device hardware. - * - * {@see BluetoothA2dp} - */ -public final class BluetoothCodecConfig implements Parcelable { - /** @hide */ - @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = { - SOURCE_CODEC_TYPE_SBC, - SOURCE_CODEC_TYPE_AAC, - SOURCE_CODEC_TYPE_APTX, - SOURCE_CODEC_TYPE_APTX_HD, - SOURCE_CODEC_TYPE_LDAC, - SOURCE_CODEC_TYPE_INVALID - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SourceCodecType {} - - /** - * Source codec type SBC. This is the mandatory source codec - * type. - */ - public static final int SOURCE_CODEC_TYPE_SBC = 0; - - /** - * Source codec type AAC. - */ - public static final int SOURCE_CODEC_TYPE_AAC = 1; - - /** - * Source codec type APTX. - */ - public static final int SOURCE_CODEC_TYPE_APTX = 2; - - /** - * Source codec type APTX HD. - */ - public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; - - /** - * Source codec type LDAC. - */ - public static final int SOURCE_CODEC_TYPE_LDAC = 4; - - /** - * Source codec type invalid. This is the default value used for codec - * type. - */ - public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; - - /** - * Represents the count of valid source codec types. Can be accessed via - * {@link #getMaxCodecType}. - */ - private static final int SOURCE_CODEC_TYPE_MAX = 5; - - /** @hide */ - @IntDef(prefix = "CODEC_PRIORITY_", value = { - CODEC_PRIORITY_DISABLED, - CODEC_PRIORITY_DEFAULT, - CODEC_PRIORITY_HIGHEST - }) - @Retention(RetentionPolicy.SOURCE) - public @interface CodecPriority {} - - /** - * Codec priority disabled. - * Used to indicate that this codec is disabled and should not be used. - */ - public static final int CODEC_PRIORITY_DISABLED = -1; - - /** - * Codec priority default. - * Default value used for codec priority. - */ - public static final int CODEC_PRIORITY_DEFAULT = 0; - - /** - * Codec priority highest. - * Used to indicate the highest priority a codec can have. - */ - public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000; - - /** @hide */ - @IntDef(prefix = "SAMPLE_RATE_", value = { - SAMPLE_RATE_NONE, - SAMPLE_RATE_44100, - SAMPLE_RATE_48000, - SAMPLE_RATE_88200, - SAMPLE_RATE_96000, - SAMPLE_RATE_176400, - SAMPLE_RATE_192000 - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SampleRate {} - - /** - * Codec sample rate 0 Hz. Default value used for - * codec sample rate. - */ - public static final int SAMPLE_RATE_NONE = 0; - - /** - * Codec sample rate 44100 Hz. - */ - public static final int SAMPLE_RATE_44100 = 0x1 << 0; - - /** - * Codec sample rate 48000 Hz. - */ - public static final int SAMPLE_RATE_48000 = 0x1 << 1; - - /** - * Codec sample rate 88200 Hz. - */ - public static final int SAMPLE_RATE_88200 = 0x1 << 2; - - /** - * Codec sample rate 96000 Hz. - */ - public static final int SAMPLE_RATE_96000 = 0x1 << 3; - - /** - * Codec sample rate 176400 Hz. - */ - public static final int SAMPLE_RATE_176400 = 0x1 << 4; - - /** - * Codec sample rate 192000 Hz. - */ - public static final int SAMPLE_RATE_192000 = 0x1 << 5; - - /** @hide */ - @IntDef(prefix = "BITS_PER_SAMPLE_", value = { - BITS_PER_SAMPLE_NONE, - BITS_PER_SAMPLE_16, - BITS_PER_SAMPLE_24, - BITS_PER_SAMPLE_32 - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BitsPerSample {} - - /** - * Codec bits per sample 0. Default value of the codec - * bits per sample. - */ - public static final int BITS_PER_SAMPLE_NONE = 0; - - /** - * Codec bits per sample 16. - */ - public static final int BITS_PER_SAMPLE_16 = 0x1 << 0; - - /** - * Codec bits per sample 24. - */ - public static final int BITS_PER_SAMPLE_24 = 0x1 << 1; - - /** - * Codec bits per sample 32. - */ - public static final int BITS_PER_SAMPLE_32 = 0x1 << 2; - - /** @hide */ - @IntDef(prefix = "CHANNEL_MODE_", value = { - CHANNEL_MODE_NONE, - CHANNEL_MODE_MONO, - CHANNEL_MODE_STEREO - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ChannelMode {} - - /** - * Codec channel mode NONE. Default value of the - * codec channel mode. - */ - public static final int CHANNEL_MODE_NONE = 0; - - /** - * Codec channel mode MONO. - */ - public static final int CHANNEL_MODE_MONO = 0x1 << 0; - - /** - * Codec channel mode STEREO. - */ - public static final int CHANNEL_MODE_STEREO = 0x1 << 1; - - private final @SourceCodecType int mCodecType; - private @CodecPriority int mCodecPriority; - private final @SampleRate int mSampleRate; - private final @BitsPerSample int mBitsPerSample; - private final @ChannelMode int mChannelMode; - private final long mCodecSpecific1; - private final long mCodecSpecific2; - private final long mCodecSpecific3; - private final long mCodecSpecific4; - - /** - * Creates a new BluetoothCodecConfig. - * - * @param codecType the source codec type - * @param codecPriority the priority of this codec - * @param sampleRate the codec sample rate - * @param bitsPerSample the bits per sample of this codec - * @param channelMode the channel mode of this codec - * @param codecSpecific1 the specific value 1 - * @param codecSpecific2 the specific value 2 - * @param codecSpecific3 the specific value 3 - * @param codecSpecific4 the specific value 4 - * values to 0. - * @hide - */ - @UnsupportedAppUsage - public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority, - @SampleRate int sampleRate, @BitsPerSample int bitsPerSample, - @ChannelMode int channelMode, long codecSpecific1, - long codecSpecific2, long codecSpecific3, - long codecSpecific4) { - mCodecType = codecType; - mCodecPriority = codecPriority; - mSampleRate = sampleRate; - mBitsPerSample = bitsPerSample; - mChannelMode = channelMode; - mCodecSpecific1 = codecSpecific1; - mCodecSpecific2 = codecSpecific2; - mCodecSpecific3 = codecSpecific3; - mCodecSpecific4 = codecSpecific4; - } - - /** - * Creates a new BluetoothCodecConfig. - * <p> By default, the codec priority will be set - * to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to - * {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to - * {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to - * {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific - * values to 0. - * - * @param codecType the source codec type - */ - public BluetoothCodecConfig(@SourceCodecType int codecType) { - this(codecType, BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_NONE, - BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, - BluetoothCodecConfig.CHANNEL_MODE_NONE, 0, 0, 0, 0); - } - - private BluetoothCodecConfig(Parcel in) { - mCodecType = in.readInt(); - mCodecPriority = in.readInt(); - mSampleRate = in.readInt(); - mBitsPerSample = in.readInt(); - mChannelMode = in.readInt(); - mCodecSpecific1 = in.readLong(); - mCodecSpecific2 = in.readLong(); - mCodecSpecific3 = in.readLong(); - mCodecSpecific4 = in.readLong(); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothCodecConfig) { - BluetoothCodecConfig other = (BluetoothCodecConfig) o; - return (other.mCodecType == mCodecType - && other.mCodecPriority == mCodecPriority - && other.mSampleRate == mSampleRate - && other.mBitsPerSample == mBitsPerSample - && other.mChannelMode == mChannelMode - && other.mCodecSpecific1 == mCodecSpecific1 - && other.mCodecSpecific2 == mCodecSpecific2 - && other.mCodecSpecific3 == mCodecSpecific3 - && other.mCodecSpecific4 == mCodecSpecific4); - } - return false; - } - - /** - * Returns a hash representation of this BluetoothCodecConfig - * based on all the config values. - */ - @Override - public int hashCode() { - return Objects.hash(mCodecType, mCodecPriority, mSampleRate, - mBitsPerSample, mChannelMode, mCodecSpecific1, - mCodecSpecific2, mCodecSpecific3, mCodecSpecific4); - } - - /** - * Adds capability string to an existing string. - * - * @param prevStr the previous string with the capabilities. Can be a {@code null} pointer - * @param capStr the capability string to append to prevStr argument - * @return the result string in the form "prevStr|capStr" - */ - private static String appendCapabilityToString(@Nullable String prevStr, - @NonNull String capStr) { - if (prevStr == null) { - return capStr; - } - return prevStr + "|" + capStr; - } - - /** - * Returns a {@link String} that describes each BluetoothCodecConfig parameter - * current value. - */ - @Override - public String toString() { - String sampleRateStr = null; - if (mSampleRate == SAMPLE_RATE_NONE) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "NONE"); - } - if ((mSampleRate & SAMPLE_RATE_44100) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "44100"); - } - if ((mSampleRate & SAMPLE_RATE_48000) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "48000"); - } - if ((mSampleRate & SAMPLE_RATE_88200) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "88200"); - } - if ((mSampleRate & SAMPLE_RATE_96000) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "96000"); - } - if ((mSampleRate & SAMPLE_RATE_176400) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "176400"); - } - if ((mSampleRate & SAMPLE_RATE_192000) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "192000"); - } - - String bitsPerSampleStr = null; - if (mBitsPerSample == BITS_PER_SAMPLE_NONE) { - bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "NONE"); - } - if ((mBitsPerSample & BITS_PER_SAMPLE_16) != 0) { - bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "16"); - } - if ((mBitsPerSample & BITS_PER_SAMPLE_24) != 0) { - bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "24"); - } - if ((mBitsPerSample & BITS_PER_SAMPLE_32) != 0) { - bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "32"); - } - - String channelModeStr = null; - if (mChannelMode == CHANNEL_MODE_NONE) { - channelModeStr = appendCapabilityToString(channelModeStr, "NONE"); - } - if ((mChannelMode & CHANNEL_MODE_MONO) != 0) { - channelModeStr = appendCapabilityToString(channelModeStr, "MONO"); - } - if ((mChannelMode & CHANNEL_MODE_STEREO) != 0) { - channelModeStr = appendCapabilityToString(channelModeStr, "STEREO"); - } - - return "{codecName:" + getCodecName() - + ",mCodecType:" + mCodecType - + ",mCodecPriority:" + mCodecPriority - + ",mSampleRate:" + String.format("0x%x", mSampleRate) - + "(" + sampleRateStr + ")" - + ",mBitsPerSample:" + String.format("0x%x", mBitsPerSample) - + "(" + bitsPerSampleStr + ")" - + ",mChannelMode:" + String.format("0x%x", mChannelMode) - + "(" + channelModeStr + ")" - + ",mCodecSpecific1:" + mCodecSpecific1 - + ",mCodecSpecific2:" + mCodecSpecific2 - + ",mCodecSpecific3:" + mCodecSpecific3 - + ",mCodecSpecific4:" + mCodecSpecific4 + "}"; - } - - /** - * @return 0 - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecConfig> CREATOR = - new Parcelable.Creator<BluetoothCodecConfig>() { - public BluetoothCodecConfig createFromParcel(Parcel in) { - return new BluetoothCodecConfig(in); - } - - public BluetoothCodecConfig[] newArray(int size) { - return new BluetoothCodecConfig[size]; - } - }; - - /** - * Flattens the object to a parcel - * - * @param out The Parcel in which the object should be written - * @param flags Additional flags about how the object should be written - * - * @hide - */ - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mCodecType); - out.writeInt(mCodecPriority); - out.writeInt(mSampleRate); - out.writeInt(mBitsPerSample); - out.writeInt(mChannelMode); - out.writeLong(mCodecSpecific1); - out.writeLong(mCodecSpecific2); - out.writeLong(mCodecSpecific3); - out.writeLong(mCodecSpecific4); - } - - /** - * Returns the codec name converted to {@link String}. - * @hide - */ - public @NonNull String getCodecName() { - switch (mCodecType) { - case SOURCE_CODEC_TYPE_SBC: - return "SBC"; - case SOURCE_CODEC_TYPE_AAC: - return "AAC"; - case SOURCE_CODEC_TYPE_APTX: - return "aptX"; - case SOURCE_CODEC_TYPE_APTX_HD: - return "aptX HD"; - case SOURCE_CODEC_TYPE_LDAC: - return "LDAC"; - case SOURCE_CODEC_TYPE_INVALID: - return "INVALID CODEC"; - default: - break; - } - return "UNKNOWN CODEC(" + mCodecType + ")"; - } - - /** - * Returns the source codec type of this config. - */ - public @SourceCodecType int getCodecType() { - return mCodecType; - } - - /** - * Returns the valid codec types count. - */ - public static int getMaxCodecType() { - return SOURCE_CODEC_TYPE_MAX; - } - - /** - * Checks whether the codec is mandatory. - * <p> The actual mandatory codec type for Android Bluetooth audio is SBC. - * See {@link #SOURCE_CODEC_TYPE_SBC}. - * - * @return {@code true} if the codec is mandatory, {@code false} otherwise - * @hide - */ - public boolean isMandatoryCodec() { - return mCodecType == SOURCE_CODEC_TYPE_SBC; - } - - /** - * Returns the codec selection priority. - * <p>The codec selection priority is relative to other codecs: larger value - * means higher priority. - */ - public @CodecPriority int getCodecPriority() { - return mCodecPriority; - } - - /** - * Sets the codec selection priority. - * <p>The codec selection priority is relative to other codecs: larger value - * means higher priority. - * - * @param codecPriority the priority this codec should have - * @hide - */ - public void setCodecPriority(@CodecPriority int codecPriority) { - mCodecPriority = codecPriority; - } - - /** - * Returns the codec sample rate. The value can be a bitmask with all - * supported sample rates. - */ - public @SampleRate int getSampleRate() { - return mSampleRate; - } - - /** - * Returns the codec bits per sample. The value can be a bitmask with all - * bits per sample supported. - */ - public @BitsPerSample int getBitsPerSample() { - return mBitsPerSample; - } - - /** - * Returns the codec channel mode. The value can be a bitmask with all - * supported channel modes. - */ - public @ChannelMode int getChannelMode() { - return mChannelMode; - } - - /** - * Returns the codec specific value1. - */ - public long getCodecSpecific1() { - return mCodecSpecific1; - } - - /** - * Returns the codec specific value2. - */ - public long getCodecSpecific2() { - return mCodecSpecific2; - } - - /** - * Returns the codec specific value3. - */ - public long getCodecSpecific3() { - return mCodecSpecific3; - } - - /** - * Returns the codec specific value4. - */ - public long getCodecSpecific4() { - return mCodecSpecific4; - } - - /** - * Checks whether a value set presented by a bitmask has zero or single bit - * - * @param valueSet the value set presented by a bitmask - * @return {@code true} if the valueSet contains zero or single bit, {@code false} otherwise - * @hide - */ - private static boolean hasSingleBit(int valueSet) { - return (valueSet == 0 || (valueSet & (valueSet - 1)) == 0); - } - - /** - * Returns whether the object contains none or single sample rate. - * @hide - */ - public boolean hasSingleSampleRate() { - return hasSingleBit(mSampleRate); - } - - /** - * Returns whether the object contains none or single bits per sample. - * @hide - */ - public boolean hasSingleBitsPerSample() { - return hasSingleBit(mBitsPerSample); - } - - /** - * Returns whether the object contains none or single channel mode. - * @hide - */ - public boolean hasSingleChannelMode() { - return hasSingleBit(mChannelMode); - } - - /** - * Checks whether the audio feeding parameters are the same. - * - * @param other the codec config to compare against - * @return {@code true} if the audio feeding parameters are same, {@code false} otherwise - * @hide - */ - public boolean sameAudioFeedingParameters(BluetoothCodecConfig other) { - return (other != null && other.mSampleRate == mSampleRate - && other.mBitsPerSample == mBitsPerSample - && other.mChannelMode == mChannelMode); - } - - /** - * Checks whether another codec config has the similar feeding parameters. - * Any parameters with NONE value will be considered to be a wildcard matching. - * - * @param other the codec config to compare against - * @return {@code true} if the audio feeding parameters are similar, {@code false} otherwise - * @hide - */ - public boolean similarCodecFeedingParameters(BluetoothCodecConfig other) { - if (other == null || mCodecType != other.mCodecType) { - return false; - } - int sampleRate = other.mSampleRate; - if (mSampleRate == SAMPLE_RATE_NONE - || sampleRate == SAMPLE_RATE_NONE) { - sampleRate = mSampleRate; - } - int bitsPerSample = other.mBitsPerSample; - if (mBitsPerSample == BITS_PER_SAMPLE_NONE - || bitsPerSample == BITS_PER_SAMPLE_NONE) { - bitsPerSample = mBitsPerSample; - } - int channelMode = other.mChannelMode; - if (mChannelMode == CHANNEL_MODE_NONE - || channelMode == CHANNEL_MODE_NONE) { - channelMode = mChannelMode; - } - return sameAudioFeedingParameters(new BluetoothCodecConfig( - mCodecType, /* priority */ 0, sampleRate, bitsPerSample, channelMode, - /* specific1 */ 0, /* specific2 */ 0, /* specific3 */ 0, - /* specific4 */ 0)); - } - - /** - * Checks whether the codec specific parameters are the same. - * <p> Currently, only AAC VBR and LDAC Playback Quality on CodecSpecific1 - * are compared. - * - * @param other the codec config to compare against - * @return {@code true} if the codec specific parameters are the same, {@code false} otherwise - * @hide - */ - public boolean sameCodecSpecificParameters(BluetoothCodecConfig other) { - if (other == null && mCodecType != other.mCodecType) { - return false; - } - switch (mCodecType) { - case SOURCE_CODEC_TYPE_AAC: - case SOURCE_CODEC_TYPE_LDAC: - if (mCodecSpecific1 != other.mCodecSpecific1) { - return false; - } - default: - return true; - } - } - - /** - * Builder for {@link BluetoothCodecConfig}. - * <p> By default, the codec type will be set to - * {@link BluetoothCodecConfig#SOURCE_CODEC_TYPE_INVALID}, the codec priority - * to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to - * {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to - * {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to - * {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific - * values to 0. - */ - public static final class Builder { - private int mCodecType = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID; - private int mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; - private int mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE; - private int mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE; - private int mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE; - private long mCodecSpecific1 = 0; - private long mCodecSpecific2 = 0; - private long mCodecSpecific3 = 0; - private long mCodecSpecific4 = 0; - - /** - * Set codec type for Bluetooth codec config. - * - * @param codecType of this codec - * @return the same Builder instance - */ - public @NonNull Builder setCodecType(@SourceCodecType int codecType) { - mCodecType = codecType; - return this; - } - - /** - * Set codec priority for Bluetooth codec config. - * - * @param codecPriority of this codec - * @return the same Builder instance - */ - public @NonNull Builder setCodecPriority(@CodecPriority int codecPriority) { - mCodecPriority = codecPriority; - return this; - } - - /** - * Set sample rate for Bluetooth codec config. - * - * @param sampleRate of this codec - * @return the same Builder instance - */ - public @NonNull Builder setSampleRate(@SampleRate int sampleRate) { - mSampleRate = sampleRate; - return this; - } - - /** - * Set the bits per sample for Bluetooth codec config. - * - * @param bitsPerSample of this codec - * @return the same Builder instance - */ - public @NonNull Builder setBitsPerSample(@BitsPerSample int bitsPerSample) { - mBitsPerSample = bitsPerSample; - return this; - } - - /** - * Set the channel mode for Bluetooth codec config. - * - * @param channelMode of this codec - * @return the same Builder instance - */ - public @NonNull Builder setChannelMode(@ChannelMode int channelMode) { - mChannelMode = channelMode; - return this; - } - - /** - * Set the first codec specific values for Bluetooth codec config. - * - * @param codecSpecific1 codec specific value or 0 if default - * @return the same Builder instance - */ - public @NonNull Builder setCodecSpecific1(long codecSpecific1) { - mCodecSpecific1 = codecSpecific1; - return this; - } - - /** - * Set the second codec specific values for Bluetooth codec config. - * - * @param codecSpecific2 codec specific value or 0 if default - * @return the same Builder instance - */ - public @NonNull Builder setCodecSpecific2(long codecSpecific2) { - mCodecSpecific2 = codecSpecific2; - return this; - } - - /** - * Set the third codec specific values for Bluetooth codec config. - * - * @param codecSpecific3 codec specific value or 0 if default - * @return the same Builder instance - */ - public @NonNull Builder setCodecSpecific3(long codecSpecific3) { - mCodecSpecific3 = codecSpecific3; - return this; - } - - /** - * Set the fourth codec specific values for Bluetooth codec config. - * - * @param codecSpecific4 codec specific value or 0 if default - * @return the same Builder instance - */ - public @NonNull Builder setCodecSpecific4(long codecSpecific4) { - mCodecSpecific4 = codecSpecific4; - return this; - } - - /** - * Build {@link BluetoothCodecConfig}. - * @return new BluetoothCodecConfig built - */ - public @NonNull BluetoothCodecConfig build() { - return new BluetoothCodecConfig(mCodecType, mCodecPriority, - mSampleRate, mBitsPerSample, - mChannelMode, mCodecSpecific1, - mCodecSpecific2, mCodecSpecific3, - mCodecSpecific4); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java deleted file mode 100644 index 02606feb3b3f..000000000000 --- a/core/java/android/bluetooth/BluetoothCodecStatus.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * Represents the codec status (configuration and capability) for a Bluetooth - * A2DP source device. - * - * {@see BluetoothA2dp} - */ -public final class BluetoothCodecStatus implements Parcelable { - /** - * Extra for the codec configuration intents of the individual profiles. - * - * This extra represents the current codec status of the A2DP - * profile. - */ - public static final String EXTRA_CODEC_STATUS = - "android.bluetooth.extra.CODEC_STATUS"; - - private final @Nullable BluetoothCodecConfig mCodecConfig; - private final @Nullable List<BluetoothCodecConfig> mCodecsLocalCapabilities; - private final @Nullable List<BluetoothCodecConfig> mCodecsSelectableCapabilities; - - public BluetoothCodecStatus(@Nullable BluetoothCodecConfig codecConfig, - @Nullable List<BluetoothCodecConfig> codecsLocalCapabilities, - @Nullable List<BluetoothCodecConfig> codecsSelectableCapabilities) { - mCodecConfig = codecConfig; - mCodecsLocalCapabilities = codecsLocalCapabilities; - mCodecsSelectableCapabilities = codecsSelectableCapabilities; - } - - private BluetoothCodecStatus(Parcel in) { - mCodecConfig = in.readTypedObject(BluetoothCodecConfig.CREATOR); - mCodecsLocalCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR); - mCodecsSelectableCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothCodecStatus) { - BluetoothCodecStatus other = (BluetoothCodecStatus) o; - return (Objects.equals(other.mCodecConfig, mCodecConfig) - && sameCapabilities(other.mCodecsLocalCapabilities, mCodecsLocalCapabilities) - && sameCapabilities(other.mCodecsSelectableCapabilities, - mCodecsSelectableCapabilities)); - } - return false; - } - - /** - * Checks whether two lists of capabilities contain same capabilities. - * The order of the capabilities in each list is ignored. - * - * @param c1 the first list of capabilities to compare - * @param c2 the second list of capabilities to compare - * @return {@code true} if both lists contain same capabilities - */ - private static boolean sameCapabilities(@Nullable List<BluetoothCodecConfig> c1, - @Nullable List<BluetoothCodecConfig> c2) { - if (c1 == null) { - return (c2 == null); - } - if (c2 == null) { - return false; - } - if (c1.size() != c2.size()) { - return false; - } - return c1.containsAll(c2); - } - - /** - * Checks whether the codec config matches the selectable capabilities. - * Any parameters of the codec config with NONE value will be considered a wildcard matching. - * - * @param codecConfig the codec config to compare against - * @return {@code true} if the codec config matches, {@code false} otherwise - */ - public boolean isCodecConfigSelectable(@Nullable BluetoothCodecConfig codecConfig) { - if (codecConfig == null || !codecConfig.hasSingleSampleRate() - || !codecConfig.hasSingleBitsPerSample() || !codecConfig.hasSingleChannelMode()) { - return false; - } - for (BluetoothCodecConfig selectableConfig : mCodecsSelectableCapabilities) { - if (codecConfig.getCodecType() != selectableConfig.getCodecType()) { - continue; - } - int sampleRate = codecConfig.getSampleRate(); - if ((sampleRate & selectableConfig.getSampleRate()) == 0 - && sampleRate != BluetoothCodecConfig.SAMPLE_RATE_NONE) { - continue; - } - int bitsPerSample = codecConfig.getBitsPerSample(); - if ((bitsPerSample & selectableConfig.getBitsPerSample()) == 0 - && bitsPerSample != BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) { - continue; - } - int channelMode = codecConfig.getChannelMode(); - if ((channelMode & selectableConfig.getChannelMode()) == 0 - && channelMode != BluetoothCodecConfig.CHANNEL_MODE_NONE) { - continue; - } - return true; - } - return false; - } - - /** - * Returns a hash based on the codec config and local capabilities. - */ - @Override - public int hashCode() { - return Objects.hash(mCodecConfig, mCodecsLocalCapabilities, - mCodecsLocalCapabilities); - } - - /** - * Returns a {@link String} that describes each BluetoothCodecStatus parameter - * current value. - */ - @Override - public String toString() { - return "{mCodecConfig:" + mCodecConfig - + ",mCodecsLocalCapabilities:" + mCodecsLocalCapabilities - + ",mCodecsSelectableCapabilities:" + mCodecsSelectableCapabilities - + "}"; - } - - /** - * @return 0 - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecStatus> CREATOR = - new Parcelable.Creator<BluetoothCodecStatus>() { - public BluetoothCodecStatus createFromParcel(Parcel in) { - return new BluetoothCodecStatus(in); - } - - public BluetoothCodecStatus[] newArray(int size) { - return new BluetoothCodecStatus[size]; - } - }; - - /** - * Flattens the object to a parcel. - * - * @param out The Parcel in which the object should be written - * @param flags Additional flags about how the object should be written - */ - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeTypedObject(mCodecConfig, 0); - out.writeTypedList(mCodecsLocalCapabilities); - out.writeTypedList(mCodecsSelectableCapabilities); - } - - /** - * Returns the current codec configuration. - */ - public @Nullable BluetoothCodecConfig getCodecConfig() { - return mCodecConfig; - } - - /** - * Returns the codecs local capabilities. - */ - public @NonNull List<BluetoothCodecConfig> getCodecsLocalCapabilities() { - return (mCodecsLocalCapabilities == null) - ? Collections.emptyList() : mCodecsLocalCapabilities; - } - - /** - * Returns the codecs selectable capabilities. - */ - public @NonNull List<BluetoothCodecConfig> getCodecsSelectableCapabilities() { - return (mCodecsSelectableCapabilities == null) - ? Collections.emptyList() : mCodecsSelectableCapabilities; - } -} diff --git a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java deleted file mode 100644 index ba57ec472a6e..000000000000 --- a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java +++ /dev/null @@ -1,555 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.CallbackExecutor; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Bluetooth CSIP set coordinator. - * - * <p>BluetoothCsipSetCoordinator is a proxy object for controlling the Bluetooth VC - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothCsipSetCoordinator proxy object. - * - */ -public final class BluetoothCsipSetCoordinator implements BluetoothProfile, AutoCloseable { - private static final String TAG = "BluetoothCsipSetCoordinator"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - - private CloseGuard mCloseGuard; - - /** - * @hide - */ - @SystemApi - public interface ClientLockCallback { - /** - * @hide - */ - @SystemApi void onGroupLockSet(int groupId, int opStatus, boolean isLocked); - } - - private static class BluetoothCsipSetCoordinatorLockCallbackDelegate - extends IBluetoothCsipSetCoordinatorLockCallback.Stub { - private final ClientLockCallback mCallback; - private final Executor mExecutor; - - BluetoothCsipSetCoordinatorLockCallbackDelegate( - Executor executor, ClientLockCallback callback) { - mExecutor = executor; - mCallback = callback; - } - - @Override - public void onGroupLockSet(int groupId, int opStatus, boolean isLocked) { - mExecutor.execute(() -> mCallback.onGroupLockSet(groupId, opStatus, isLocked)); - } - }; - - /** - * Intent used to broadcast the change in connection state of the CSIS - * Client. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CSIS_CONNECTION_STATE_CHANGED = - "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED"; - - /** - * Intent used to expose broadcast receiving device. - * - * <p>This intent will have 2 extras: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Broadcast receiver device. </li> - * <li> {@link #EXTRA_CSIS_GROUP_ID} - Group identifier. </li> - * <li> {@link #EXTRA_CSIS_GROUP_SIZE} - Group size. </li> - * <li> {@link #EXTRA_CSIS_GROUP_TYPE_UUID} - Group type UUID. </li> - * </ul> - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CSIS_DEVICE_AVAILABLE = - "android.bluetooth.action.CSIS_DEVICE_AVAILABLE"; - - /** - * Used as an extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent. - * Contains the group id. - * - * @hide - */ - public static final String EXTRA_CSIS_GROUP_ID = "android.bluetooth.extra.CSIS_GROUP_ID"; - - /** - * Group size as int extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent. - * - * @hide - */ - public static final String EXTRA_CSIS_GROUP_SIZE = "android.bluetooth.extra.CSIS_GROUP_SIZE"; - - /** - * Group type uuid extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent. - * - * @hide - */ - public static final String EXTRA_CSIS_GROUP_TYPE_UUID = - "android.bluetooth.extra.CSIS_GROUP_TYPE_UUID"; - - /** - * Intent used to broadcast information about identified set member - * ready to connect. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * <li> {@link #EXTRA_CSIS_GROUP_ID} - Group identifier. </li> - * </ul> - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CSIS_SET_MEMBER_AVAILABLE = - "android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE"; - - /** - * This represents an invalid group ID. - * - * @hide - */ - public static final int GROUP_ID_INVALID = IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID; - - /** - * Indicating that group was locked with success. - * - * @hide - */ - public static final int GROUP_LOCK_SUCCESS = 0; - - /** - * Indicating that group locked failed due to invalid group ID. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_INVALID_GROUP = 1; - - /** - * Indicating that group locked failed due to empty group. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_GROUP_EMPTY = 2; - - /** - * Indicating that group locked failed due to group members being disconnected. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_GROUP_NOT_CONNECTED = 3; - - /** - * Indicating that group locked failed due to group member being already locked. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_LOCKED_BY_OTHER = 4; - - /** - * Indicating that group locked failed due to other reason. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_OTHER_REASON = 5; - - /** - * Indicating that group member in locked state was lost. - * - * @hide - */ - public static final int LOCKED_GROUP_MEMBER_LOST = 6; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothCsipSetCoordinator> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.CSIP_SET_COORDINATOR, TAG, - IBluetoothCsipSetCoordinator.class.getName()) { - @Override - public IBluetoothCsipSetCoordinator getServiceInterface(IBinder service) { - return IBluetoothCsipSetCoordinator.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothCsipSetCoordinator proxy object for interacting with the local - * Bluetooth CSIS service. - */ - /*package*/ BluetoothCsipSetCoordinator(Context context, ServiceListener listener, BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - mCloseGuard = new CloseGuard(); - mCloseGuard.open("close"); - } - - /** - * @hide - */ - protected void finalize() { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - close(); - } - - /** - * @hide - */ - public void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothCsipSetCoordinator getService() { - return mProfileConnector.getService(); - } - - /** - * Lock the set. - * @param groupId group ID to lock, - * @param executor callback executor, - * @param cb callback to report lock and unlock events - stays valid until the app unlocks - * using the returned lock identifier or the lock timeouts on the remote side, - * as per CSIS specification, - * @return unique lock identifier used for unlocking or null if lock has failed. - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public - @Nullable UUID groupLock(int groupId, @Nullable @CallbackExecutor Executor executor, - @Nullable ClientLockCallback cb) { - if (VDBG) log("groupLockSet()"); - final IBluetoothCsipSetCoordinator service = getService(); - final UUID defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - IBluetoothCsipSetCoordinatorLockCallback delegate = null; - if ((executor != null) && (cb != null)) { - delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb); - } - try { - final SynchronousResultReceiver<ParcelUuid> recv = new SynchronousResultReceiver(); - service.groupLock(groupId, delegate, mAttributionSource, recv); - final ParcelUuid ret = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - return ret == null ? defaultValue : ret.getUuid(); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Unlock the set. - * @param lockUuid unique lock identifier - * @return true if unlocked, false on error - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean groupUnlock(@NonNull UUID lockUuid) { - if (VDBG) log("groupLockSet()"); - if (lockUuid == null) { - return false; - } - final IBluetoothCsipSetCoordinator service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - return true; - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get device's groups. - * @param device the active device - * @return Map of groups ids and related UUIDs - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public @NonNull Map getGroupUuidMapByDevice(@Nullable BluetoothDevice device) { - if (VDBG) log("getGroupUuidMapByDevice()"); - final IBluetoothCsipSetCoordinator service = getService(); - final Map defaultValue = new HashMap<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Map> recv = new SynchronousResultReceiver(); - service.getGroupUuidMapByDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get group id for the given UUID - * @param uuid - * @return list of group IDs - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public @NonNull List<Integer> getAllGroupIds(@Nullable ParcelUuid uuid) { - if (VDBG) log("getAllGroupIds()"); - final IBluetoothCsipSetCoordinator service = getService(); - final List<Integer> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<Integer>> recv = - new SynchronousResultReceiver(); - service.getAllGroupIds(uuid, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothCsipSetCoordinator service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - public - @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { - if (VDBG) log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")"); - final IBluetoothCsipSetCoordinator service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - public - @BluetoothProfile.BtProfileState int getConnectionState(@Nullable BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothCsipSetCoordinator service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean setConnectionPolicy( - @Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothCsipSetCoordinator service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothCsipSetCoordinator service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(@Nullable BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java deleted file mode 100644 index fc99942cb784..000000000000 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ /dev/null @@ -1,2830 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.app.PropertyInvalidatedCache; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresBluetoothLocationPermission; -import android.bluetooth.annotations.RequiresBluetoothScanPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.companion.AssociationRequest; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.Handler; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.os.Process; -import android.os.RemoteException; -import android.util.Log; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.UUID; - -/** - * Represents a remote Bluetooth device. A {@link BluetoothDevice} lets you - * create a connection with the respective device or query information about - * it, such as the name, address, class, and bonding state. - * - * <p>This class is really just a thin wrapper for a Bluetooth hardware - * address. Objects of this class are immutable. Operations on this class - * are performed on the remote Bluetooth hardware address, using the - * {@link BluetoothAdapter} that was used to create this {@link - * BluetoothDevice}. - * - * <p>To get a {@link BluetoothDevice}, use - * {@link BluetoothAdapter#getRemoteDevice(String) - * BluetoothAdapter.getRemoteDevice(String)} to create one representing a device - * of a known MAC address (which you can get through device discovery with - * {@link BluetoothAdapter}) or get one from the set of bonded devices - * returned by {@link BluetoothAdapter#getBondedDevices() - * BluetoothAdapter.getBondedDevices()}. You can then open a - * {@link BluetoothSocket} for communication with the remote device, using - * {@link #createRfcommSocketToServiceRecord(UUID)} over Bluetooth BR/EDR or using - * {@link #createL2capChannel(int)} over Bluetooth LE. - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p> - * For more information about using Bluetooth, read the <a href= - * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer - * guide. - * </p> - * </div> - * - * {@see BluetoothAdapter} - * {@see BluetoothSocket} - */ -public final class BluetoothDevice implements Parcelable, Attributable { - private static final String TAG = "BluetoothDevice"; - private static final boolean DBG = false; - - /** - * Connection state bitmask as returned by getConnectionState. - */ - private static final int CONNECTION_STATE_DISCONNECTED = 0; - private static final int CONNECTION_STATE_CONNECTED = 1; - private static final int CONNECTION_STATE_ENCRYPTED_BREDR = 2; - private static final int CONNECTION_STATE_ENCRYPTED_LE = 4; - - /** - * Sentinel error value for this class. Guaranteed to not equal any other - * integer constant in this class. Provided as a convenience for functions - * that require a sentinel error value, for example: - * <p><code>Intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, - * BluetoothDevice.ERROR)</code> - */ - public static final int ERROR = Integer.MIN_VALUE; - - /** - * Broadcast Action: Remote device discovered. - * <p>Sent when a remote device is found during discovery. - * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link - * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or - * {@link #EXTRA_RSSI} and/or {@link #EXTRA_IS_COORDINATED_SET_MEMBER} if they are available. - */ - // TODO: Change API to not broadcast RSSI if not available (incoming connection) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_FOUND = - "android.bluetooth.device.action.FOUND"; - - /** - * Broadcast Action: Bluetooth class of a remote device has changed. - * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link - * #EXTRA_CLASS}. - * {@see BluetoothClass} - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CLASS_CHANGED = - "android.bluetooth.device.action.CLASS_CHANGED"; - - /** - * Broadcast Action: Indicates a low level (ACL) connection has been - * established with a remote device. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * <p>ACL connections are managed automatically by the Android Bluetooth - * stack. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ACL_CONNECTED = - "android.bluetooth.device.action.ACL_CONNECTED"; - - /** - * Broadcast Action: Indicates that a low level (ACL) disconnection has - * been requested for a remote device, and it will soon be disconnected. - * <p>This is useful for graceful disconnection. Applications should use - * this intent as a hint to immediately terminate higher level connections - * (RFCOMM, L2CAP, or profile connections) to the remote device. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ACL_DISCONNECT_REQUESTED = - "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED"; - - /** - * Broadcast Action: Indicates a low level (ACL) disconnection from a - * remote device. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * <p>ACL connections are managed automatically by the Android Bluetooth - * stack. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ACL_DISCONNECTED = - "android.bluetooth.device.action.ACL_DISCONNECTED"; - - /** - * Broadcast Action: Indicates the friendly name of a remote device has - * been retrieved for the first time, or changed since the last retrieval. - * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link - * #EXTRA_NAME}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_NAME_CHANGED = - "android.bluetooth.device.action.NAME_CHANGED"; - - /** - * Broadcast Action: Indicates the alias of a remote device has been - * changed. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - */ - @SuppressLint("ActionValue") - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ALIAS_CHANGED = - "android.bluetooth.device.action.ALIAS_CHANGED"; - - /** - * Broadcast Action: Indicates a change in the bond state of a remote - * device. For example, if a device is bonded (paired). - * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link - * #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}. - */ - // Note: When EXTRA_BOND_STATE is BOND_NONE then this will also - // contain a hidden extra field EXTRA_REASON with the result code. - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BOND_STATE_CHANGED = - "android.bluetooth.device.action.BOND_STATE_CHANGED"; - - /** - * Broadcast Action: Indicates the battery level of a remote device has - * been retrieved for the first time, or changed since the last retrieval - * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link - * #EXTRA_BATTERY_LEVEL}. - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BATTERY_LEVEL_CHANGED = - "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED"; - - /** - * Used as an Integer extra field in {@link #ACTION_BATTERY_LEVEL_CHANGED} - * intent. It contains the most recently retrieved battery level information - * ranging from 0% to 100% for a remote device, {@link #BATTERY_LEVEL_UNKNOWN} - * when the valid is unknown or there is an error - * - * @hide - */ - public static final String EXTRA_BATTERY_LEVEL = - "android.bluetooth.device.extra.BATTERY_LEVEL"; - - /** - * Used as the unknown value for {@link #EXTRA_BATTERY_LEVEL} and {@link #getBatteryLevel()} - * - * @hide - */ - public static final int BATTERY_LEVEL_UNKNOWN = -1; - - /** - * Used as an error value for {@link #getBatteryLevel()} to represent bluetooth is off - * - * @hide - */ - public static final int BATTERY_LEVEL_BLUETOOTH_OFF = -100; - - /** - * Used as a Parcelable {@link BluetoothDevice} extra field in every intent - * broadcast by this class. It contains the {@link BluetoothDevice} that - * the intent applies to. - */ - public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE"; - - /** - * Used as a String extra field in {@link #ACTION_NAME_CHANGED} and {@link - * #ACTION_FOUND} intents. It contains the friendly Bluetooth name. - */ - public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME"; - - /** - * Used as an optional short extra field in {@link #ACTION_FOUND} intents. - * Contains the RSSI value of the remote device as reported by the - * Bluetooth hardware. - */ - public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI"; - - /** - * Used as an bool extra field in {@link #ACTION_FOUND} intents. - * It contains the information if device is discovered as member of a coordinated set or not. - * Pairing with device that belongs to a set would trigger pairing with the rest of set members. - * See Bluetooth CSIP specification for more details. - */ - public static final String EXTRA_IS_COORDINATED_SET_MEMBER = - "android.bluetooth.extra.IS_COORDINATED_SET_MEMBER"; - - /** - * Used as a Parcelable {@link BluetoothClass} extra field in {@link - * #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents. - */ - public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS"; - - /** - * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. - * Contains the bond state of the remote device. - * <p>Possible values are: - * {@link #BOND_NONE}, - * {@link #BOND_BONDING}, - * {@link #BOND_BONDED}. - */ - public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE"; - /** - * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. - * Contains the previous bond state of the remote device. - * <p>Possible values are: - * {@link #BOND_NONE}, - * {@link #BOND_BONDING}, - * {@link #BOND_BONDED}. - */ - public static final String EXTRA_PREVIOUS_BOND_STATE = - "android.bluetooth.device.extra.PREVIOUS_BOND_STATE"; - /** - * Indicates the remote device is not bonded (paired). - * <p>There is no shared link key with the remote device, so communication - * (if it is allowed at all) will be unauthenticated and unencrypted. - */ - public static final int BOND_NONE = 10; - /** - * Indicates bonding (pairing) is in progress with the remote device. - */ - public static final int BOND_BONDING = 11; - /** - * Indicates the remote device is bonded (paired). - * <p>A shared link keys exists locally for the remote device, so - * communication can be authenticated and encrypted. - * <p><i>Being bonded (paired) with a remote device does not necessarily - * mean the device is currently connected. It just means that the pending - * procedure was completed at some earlier time, and the link key is still - * stored locally, ready to use on the next connection. - * </i> - */ - public static final int BOND_BONDED = 12; - - /** - * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} - * intents for unbond reason. - * - * @hide - */ - @UnsupportedAppUsage - public static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON"; - - /** - * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} - * intents to indicate pairing method used. Possible values are: - * {@link #PAIRING_VARIANT_PIN}, - * {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION}, - */ - public static final String EXTRA_PAIRING_VARIANT = - "android.bluetooth.device.extra.PAIRING_VARIANT"; - - /** - * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} - * intents as the value of passkey. - */ - public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY"; - - /** - * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} - * intents as the value of passkey. - * @hide - */ - public static final String EXTRA_PAIRING_INITIATOR = - "android.bluetooth.device.extra.PAIRING_INITIATOR"; - - /** - * Bluetooth pairing initiator, Foreground App - * @hide - */ - public static final int EXTRA_PAIRING_INITIATOR_FOREGROUND = 1; - - /** - * Bluetooth pairing initiator, Background - * @hide - */ - public static final int EXTRA_PAIRING_INITIATOR_BACKGROUND = 2; - - /** - * Bluetooth device type, Unknown - */ - public static final int DEVICE_TYPE_UNKNOWN = 0; - - /** - * Bluetooth device type, Classic - BR/EDR devices - */ - public static final int DEVICE_TYPE_CLASSIC = 1; - - /** - * Bluetooth device type, Low Energy - LE-only - */ - public static final int DEVICE_TYPE_LE = 2; - - /** - * Bluetooth device type, Dual Mode - BR/EDR/LE - */ - public static final int DEVICE_TYPE_DUAL = 3; - - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final String ACTION_SDP_RECORD = - "android.bluetooth.device.action.SDP_RECORD"; - - /** @hide */ - @IntDef(prefix = "METADATA_", value = { - METADATA_MANUFACTURER_NAME, - METADATA_MODEL_NAME, - METADATA_SOFTWARE_VERSION, - METADATA_HARDWARE_VERSION, - METADATA_COMPANION_APP, - METADATA_MAIN_ICON, - METADATA_IS_UNTETHERED_HEADSET, - METADATA_UNTETHERED_LEFT_ICON, - METADATA_UNTETHERED_RIGHT_ICON, - METADATA_UNTETHERED_CASE_ICON, - METADATA_UNTETHERED_LEFT_BATTERY, - METADATA_UNTETHERED_RIGHT_BATTERY, - METADATA_UNTETHERED_CASE_BATTERY, - METADATA_UNTETHERED_LEFT_CHARGING, - METADATA_UNTETHERED_RIGHT_CHARGING, - METADATA_UNTETHERED_CASE_CHARGING, - METADATA_ENHANCED_SETTINGS_UI_URI, - METADATA_DEVICE_TYPE, - METADATA_MAIN_BATTERY, - METADATA_MAIN_CHARGING, - METADATA_MAIN_LOW_BATTERY_THRESHOLD, - METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD, - METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD, - METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD}) - @Retention(RetentionPolicy.SOURCE) - public @interface MetadataKey{} - - /** - * Maximum length of a metadata entry, this is to avoid exploding Bluetooth - * disk usage - * @hide - */ - @SystemApi - public static final int METADATA_MAX_LENGTH = 2048; - - /** - * Manufacturer name of this Bluetooth device - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MANUFACTURER_NAME = 0; - - /** - * Model name of this Bluetooth device - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MODEL_NAME = 1; - - /** - * Software version of this Bluetooth device - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_SOFTWARE_VERSION = 2; - - /** - * Hardware version of this Bluetooth device - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_HARDWARE_VERSION = 3; - - /** - * Package name of the companion app, if any - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_COMPANION_APP = 4; - - /** - * URI to the main icon shown on the settings UI - * Data type should be {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MAIN_ICON = 5; - - /** - * Whether this device is an untethered headset with left, right and case - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_IS_UNTETHERED_HEADSET = 6; - - /** - * URI to icon of the left headset - * Data type should be {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_LEFT_ICON = 7; - - /** - * URI to icon of the right headset - * Data type should be {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_RIGHT_ICON = 8; - - /** - * URI to icon of the headset charging case - * Data type should be {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_CASE_ICON = 9; - - /** - * Battery level of left headset - * Data type should be {@String} 0-100 as {@link Byte} array, otherwise - * as invalid. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10; - - /** - * Battery level of rigth headset - * Data type should be {@String} 0-100 as {@link Byte} array, otherwise - * as invalid. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11; - - /** - * Battery level of the headset charging case - * Data type should be {@String} 0-100 as {@link Byte} array, otherwise - * as invalid. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_CASE_BATTERY = 12; - - /** - * Whether the left headset is charging - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13; - - /** - * Whether the right headset is charging - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14; - - /** - * Whether the headset charging case is charging - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_CASE_CHARGING = 15; - - /** - * URI to the enhanced settings UI slice - * Data type should be {@String} as {@link Byte} array, null means - * the UI does not exist. - * @hide - */ - @SystemApi - public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; - - /** - * Type of the Bluetooth device, must be within the list of - * BluetoothDevice.DEVICE_TYPE_* - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_DEVICE_TYPE = 17; - - /** - * Battery level of the Bluetooth device, use when the Bluetooth device - * does not support HFP battery indicator. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MAIN_BATTERY = 18; - - /** - * Whether the device is charging. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MAIN_CHARGING = 19; - - /** - * The battery threshold of the Bluetooth device to show low battery icon. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20; - - /** - * The battery threshold of the left headset to show low battery icon. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21; - - /** - * The battery threshold of the right headset to show low battery icon. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22; - - /** - * The battery threshold of the case to show low battery icon. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23; - - /** - * Device type which is used in METADATA_DEVICE_TYPE - * Indicates this Bluetooth device is a standard Bluetooth accessory or - * not listed in METADATA_DEVICE_TYPE_*. - * @hide - */ - @SystemApi - public static final String DEVICE_TYPE_DEFAULT = "Default"; - - /** - * Device type which is used in METADATA_DEVICE_TYPE - * Indicates this Bluetooth device is a watch. - * @hide - */ - @SystemApi - public static final String DEVICE_TYPE_WATCH = "Watch"; - - /** - * Device type which is used in METADATA_DEVICE_TYPE - * Indicates this Bluetooth device is an untethered headset. - * @hide - */ - @SystemApi - public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset"; - - /** - * Broadcast Action: This intent is used to broadcast the {@link UUID} - * wrapped as a {@link android.os.ParcelUuid} of the remote device after it - * has been fetched. This intent is sent only when the UUIDs of the remote - * device are requested to be fetched using Service Discovery Protocol - * <p> Always contains the extra field {@link #EXTRA_DEVICE} - * <p> Always contains the extra field {@link #EXTRA_UUID} - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_UUID = - "android.bluetooth.device.action.UUID"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MAS_INSTANCE = - "android.bluetooth.device.action.MAS_INSTANCE"; - - /** - * Broadcast Action: Indicates a failure to retrieve the name of a remote - * device. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * - * @hide - */ - //TODO: is this actually useful? - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_NAME_FAILED = - "android.bluetooth.device.action.NAME_FAILED"; - - /** - * Broadcast Action: This intent is used to broadcast PAIRING REQUEST - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PAIRING_REQUEST = - "android.bluetooth.device.action.PAIRING_REQUEST"; - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage - public static final String ACTION_PAIRING_CANCEL = - "android.bluetooth.device.action.PAIRING_CANCEL"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_ACCESS_REQUEST = - "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_ACCESS_REPLY = - "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_ACCESS_CANCEL = - "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL"; - - /** - * Intent to broadcast silence mode changed. - * Alway contains the extra field {@link #EXTRA_DEVICE} - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @SystemApi - public static final String ACTION_SILENCE_MODE_CHANGED = - "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent. - * - * @hide - */ - public static final String EXTRA_ACCESS_REQUEST_TYPE = - "android.bluetooth.device.extra.ACCESS_REQUEST_TYPE"; - - /** @hide */ - public static final int REQUEST_TYPE_PROFILE_CONNECTION = 1; - - /** @hide */ - public static final int REQUEST_TYPE_PHONEBOOK_ACCESS = 2; - - /** @hide */ - public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3; - - /** @hide */ - public static final int REQUEST_TYPE_SIM_ACCESS = 4; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents, - * Contains package name to return reply intent to. - * - * @hide - */ - public static final String EXTRA_PACKAGE_NAME = "android.bluetooth.device.extra.PACKAGE_NAME"; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents, - * Contains class name to return reply intent to. - * - * @hide - */ - public static final String EXTRA_CLASS_NAME = "android.bluetooth.device.extra.CLASS_NAME"; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intent. - * - * @hide - */ - public static final String EXTRA_CONNECTION_ACCESS_RESULT = - "android.bluetooth.device.extra.CONNECTION_ACCESS_RESULT"; - - /** @hide */ - public static final int CONNECTION_ACCESS_YES = 1; - - /** @hide */ - public static final int CONNECTION_ACCESS_NO = 2; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intents, - * Contains boolean to indicate if the allowed response is once-for-all so that - * next request will be granted without asking user again. - * - * @hide - */ - public static final String EXTRA_ALWAYS_ALLOWED = - "android.bluetooth.device.extra.ALWAYS_ALLOWED"; - - /** - * A bond attempt succeeded - * - * @hide - */ - public static final int BOND_SUCCESS = 0; - - /** - * A bond attempt failed because pins did not match, or remote device did - * not respond to pin request in time - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_AUTH_FAILED = 1; - - /** - * A bond attempt failed because the other side explicitly rejected - * bonding - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_AUTH_REJECTED = 2; - - /** - * A bond attempt failed because we canceled the bonding process - * - * @hide - */ - public static final int UNBOND_REASON_AUTH_CANCELED = 3; - - /** - * A bond attempt failed because we could not contact the remote device - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4; - - /** - * A bond attempt failed because a discovery is in progress - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5; - - /** - * A bond attempt failed because of authentication timeout - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_AUTH_TIMEOUT = 6; - - /** - * A bond attempt failed because of repeated attempts - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7; - - /** - * A bond attempt failed because we received an Authentication Cancel - * by remote end - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_REMOTE_AUTH_CANCELED = 8; - - /** - * An existing bond was explicitly revoked - * - * @hide - */ - public static final int UNBOND_REASON_REMOVED = 9; - - /** - * The user will be prompted to enter a pin or - * an app will enter a pin for user. - */ - public static final int PAIRING_VARIANT_PIN = 0; - - /** - * The user will be prompted to enter a passkey - * - * @hide - */ - public static final int PAIRING_VARIANT_PASSKEY = 1; - - /** - * The user will be prompted to confirm the passkey displayed on the screen or - * an app will confirm the passkey for the user. - */ - public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; - - /** - * The user will be prompted to accept or deny the incoming pairing request - * - * @hide - */ - public static final int PAIRING_VARIANT_CONSENT = 3; - - /** - * The user will be prompted to enter the passkey displayed on remote device - * This is used for Bluetooth 2.1 pairing. - * - * @hide - */ - public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4; - - /** - * The user will be prompted to enter the PIN displayed on remote device. - * This is used for Bluetooth 2.0 pairing. - * - * @hide - */ - public static final int PAIRING_VARIANT_DISPLAY_PIN = 5; - - /** - * The user will be prompted to accept or deny the OOB pairing request - * - * @hide - */ - public static final int PAIRING_VARIANT_OOB_CONSENT = 6; - - /** - * The user will be prompted to enter a 16 digit pin or - * an app will enter a 16 digit pin for user. - * - * @hide - */ - public static final int PAIRING_VARIANT_PIN_16_DIGITS = 7; - - /** - * Used as an extra field in {@link #ACTION_UUID} intents, - * Contains the {@link android.os.ParcelUuid}s of the remote device which - * is a parcelable version of {@link UUID}. - */ - public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; - - /** @hide */ - public static final String EXTRA_SDP_RECORD = - "android.bluetooth.device.extra.SDP_RECORD"; - - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final String EXTRA_SDP_SEARCH_STATUS = - "android.bluetooth.device.extra.SDP_SEARCH_STATUS"; - - /** @hide */ - @IntDef(prefix = "ACCESS_", value = {ACCESS_UNKNOWN, - ACCESS_ALLOWED, ACCESS_REJECTED}) - @Retention(RetentionPolicy.SOURCE) - public @interface AccessPermission{} - - /** - * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission}, - * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. - * - * @hide - */ - @SystemApi - public static final int ACCESS_UNKNOWN = 0; - - /** - * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission}, - * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. - * - * @hide - */ - @SystemApi - public static final int ACCESS_ALLOWED = 1; - - /** - * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission}, - * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. - * - * @hide - */ - @SystemApi - public static final int ACCESS_REJECTED = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "TRANSPORT_" }, - value = { - /** Allow host to automatically select a transport (dual-mode only) */ - TRANSPORT_AUTO, - /** Use Classic or BR/EDR transport.*/ - TRANSPORT_BREDR, - /** Use Low Energy transport.*/ - TRANSPORT_LE, - } - ) - public @interface Transport {} - - /** - * No preference of physical transport for GATT connections to remote dual-mode devices - */ - public static final int TRANSPORT_AUTO = 0; - - /** - * Prefer BR/EDR transport for GATT connections to remote dual-mode devices - */ - public static final int TRANSPORT_BREDR = 1; - - /** - * Prefer LE transport for GATT connections to remote dual-mode devices - */ - public static final int TRANSPORT_LE = 2; - - /** - * Bluetooth LE 1M PHY. Used to refer to LE 1M Physical Channel for advertising, scanning or - * connection. - */ - public static final int PHY_LE_1M = 1; - - /** - * Bluetooth LE 2M PHY. Used to refer to LE 2M Physical Channel for advertising, scanning or - * connection. - */ - public static final int PHY_LE_2M = 2; - - /** - * Bluetooth LE Coded PHY. Used to refer to LE Coded Physical Channel for advertising, scanning - * or connection. - */ - public static final int PHY_LE_CODED = 3; - - /** - * Bluetooth LE 1M PHY mask. Used to specify LE 1M Physical Channel as one of many available - * options in a bitmask. - */ - public static final int PHY_LE_1M_MASK = 1; - - /** - * Bluetooth LE 2M PHY mask. Used to specify LE 2M Physical Channel as one of many available - * options in a bitmask. - */ - public static final int PHY_LE_2M_MASK = 2; - - /** - * Bluetooth LE Coded PHY mask. Used to specify LE Coded Physical Channel as one of many - * available options in a bitmask. - */ - public static final int PHY_LE_CODED_MASK = 4; - - /** - * No preferred coding when transmitting on the LE Coded PHY. - */ - public static final int PHY_OPTION_NO_PREFERRED = 0; - - /** - * Prefer the S=2 coding to be used when transmitting on the LE Coded PHY. - */ - public static final int PHY_OPTION_S2 = 1; - - /** - * Prefer the S=8 coding to be used when transmitting on the LE Coded PHY. - */ - public static final int PHY_OPTION_S8 = 2; - - - /** @hide */ - public static final String EXTRA_MAS_INSTANCE = - "android.bluetooth.device.extra.MAS_INSTANCE"; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "ADDRESS_TYPE_" }, - value = { - /** Hardware MAC Address */ - ADDRESS_TYPE_PUBLIC, - /** Address is either resolvable, non-resolvable or static.*/ - ADDRESS_TYPE_RANDOM, - } - ) - public @interface AddressType {} - - /** Hardware MAC Address of the device */ - public static final int ADDRESS_TYPE_PUBLIC = 0; - /** Address is either resolvable, non-resolvable or static. */ - public static final int ADDRESS_TYPE_RANDOM = 1; - - private static final String NULL_MAC_ADDRESS = "00:00:00:00:00:00"; - - /** - * Lazy initialization. Guaranteed final after first object constructed, or - * getService() called. - * TODO: Unify implementation of sService amongst BluetoothFoo API's - */ - private static volatile IBluetooth sService; - - private final String mAddress; - @AddressType private final int mAddressType; - - private AttributionSource mAttributionSource; - - /*package*/ - @UnsupportedAppUsage - static IBluetooth getService() { - synchronized (BluetoothDevice.class) { - if (sService == null) { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - sService = adapter.getBluetoothService(sStateChangeCallback); - } - } - return sService; - } - - static IBluetoothManagerCallback sStateChangeCallback = new IBluetoothManagerCallback.Stub() { - - public void onBluetoothServiceUp(IBluetooth bluetoothService) - throws RemoteException { - synchronized (BluetoothDevice.class) { - if (sService == null) { - sService = bluetoothService; - } - } - } - - public void onBluetoothServiceDown() - throws RemoteException { - synchronized (BluetoothDevice.class) { - sService = null; - } - } - - public void onBrEdrDown() { - if (DBG) Log.d(TAG, "onBrEdrDown: reached BLE ON state"); - } - - public void onOobData(@Transport int transport, OobData oobData) { - if (DBG) Log.d(TAG, "onOobData: got data"); - } - }; - - /** - * Create a new BluetoothDevice - * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB", - * and is validated in this constructor. - * - * @param address valid Bluetooth MAC address - * @param attributionSource attribution for permission-protected calls - * @throws RuntimeException Bluetooth is not available on this platform - * @throws IllegalArgumentException address is invalid - * @hide - */ - @UnsupportedAppUsage - /*package*/ BluetoothDevice(String address) { - getService(); // ensures sService is initialized - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - throw new IllegalArgumentException(address + " is not a valid Bluetooth address"); - } - - mAddress = address; - mAddressType = ADDRESS_TYPE_PUBLIC; - mAttributionSource = AttributionSource.myAttributionSource(); - } - - /** {@hide} */ - public void setAttributionSource(@NonNull AttributionSource attributionSource) { - mAttributionSource = attributionSource; - } - - /** {@hide} */ - public void prepareToEnterProcess(@NonNull AttributionSource attributionSource) { - setAttributionSource(attributionSource); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothDevice) { - return mAddress.equals(((BluetoothDevice) o).getAddress()); - } - return false; - } - - @Override - public int hashCode() { - return mAddress.hashCode(); - } - - /** - * Returns a string representation of this BluetoothDevice. - * <p>Currently this is the Bluetooth hardware address, for example - * "00:11:22:AA:BB:CC". However, you should always use {@link #getAddress} - * if you explicitly require the Bluetooth hardware address in case the - * {@link #toString} representation changes in the future. - * - * @return string representation of this BluetoothDevice - */ - @Override - public String toString() { - return mAddress; - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothDevice> CREATOR = - new Parcelable.Creator<BluetoothDevice>() { - public BluetoothDevice createFromParcel(Parcel in) { - return new BluetoothDevice(in.readString()); - } - - public BluetoothDevice[] newArray(int size) { - return new BluetoothDevice[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mAddress); - } - - /** - * Returns the hardware address of this BluetoothDevice. - * <p> For example, "00:11:22:AA:BB:CC". - * - * @return Bluetooth hardware address as string - */ - public String getAddress() { - if (DBG) Log.d(TAG, "mAddress: " + mAddress); - return mAddress; - } - - /** - * Returns the anonymized hardware address of this BluetoothDevice. The first three octets - * will be suppressed for anonymization. - * <p> For example, "XX:XX:XX:AA:BB:CC". - * - * @return Anonymized bluetooth hardware address as string - * @hide - */ - public String getAnonymizedAddress() { - return "XX:XX:XX" + getAddress().substring(8); - } - - /** - * Get the friendly Bluetooth name of the remote device. - * - * <p>The local adapter will automatically retrieve remote names when - * performing a device scan, and will cache them. This method just returns - * the name for this device from the cache. - * - * @return the Bluetooth name, or null if there was a problem. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public String getName() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get Remote Device name"); - return null; - } - try { - String name = service.getRemoteName(this, mAttributionSource); - if (name != null) { - // remove whitespace characters from the name - return name - .replace('\t', ' ') - .replace('\n', ' ') - .replace('\r', ' '); - } - return null; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Get the Bluetooth device type of the remote device. - * - * @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE} {@link - * #DEVICE_TYPE_DUAL}. {@link #DEVICE_TYPE_UNKNOWN} if it's not available - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getType() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get Remote Device type"); - return DEVICE_TYPE_UNKNOWN; - } - try { - return service.getRemoteType(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return DEVICE_TYPE_UNKNOWN; - } - - /** - * Get the locally modifiable name (alias) of the remote Bluetooth device. - * - * @return the Bluetooth alias, the friendly device name if no alias, or - * null if there was a problem - */ - @Nullable - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public String getAlias() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias"); - return null; - } - try { - String alias = service.getRemoteAliasWithAttribution(this, mAttributionSource); - if (alias == null) { - return getName(); - } - return alias - .replace('\t', ' ') - .replace('\n', ' ') - .replace('\r', ' '); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, - BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED - }) - public @interface SetAliasReturnValues{} - - /** - * Sets the locally modifiable name (alias) of the remote Bluetooth device. This method - * overwrites the previously stored alias. The new alias is saved in local - * storage so that the change is preserved over power cycles. - * - * <p>This method requires the calling app to be associated with Companion Device Manager (see - * {@link android.companion.CompanionDeviceManager#associate(AssociationRequest, - * android.companion.CompanionDeviceManager.Callback, Handler)}) and have the - * {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission. Alternatively, if the - * caller has the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission, they can - * bypass the Companion Device Manager association requirement as well as other permission - * requirements. - * - * @param alias is the new locally modifiable name for the remote Bluetooth device which must - * be the empty string. If null, we clear the alias. - * @return whether the alias was successfully changed - * @throws IllegalArgumentException if the alias is the empty string - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @SetAliasReturnValues int setAlias(@Nullable String alias) { - if (alias != null && alias.isEmpty()) { - throw new IllegalArgumentException("alias cannot be the empty string"); - } - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot set Remote Device name"); - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - try { - return service.setRemoteAlias(this, alias, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Get the most recent identified battery level of this Bluetooth device - * - * @return Battery level in percents from 0 to 100, {@link #BATTERY_LEVEL_BLUETOOTH_OFF} if - * Bluetooth is disabled or {@link #BATTERY_LEVEL_UNKNOWN} if device is disconnected, or does - * not have any battery reporting service, or return value is invalid - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getBatteryLevel() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "Bluetooth disabled. Cannot get remote device battery level"); - return BATTERY_LEVEL_BLUETOOTH_OFF; - } - try { - return service.getBatteryLevel(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return BATTERY_LEVEL_UNKNOWN; - } - - /** - * Start the bonding (pairing) process with the remote device. - * <p>This is an asynchronous call, it will return immediately. Register - * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when - * the bonding process completes, and its result. - * <p>Android system services will handle the necessary user interactions - * to confirm and complete the bonding process. - * - * @return false on immediate error, true if bonding will begin - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean createBond() { - return createBond(TRANSPORT_AUTO); - } - - /** - * Start the bonding (pairing) process with the remote device using the - * specified transport. - * - * <p>This is an asynchronous call, it will return immediately. Register - * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when - * the bonding process completes, and its result. - * <p>Android system services will handle the necessary user interactions - * to confirm and complete the bonding process. - * - * @param transport The transport to use for the pairing procedure. - * @return false on immediate error, true if bonding will begin - * @throws IllegalArgumentException if an invalid transport was specified - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean createBond(int transport) { - return createBondInternal(transport, null, null); - } - - /** - * Start the bonding (pairing) process with the remote device using the - * Out Of Band mechanism. - * - * <p>This is an asynchronous call, it will return immediately. Register - * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when - * the bonding process completes, and its result. - * - * <p>Android system services will handle the necessary user interactions - * to confirm and complete the bonding process. - * - * <p>There are two possible versions of OOB Data. This data can come in as - * P192 or P256. This is a reference to the cryptography used to generate the key. - * The caller may pass one or both. If both types of data are passed, then the - * P256 data will be preferred, and thus used. - * - * @param transport - Transport to use - * @param remoteP192Data - Out Of Band data (P192) or null - * @param remoteP256Data - Out Of Band data (P256) or null - * @return false on immediate error, true if bonding will begin - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data, - @Nullable OobData remoteP256Data) { - if (remoteP192Data == null && remoteP256Data == null) { - throw new IllegalArgumentException( - "One or both arguments for the OOB data types are required to not be null." - + " Please use createBond() instead if you do not have OOB data to pass."); - } - return createBondInternal(transport, remoteP192Data, remoteP256Data); - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data, - @Nullable OobData remoteP256Data) { - final IBluetooth service = sService; - if (service == null) { - Log.w(TAG, "BT not enabled, createBondOutOfBand failed"); - return false; - } - if (NULL_MAC_ADDRESS.equals(mAddress)) { - Log.e(TAG, "Unable to create bond, invalid address " + mAddress); - return false; - } - try { - return service.createBond( - this, transport, remoteP192Data, remoteP256Data, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Gets whether bonding was initiated locally - * - * @return true if bonding is initiated locally, false otherwise - * - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isBondingInitiatedLocally() { - final IBluetooth service = sService; - if (service == null) { - Log.w(TAG, "BT not enabled, isBondingInitiatedLocally failed"); - return false; - } - try { - return service.isBondingInitiatedLocally(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Cancel an in-progress bonding request started with {@link #createBond}. - * - * @return true on success, false on error - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean cancelBondProcess() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot cancel Remote Device bond"); - return false; - } - try { - Log.i(TAG, "cancelBondProcess() for device " + getAddress() - + " called by pid: " + Process.myPid() - + " tid: " + Process.myTid()); - return service.cancelBondProcess(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Remove bond (pairing) with the remote device. - * <p>Delete the link key associated with the remote device, and - * immediately terminate connections to that device that require - * authentication and encryption. - * - * @return true on success, false on error - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean removeBond() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond"); - return false; - } - try { - Log.i(TAG, "removeBond() for device " + getAddress() - + " called by pid: " + Process.myPid() - + " tid: " + Process.myTid()); - return service.removeBond(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - private static final String BLUETOOTH_BONDING_CACHE_PROPERTY = - "cache_key.bluetooth.get_bond_state"; - private final PropertyInvalidatedCache<BluetoothDevice, Integer> mBluetoothBondCache = - new PropertyInvalidatedCache<BluetoothDevice, Integer>( - 8, BLUETOOTH_BONDING_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Integer recompute(BluetoothDevice query) { - try { - return sService.getBondState(query, mAttributionSource); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - }; - - /** @hide */ - public void disableBluetoothGetBondStateCache() { - mBluetoothBondCache.disableLocal(); - } - - /** @hide */ - public static void invalidateBluetoothGetBondStateCache() { - PropertyInvalidatedCache.invalidateCache(BLUETOOTH_BONDING_CACHE_PROPERTY); - } - - /** - * Get the bond state of the remote device. - * <p>Possible values for the bond state are: - * {@link #BOND_NONE}, - * {@link #BOND_BONDING}, - * {@link #BOND_BONDED}. - * - * @return the bond state - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public int getBondState() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get bond state"); - return BOND_NONE; - } - try { - return mBluetoothBondCache.query(this); - } catch (RuntimeException e) { - if (e.getCause() instanceof RemoteException) { - Log.e(TAG, "", e); - } else { - throw e; - } - } - return BOND_NONE; - } - - /** - * Checks whether this bluetooth device is associated with CDM and meets the criteria to skip - * the bluetooth pairing dialog because it has been already consented by the CDM prompt. - * - * @return true if we can bond without the dialog, false otherwise - * - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean canBondWithoutDialog() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot check if we can skip pairing dialog"); - return false; - } - try { - if (DBG) Log.d(TAG, "canBondWithoutDialog, device: " + this); - return service.canBondWithoutDialog(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, - BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED - }) - public @interface ConnectionReturnValues{} - - /** - * Connects all user enabled and supported bluetooth profiles between the local and remote - * device. If no profiles are user enabled (e.g. first connection), we connect all supported - * profiles. If the device is not already connected, this will page the device before initiating - * profile connections. Connection is asynchronous and you should listen to each profile's - * broadcast intent ACTION_CONNECTION_STATE_CHANGED to verify whether connection was successful. - * For example, to verify a2dp is connected, you would listen for - * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED} - * - * @return whether the messages were successfully sent to try to connect all profiles - * @throws IllegalArgumentException if the device address is invalid - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public @ConnectionReturnValues int connect() { - if (!BluetoothAdapter.checkBluetoothAddress(getAddress())) { - throw new IllegalArgumentException("device cannot have an invalid address"); - } - - try { - if (sService == null) { - Log.e(TAG, "BT not enabled. Cannot connect to remote device."); - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - return sService.connectAllEnabledProfiles(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Disconnects all connected bluetooth profiles between the local and remote device. - * Disconnection is asynchronous and you should listen to each profile's broadcast intent - * ACTION_CONNECTION_STATE_CHANGED to verify whether disconnection was successful. For example, - * to verify a2dp is disconnected, you would listen for - * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED} - * - * @return whether the messages were successfully sent to try to disconnect all profiles - * @throws IllegalArgumentException if the device address is invalid - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionReturnValues int disconnect() { - if (!BluetoothAdapter.checkBluetoothAddress(getAddress())) { - throw new IllegalArgumentException("device cannot have an invalid address"); - } - - try { - if (sService == null) { - Log.e(TAG, "BT not enabled. Cannot disconnect from remote device."); - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - return sService.disconnectAllEnabledProfiles(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Returns whether there is an open connection to this device. - * - * @return True if there is at least one open connection to this device. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isConnected() { - final IBluetooth service = sService; - if (service == null) { - // BT is not enabled, we cannot be connected. - return false; - } - try { - return service.getConnectionStateWithAttribution(this, mAttributionSource) - != CONNECTION_STATE_DISCONNECTED; - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - } - - /** - * Returns whether there is an open connection to this device - * that has been encrypted. - * - * @return True if there is at least one encrypted connection to this device. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isEncrypted() { - final IBluetooth service = sService; - if (service == null) { - // BT is not enabled, we cannot be connected. - return false; - } - try { - return service.getConnectionStateWithAttribution(this, mAttributionSource) - > CONNECTION_STATE_CONNECTED; - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - } - - /** - * Get the Bluetooth class of the remote device. - * - * @return Bluetooth class object, or null on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothClass getBluetoothClass() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get Bluetooth Class"); - return null; - } - try { - int classInt = service.getRemoteClass(this, mAttributionSource); - if (classInt == BluetoothClass.ERROR) return null; - return new BluetoothClass(classInt); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Returns the supported features (UUIDs) of the remote device. - * - * <p>This method does not start a service discovery procedure to retrieve the UUIDs - * from the remote device. Instead, the local cached copy of the service - * UUIDs are returned. - * <p>Use {@link #fetchUuidsWithSdp} if fresh UUIDs are desired. - * - * @return the supported features (UUIDs) of the remote device, or null on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public ParcelUuid[] getUuids() { - final IBluetooth service = sService; - if (service == null || !isBluetoothEnabled()) { - Log.e(TAG, "BT not enabled. Cannot get remote device Uuids"); - return null; - } - try { - return service.getRemoteUuids(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Perform a service discovery on the remote device to get the UUIDs supported. - * - * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent, - * with the UUIDs supported by the remote end. If there is an error - * in getting the SDP records or if the process takes a long time, or the device is bonding and - * we have its UUIDs cached, {@link #ACTION_UUID} intent is sent with the UUIDs that is - * currently present in the cache. Clients should use the {@link #getUuids} to get UUIDs - * if service discovery is not to be performed. If there is an ongoing bonding process, - * service discovery or device inquiry, the request will be queued. - * - * @return False if the check fails, True if the process of initiating an ACL connection - * to the remote device was started or cached UUIDs will be broadcast. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean fetchUuidsWithSdp() { - return fetchUuidsWithSdp(TRANSPORT_AUTO); - } - - /** - * Perform a service discovery on the remote device to get the UUIDs supported with the - * specific transport. - * - * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent, - * with the UUIDs supported by the remote end. If there is an error - * in getting the SDP or GATT records or if the process takes a long time, or the device - * is bonding and we have its UUIDs cached, {@link #ACTION_UUID} intent is sent with the - * UUIDs that is currently present in the cache. Clients should use the {@link #getUuids} - * to get UUIDs if service discovery is not to be performed. If there is an ongoing bonding - * process, service discovery or device inquiry, the request will be queued. - * - * @param transport - provide type of transport (e.g. LE or Classic). - * @return False if the check fails, True if the process of initiating an ACL connection - * to the remote device was started or cached UUIDs will be broadcast with the specific - * transport. - * - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean fetchUuidsWithSdp(@Transport int transport) { - final IBluetooth service = sService; - if (service == null || !isBluetoothEnabled()) { - Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp"); - return false; - } - try { - return service.fetchRemoteUuidsWithAttribution(this, transport, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Perform a service discovery on the remote device to get the SDP records associated - * with the specified UUID. - * - * <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent, - * with the SDP records found on the remote end. If there is an error - * in getting the SDP records or if the process takes a long time, - * {@link #ACTION_SDP_RECORD} intent is sent with an status value in - * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0. - * Detailed status error codes can be found by members of the Bluetooth package in - * the AbstractionLayer class. - * <p>The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}. - * The object type will match one of the SdpXxxRecord types, depending on the UUID searched - * for. - * - * @return False if the check fails, True if the process - * of initiating an ACL connection to the remote device - * was started. - */ - /** @hide */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sdpSearch(ParcelUuid uuid) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot query remote device sdp records"); - return false; - } - try { - return service.sdpSearch(this, uuid, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} - * - * @return true pin has been set false for error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setPin(byte[] pin) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot set Remote Device pin"); - return false; - } - try { - return service.setPin(this, true, pin.length, pin, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} - * - * @return true pin has been set false for error - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setPin(@NonNull String pin) { - byte[] pinBytes = convertPinToBytes(pin); - if (pinBytes == null) { - return false; - } - return setPin(pinBytes); - } - - /** - * Confirm passkey for {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION} pairing. - * - * @return true confirmation has been sent out false for error - */ - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPairingConfirmation(boolean confirm) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot set pairing confirmation"); - return false; - } - try { - return service.setPairingConfirmation(this, confirm, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Cancels pairing to this device - * - * @return true if pairing cancelled successfully, false otherwise - * - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean cancelPairing() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot cancel pairing"); - return false; - } - try { - return service.cancelBondProcess(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - boolean isBluetoothEnabled() { - boolean ret = false; - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null && adapter.isEnabled()) { - ret = true; - } - return ret; - } - - /** - * Gets whether the phonebook access is allowed for this bluetooth device - * - * @return Whether the phonebook access is allowed to this device. Can be {@link - * #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @AccessPermission int getPhonebookAccessPermission() { - final IBluetooth service = sService; - if (service == null) { - return ACCESS_UNKNOWN; - } - try { - return service.getPhonebookAccessPermission(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return ACCESS_UNKNOWN; - } - - /** - * Sets whether the {@link BluetoothDevice} enters silence mode. Audio will not - * be routed to the {@link BluetoothDevice} if set to {@code true}. - * - * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice} - * is an active device (for A2DP or HFP), the active device for that profile - * will be set to null. - * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP - * active device is null, the {@link BluetoothDevice} will be set as the - * active device for that profile. - * If the {@link BluetoothDevice} is disconnected, it exits silence mode. - * If the {@link BluetoothDevice} is set as the active device for A2DP or - * HFP, while silence mode is enabled, then the device will exit silence mode. - * If the {@link BluetoothDevice} is in silence mode, AVRCP position change - * event and HFP AG indicators will be disabled. - * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot - * enter silence mode. - * - * @param silence true to enter silence mode, false to exit - * @return true on success, false on error. - * @throws IllegalStateException if Bluetooth is not turned ON. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setSilenceMode(boolean silence) { - final IBluetooth service = sService; - if (service == null) { - throw new IllegalStateException("Bluetooth is not turned ON"); - } - try { - return service.setSilenceMode(this, silence, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "setSilenceMode fail", e); - return false; - } - } - - /** - * Check whether the {@link BluetoothDevice} is in silence mode - * - * @return true on device in silence mode, otherwise false. - * @throws IllegalStateException if Bluetooth is not turned ON. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean isInSilenceMode() { - final IBluetooth service = sService; - if (service == null) { - throw new IllegalStateException("Bluetooth is not turned ON"); - } - try { - return service.getSilenceMode(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "isInSilenceMode fail", e); - return false; - } - } - - /** - * Sets whether the phonebook access is allowed to this device. - * - * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link - * #ACCESS_REJECTED}. - * @return Whether the value has been successfully set. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPhonebookAccessPermission(@AccessPermission int value) { - final IBluetooth service = sService; - if (service == null) { - return false; - } - try { - return service.setPhonebookAccessPermission(this, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Gets whether message access is allowed to this bluetooth device - * - * @return Whether the message access is allowed to this device. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @AccessPermission int getMessageAccessPermission() { - final IBluetooth service = sService; - if (service == null) { - return ACCESS_UNKNOWN; - } - try { - return service.getMessageAccessPermission(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return ACCESS_UNKNOWN; - } - - /** - * Sets whether the message access is allowed to this device. - * - * @param value Can be {@link #ACCESS_UNKNOWN} if the device is unbonded, - * {@link #ACCESS_ALLOWED} if the permission is being granted, or {@link #ACCESS_REJECTED} if - * the permission is not being granted. - * @return Whether the value has been successfully set. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setMessageAccessPermission(@AccessPermission int value) { - // Validates param value is one of the accepted constants - if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) { - throw new IllegalArgumentException(value + "is not a valid AccessPermission value"); - } - final IBluetooth service = sService; - if (service == null) { - return false; - } - try { - return service.setMessageAccessPermission(this, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Gets whether sim access is allowed for this bluetooth device - * - * @return Whether the Sim access is allowed to this device. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @AccessPermission int getSimAccessPermission() { - final IBluetooth service = sService; - if (service == null) { - return ACCESS_UNKNOWN; - } - try { - return service.getSimAccessPermission(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return ACCESS_UNKNOWN; - } - - /** - * Sets whether the Sim access is allowed to this device. - * - * @param value Can be {@link #ACCESS_UNKNOWN} if the device is unbonded, - * {@link #ACCESS_ALLOWED} if the permission is being granted, or {@link #ACCESS_REJECTED} if - * the permission is not being granted. - * @return Whether the value has been successfully set. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setSimAccessPermission(int value) { - final IBluetooth service = sService; - if (service == null) { - return false; - } - try { - return service.setSimAccessPermission(this, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Create an RFCOMM {@link BluetoothSocket} ready to start a secure - * outgoing connection to this remote device on given channel. - * <p>The remote device will be authenticated and communication on this - * socket will be encrypted. - * <p> Use this socket only if an authenticated socket link is possible. - * Authentication refers to the authentication of the link key to - * prevent person-in-the-middle type of attacks. - * For example, for Bluetooth 2.1 devices, if any of the devices does not - * have an input and output capability or just has the ability to - * display a numeric key, a secure socket connection is not possible. - * In such a case, use {@link createInsecureRfcommSocket}. - * For more details, refer to the Security Model section 5.2 (vol 3) of - * Bluetooth Core Specification version 2.1 + EDR. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. - * <p>Valid RFCOMM channels are in range 1 to 30. - * - * @param channel RFCOMM channel to connect to - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createRfcommSocket(int channel) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel, - null); - } - - /** - * Create an L2cap {@link BluetoothSocket} ready to start a secure - * outgoing connection to this remote device on given channel. - * <p>The remote device will be authenticated and communication on this - * socket will be encrypted. - * <p> Use this socket only if an authenticated socket link is possible. - * Authentication refers to the authentication of the link key to - * prevent person-in-the-middle type of attacks. - * For example, for Bluetooth 2.1 devices, if any of the devices does not - * have an input and output capability or just has the ability to - * display a numeric key, a secure socket connection is not possible. - * In such a case, use {@link createInsecureRfcommSocket}. - * For more details, refer to the Security Model section 5.2 (vol 3) of - * Bluetooth Core Specification version 2.1 + EDR. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. - * <p>Valid L2CAP PSM channels are in range 1 to 2^16. - * - * @param channel L2cap PSM/channel to connect to - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createL2capSocket(int channel) throws IOException { - return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel, - null); - } - - /** - * Create an L2cap {@link BluetoothSocket} ready to start an insecure - * outgoing connection to this remote device on given channel. - * <p>The remote device will be not authenticated and communication on this - * socket will not be encrypted. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. - * <p>Valid L2CAP PSM channels are in range 1 to 2^16. - * - * @param channel L2cap PSM/channel to connect to - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createInsecureL2capSocket(int channel) throws IOException { - return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, false, false, this, channel, - null); - } - - /** - * Create an RFCOMM {@link BluetoothSocket} ready to start a secure - * outgoing connection to this remote device using SDP lookup of uuid. - * <p>This is designed to be used with {@link - * BluetoothAdapter#listenUsingRfcommWithServiceRecord} for peer-peer - * Bluetooth applications. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. This will also perform an SDP lookup of the given uuid to - * determine which channel to connect to. - * <p>The remote device will be authenticated and communication on this - * socket will be encrypted. - * <p> Use this socket only if an authenticated socket link is possible. - * Authentication refers to the authentication of the link key to - * prevent person-in-the-middle type of attacks. - * For example, for Bluetooth 2.1 devices, if any of the devices does not - * have an input and output capability or just has the ability to - * display a numeric key, a secure socket connection is not possible. - * In such a case, use {@link #createInsecureRfcommSocketToServiceRecord}. - * For more details, refer to the Security Model section 5.2 (vol 3) of - * Bluetooth Core Specification version 2.1 + EDR. - * <p>Hint: If you are connecting to a Bluetooth serial board then try - * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB. - * However if you are connecting to an Android peer then please generate - * your own unique UUID. - * - * @param uuid service record uuid to lookup RFCOMM channel - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - - return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1, - new ParcelUuid(uuid)); - } - - /** - * Create an RFCOMM {@link BluetoothSocket} socket ready to start an insecure - * outgoing connection to this remote device using SDP lookup of uuid. - * <p> The communication channel will not have an authenticated link key - * i.e it will be subject to person-in-the-middle attacks. For Bluetooth 2.1 - * devices, the link key will be encrypted, as encryption is mandatory. - * For legacy devices (pre Bluetooth 2.1 devices) the link key will - * be not be encrypted. Use {@link #createRfcommSocketToServiceRecord} if an - * encrypted and authenticated communication channel is desired. - * <p>This is designed to be used with {@link - * BluetoothAdapter#listenUsingInsecureRfcommWithServiceRecord} for peer-peer - * Bluetooth applications. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. This will also perform an SDP lookup of the given uuid to - * determine which channel to connect to. - * <p>The remote device will be authenticated and communication on this - * socket will be encrypted. - * <p>Hint: If you are connecting to a Bluetooth serial board then try - * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB. - * However if you are connecting to an Android peer then please generate - * your own unique UUID. - * - * @param uuid service record uuid to lookup RFCOMM channel - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, -1, - new ParcelUuid(uuid)); - } - - /** - * Construct an insecure RFCOMM socket ready to start an outgoing - * connection. - * Call #connect on the returned #BluetoothSocket to begin the connection. - * The remote device will not be authenticated and communication on this - * socket will not be encrypted. - * - * @param port remote port - * @return An RFCOMM BluetoothSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @UnsupportedAppUsage(publicAlternatives = "Use " - + "{@link #createInsecureRfcommSocketToServiceRecord} instead.") - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port, - null); - } - - /** - * Construct a SCO socket ready to start an outgoing connection. - * Call #connect on the returned #BluetoothSocket to begin the connection. - * - * @return a SCO BluetoothSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createScoSocket() throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null); - } - - /** - * Check that a pin is valid and convert to byte array. - * - * Bluetooth pin's are 1 to 16 bytes of UTF-8 characters. - * - * @param pin pin as java String - * @return the pin code as a UTF-8 byte array, or null if it is an invalid Bluetooth pin. - * @hide - */ - @UnsupportedAppUsage - public static byte[] convertPinToBytes(String pin) { - if (pin == null) { - return null; - } - byte[] pinBytes; - try { - pinBytes = pin.getBytes("UTF-8"); - } catch (UnsupportedEncodingException uee) { - Log.e(TAG, "UTF-8 not supported?!?"); // this should not happen - return null; - } - if (pinBytes.length <= 0 || pinBytes.length > 16) { - return null; - } - return pinBytes; - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @throws IllegalArgumentException if callback is null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback) { - return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO)); - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @throws IllegalArgumentException if callback is null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport) { - return (connectGatt(context, autoConnect, callback, transport, PHY_LE_1M_MASK)); - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect} - * is set to true. - * @throws NullPointerException if callback is null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport, int phy) { - return connectGatt(context, autoConnect, callback, transport, phy, null); - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, an d{@link - * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect} - * is set to true. - * @param handler The handler to use for the callback. If {@code null}, callbacks will happen on - * an un-specified background thread. - * @throws NullPointerException if callback is null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport, int phy, - Handler handler) { - return connectGatt(context, autoConnect, callback, transport, false, phy, handler); - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @param opportunistic Whether this GATT client is opportunistic. An opportunistic GATT client - * does not hold a GATT connection. It automatically disconnects when no other GATT connections - * are active for the remote device. - * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, an d{@link - * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect} - * is set to true. - * @param handler The handler to use for the callback. If {@code null}, callbacks will happen on - * an un-specified background thread. - * @return A BluetoothGatt instance. You can use BluetoothGatt to conduct GATT client - * operations. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport, - boolean opportunistic, int phy, Handler handler) { - if (callback == null) { - throw new NullPointerException("callback is null"); - } - - // TODO(Bluetooth) check whether platform support BLE - // Do the check here or in GattServer? - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - IBluetoothManager managerService = adapter.getBluetoothManager(); - try { - IBluetoothGatt iGatt = managerService.getBluetoothGatt(); - if (iGatt == null) { - // BLE is not supported - return null; - } - BluetoothGatt gatt = new BluetoothGatt( - iGatt, this, transport, opportunistic, phy, mAttributionSource); - gatt.connect(autoConnect, callback, handler); - return gatt; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can - * be used to start a secure outgoing connection to the remote device with the same dynamic - * protocol/service multiplexer (PSM) value. The supported Bluetooth transport is LE only. - * <p>This is designed to be used with {@link BluetoothAdapter#listenUsingL2capChannel()} for - * peer-peer Bluetooth applications. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection. - * <p>Application using this API is responsible for obtaining PSM value from remote device. - * <p>The remote device will be authenticated and communication on this socket will be - * encrypted. - * <p> Use this socket if an authenticated socket link is possible. Authentication refers - * to the authentication of the link key to prevent person-in-the-middle type of attacks. - * - * @param psm dynamic PSM value from remote device - * @return a CoC #BluetoothSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public @NonNull BluetoothSocket createL2capChannel(int psm) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "createL2capChannel: Bluetooth is not enabled"); - throw new IOException(); - } - if (DBG) Log.d(TAG, "createL2capChannel: psm=" + psm); - return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, true, true, this, psm, - null); - } - - /** - * Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can - * be used to start a secure outgoing connection to the remote device with the same dynamic - * protocol/service multiplexer (PSM) value. The supported Bluetooth transport is LE only. - * <p>This is designed to be used with {@link - * BluetoothAdapter#listenUsingInsecureL2capChannel()} for peer-peer Bluetooth applications. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection. - * <p>Application using this API is responsible for obtaining PSM value from remote device. - * <p> The communication channel may not have an authenticated link key, i.e. it may be subject - * to person-in-the-middle attacks. Use {@link #createL2capChannel(int)} if an encrypted and - * authenticated communication channel is possible. - * - * @param psm dynamic PSM value from remote device - * @return a CoC #BluetoothSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public @NonNull BluetoothSocket createInsecureL2capChannel(int psm) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "createInsecureL2capChannel: Bluetooth is not enabled"); - throw new IOException(); - } - if (DBG) { - Log.d(TAG, "createInsecureL2capChannel: psm=" + psm); - } - return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, false, false, this, psm, - null); - } - - /** - * Set a keyed metadata of this {@link BluetoothDevice} to a - * {@link String} value. - * Only bonded devices's metadata will be persisted across Bluetooth - * restart. - * Metadata will be removed when the device's bond state is moved to - * {@link #BOND_NONE}. - * - * @param key must be within the list of BluetoothDevice.METADATA_* - * @param value a byte array data to set for key. Must be less than - * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length - * @return true on success, false on error - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata"); - return false; - } - if (value.length > METADATA_MAX_LENGTH) { - throw new IllegalArgumentException("value length is " + value.length - + ", should not over " + METADATA_MAX_LENGTH); - } - try { - return service.setMetadata(this, key, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "setMetadata fail", e); - return false; - } - } - - /** - * Get a keyed metadata for this {@link BluetoothDevice} as {@link String} - * - * @param key must be within the list of BluetoothDevice.METADATA_* - * @return Metadata of the key as byte array, null on error or not found - * @hide - */ - @SystemApi - @Nullable - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public byte[] getMetadata(@MetadataKey int key) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata"); - return null; - } - try { - return service.getMetadata(this, key, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "getMetadata fail", e); - return null; - } - } - - /** - * Get the maxinum metadata key ID. - * - * @return the last supported metadata key - * @hide - */ - public static @MetadataKey int getMaxMetadataKey() { - return METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD; - } -} diff --git a/core/java/android/bluetooth/BluetoothDevicePicker.java b/core/java/android/bluetooth/BluetoothDevicePicker.java deleted file mode 100644 index 26e46573dd95..000000000000 --- a/core/java/android/bluetooth/BluetoothDevicePicker.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; - -/** - * A helper to show a system "Device Picker" activity to the user. - * - * @hide - */ -public interface BluetoothDevicePicker { - public static final String EXTRA_NEED_AUTH = - "android.bluetooth.devicepicker.extra.NEED_AUTH"; - public static final String EXTRA_FILTER_TYPE = - "android.bluetooth.devicepicker.extra.FILTER_TYPE"; - public static final String EXTRA_LAUNCH_PACKAGE = - "android.bluetooth.devicepicker.extra.LAUNCH_PACKAGE"; - public static final String EXTRA_LAUNCH_CLASS = - "android.bluetooth.devicepicker.extra.DEVICE_PICKER_LAUNCH_CLASS"; - - /** - * Broadcast when one BT device is selected from BT device picker screen. - * Selected {@link BluetoothDevice} is returned in extra data named - * {@link BluetoothDevice#EXTRA_DEVICE}. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_DEVICE_SELECTED = - "android.bluetooth.devicepicker.action.DEVICE_SELECTED"; - - /** - * Broadcast when someone want to select one BT device from devices list. - * This intent contains below extra data: - * - {@link #EXTRA_NEED_AUTH} (boolean): if need authentication - * - {@link #EXTRA_FILTER_TYPE} (int): what kinds of device should be - * listed - * - {@link #EXTRA_LAUNCH_PACKAGE} (string): where(which package) this - * intent come from - * - {@link #EXTRA_LAUNCH_CLASS} (string): where(which class) this intent - * come from - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LAUNCH = - "android.bluetooth.devicepicker.action.LAUNCH"; - - /** Ask device picker to show all kinds of BT devices */ - public static final int FILTER_TYPE_ALL = 0; - /** Ask device picker to show BT devices that support AUDIO profiles */ - public static final int FILTER_TYPE_AUDIO = 1; - /** Ask device picker to show BT devices that support Object Transfer */ - public static final int FILTER_TYPE_TRANSFER = 2; - /** - * Ask device picker to show BT devices that support - * Personal Area Networking User (PANU) profile - */ - public static final int FILTER_TYPE_PANU = 3; - /** Ask device picker to show BT devices that support Network Access Point (NAP) profile */ - public static final int FILTER_TYPE_NAP = 4; -} diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java deleted file mode 100644 index b531829d2940..000000000000 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ /dev/null @@ -1,1848 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.os.Build; -import android.os.Handler; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Public API for the Bluetooth GATT Profile. - * - * <p>This class provides Bluetooth GATT functionality to enable communication - * with Bluetooth Smart or Smart Ready devices. - * - * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback} - * and call {@link BluetoothDevice#connectGatt} to get a instance of this class. - * GATT capable devices can be discovered using the Bluetooth device discovery or BLE - * scan process. - */ -public final class BluetoothGatt implements BluetoothProfile { - private static final String TAG = "BluetoothGatt"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - @UnsupportedAppUsage - private IBluetoothGatt mService; - @UnsupportedAppUsage - private volatile BluetoothGattCallback mCallback; - private Handler mHandler; - @UnsupportedAppUsage - private int mClientIf; - private BluetoothDevice mDevice; - @UnsupportedAppUsage - private boolean mAutoConnect; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private int mAuthRetryState; - private int mConnState; - private final Object mStateLock = new Object(); - private final Object mDeviceBusyLock = new Object(); - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private Boolean mDeviceBusy = false; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private int mTransport; - private int mPhy; - private boolean mOpportunistic; - private final AttributionSource mAttributionSource; - - private static final int AUTH_RETRY_STATE_IDLE = 0; - private static final int AUTH_RETRY_STATE_NO_MITM = 1; - private static final int AUTH_RETRY_STATE_MITM = 2; - - private static final int CONN_STATE_IDLE = 0; - private static final int CONN_STATE_CONNECTING = 1; - private static final int CONN_STATE_CONNECTED = 2; - private static final int CONN_STATE_DISCONNECTING = 3; - private static final int CONN_STATE_CLOSED = 4; - - private static final int WRITE_CHARACTERISTIC_MAX_RETRIES = 5; - private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 10; // milliseconds - - private List<BluetoothGattService> mServices; - - /** A GATT operation completed successfully */ - public static final int GATT_SUCCESS = 0; - - /** GATT read operation is not permitted */ - public static final int GATT_READ_NOT_PERMITTED = 0x2; - - /** GATT write operation is not permitted */ - public static final int GATT_WRITE_NOT_PERMITTED = 0x3; - - /** Insufficient authentication for a given operation */ - public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5; - - /** The given request is not supported */ - public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6; - - /** Insufficient encryption for a given operation */ - public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf; - - /** A read or write operation was requested with an invalid offset */ - public static final int GATT_INVALID_OFFSET = 0x7; - - /** Insufficient authorization for a given operation */ - public static final int GATT_INSUFFICIENT_AUTHORIZATION = 0x8; - - /** A write operation exceeds the maximum length of the attribute */ - public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd; - - /** A remote device connection is congested. */ - public static final int GATT_CONNECTION_CONGESTED = 0x8f; - - /** A GATT operation failed, errors other than the above */ - public static final int GATT_FAILURE = 0x101; - - /** - * Connection parameter update - Use the connection parameters recommended by the - * Bluetooth SIG. This is the default value if no connection parameter update - * is requested. - */ - public static final int CONNECTION_PRIORITY_BALANCED = 0; - - /** - * Connection parameter update - Request a high priority, low latency connection. - * An application should only request high priority connection parameters to transfer large - * amounts of data over LE quickly. Once the transfer is complete, the application should - * request {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} connection parameters to reduce - * energy use. - */ - public static final int CONNECTION_PRIORITY_HIGH = 1; - - /** Connection parameter update - Request low power, reduced data rate connection parameters. */ - public static final int CONNECTION_PRIORITY_LOW_POWER = 2; - - /** - * No authentication required. - * - * @hide - */ - /*package*/ static final int AUTHENTICATION_NONE = 0; - - /** - * Authentication requested; no person-in-the-middle protection required. - * - * @hide - */ - /*package*/ static final int AUTHENTICATION_NO_MITM = 1; - - /** - * Authentication with person-in-the-middle protection requested. - * - * @hide - */ - /*package*/ static final int AUTHENTICATION_MITM = 2; - - /** - * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation. - */ - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothGattCallback mBluetoothGattCallback = - new IBluetoothGattCallback.Stub() { - /** - * Application interface registered - app is ready to go - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onClientRegistered(int status, int clientIf) { - if (DBG) { - Log.d(TAG, "onClientRegistered() - status=" + status - + " clientIf=" + clientIf); - } - if (VDBG) { - synchronized (mStateLock) { - if (mConnState != CONN_STATE_CONNECTING) { - Log.e(TAG, "Bad connection state: " + mConnState); - } - } - } - mClientIf = clientIf; - if (status != GATT_SUCCESS) { - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onConnectionStateChange(BluetoothGatt.this, - GATT_FAILURE, - BluetoothProfile.STATE_DISCONNECTED); - } - } - }); - - synchronized (mStateLock) { - mConnState = CONN_STATE_IDLE; - } - return; - } - try { - mService.clientConnect(mClientIf, mDevice.getAddress(), - !mAutoConnect, mTransport, mOpportunistic, - mPhy, mAttributionSource); // autoConnect is inverse of "isDirect" - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Phy update callback - * @hide - */ - @Override - public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) { - if (DBG) { - Log.d(TAG, "onPhyUpdate() - status=" + status - + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status); - } - } - }); - } - - /** - * Phy read callback - * @hide - */ - @Override - public void onPhyRead(String address, int txPhy, int rxPhy, int status) { - if (DBG) { - Log.d(TAG, "onPhyRead() - status=" + status - + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status); - } - } - }); - } - - /** - * Client connection state changed - * @hide - */ - @Override - public void onClientConnectionState(int status, int clientIf, - boolean connected, String address) { - if (DBG) { - Log.d(TAG, "onClientConnectionState() - status=" + status - + " clientIf=" + clientIf + " device=" + address); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - int profileState = connected ? BluetoothProfile.STATE_CONNECTED : - BluetoothProfile.STATE_DISCONNECTED; - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onConnectionStateChange(BluetoothGatt.this, status, - profileState); - } - } - }); - - synchronized (mStateLock) { - if (connected) { - mConnState = CONN_STATE_CONNECTED; - } else { - mConnState = CONN_STATE_IDLE; - } - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - } - - /** - * Remote search has been completed. - * The internal object structure should now reflect the state - * of the remote device database. Let the application know that - * we are done at this point. - * @hide - */ - @Override - public void onSearchComplete(String address, List<BluetoothGattService> services, - int status) { - if (DBG) { - Log.d(TAG, - "onSearchComplete() = Device=" + address + " Status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - for (BluetoothGattService s : services) { - //services we receive don't have device set properly. - s.setDevice(mDevice); - } - - mServices.addAll(services); - - // Fix references to included services, as they doesn't point to right objects. - for (BluetoothGattService fixedService : mServices) { - ArrayList<BluetoothGattService> includedServices = - new ArrayList(fixedService.getIncludedServices()); - fixedService.getIncludedServices().clear(); - - for (BluetoothGattService brokenRef : includedServices) { - BluetoothGattService includedService = getService(mDevice, - brokenRef.getUuid(), brokenRef.getInstanceId()); - if (includedService != null) { - fixedService.addIncludedService(includedService); - } else { - Log.e(TAG, "Broken GATT database: can't find included service."); - } - } - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onServicesDiscovered(BluetoothGatt.this, status); - } - } - }); - } - - /** - * Remote characteristic has been read. - * Updates the internal value. - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onCharacteristicRead(String address, int status, int handle, - byte[] value) { - if (VDBG) { - Log.d(TAG, "onCharacteristicRead() - Device=" + address - + " handle=" + handle + " Status=" + status); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - if ((status == GATT_INSUFFICIENT_AUTHENTICATION - || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { - try { - final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) - ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - mService.readCharacteristic( - mClientIf, address, handle, authReq, mAttributionSource); - mAuthRetryState++; - return; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - - BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, - handle); - if (characteristic == null) { - Log.w(TAG, "onCharacteristicRead() failed to find characteristic!"); - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - if (status == 0) characteristic.setValue(value); - callback.onCharacteristicRead(BluetoothGatt.this, characteristic, - value, status); - // Keep calling deprecated callback to maintain app compatibility - callback.onCharacteristicRead(BluetoothGatt.this, characteristic, - status); - } - } - }); - } - - /** - * Characteristic has been written to the remote device. - * Let the app know how we did... - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onCharacteristicWrite(String address, int status, int handle, - byte[] value) { - if (VDBG) { - Log.d(TAG, "onCharacteristicWrite() - Device=" + address - + " handle=" + handle + " Status=" + status); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, - handle); - if (characteristic == null) return; - - if ((status == GATT_INSUFFICIENT_AUTHENTICATION - || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { - try { - final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) - ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN; - for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) { - requestStatus = mService.writeCharacteristic(mClientIf, address, - handle, characteristic.getWriteType(), authReq, - value, mAttributionSource); - if (requestStatus - != BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY) { - break; - } - try { - Thread.sleep(WRITE_CHARACTERISTIC_TIME_TO_WAIT); - } catch (InterruptedException e) { - } - } - mAuthRetryState++; - return; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onCharacteristicWrite(BluetoothGatt.this, characteristic, - status); - } - } - }); - } - - /** - * Remote characteristic has been updated. - * Updates the internal value. - * @hide - */ - @Override - public void onNotify(String address, int handle, byte[] value) { - if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle); - - if (!address.equals(mDevice.getAddress())) { - return; - } - - BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, - handle); - if (characteristic == null) return; - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - characteristic.setValue(value); - callback.onCharacteristicChanged(BluetoothGatt.this, - characteristic, value); - // Keep calling deprecated callback to maintain app compatibility - callback.onCharacteristicChanged(BluetoothGatt.this, - characteristic); - } - } - }); - } - - /** - * Descriptor has been read. - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onDescriptorRead(String address, int status, int handle, byte[] value) { - if (VDBG) { - Log.d(TAG, - "onDescriptorRead() - Device=" + address + " handle=" + handle); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); - if (descriptor == null) return; - - - if ((status == GATT_INSUFFICIENT_AUTHENTICATION - || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { - try { - final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) - ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - mService.readDescriptor( - mClientIf, address, handle, authReq, mAttributionSource); - mAuthRetryState++; - return; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - if (status == 0) descriptor.setValue(value); - callback.onDescriptorRead(BluetoothGatt.this, descriptor, status, - value); - // Keep calling deprecated callback to maintain app compatibility - callback.onDescriptorRead(BluetoothGatt.this, descriptor, status); - } - } - }); - } - - /** - * Descriptor write operation complete. - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onDescriptorWrite(String address, int status, int handle, - byte[] value) { - if (VDBG) { - Log.d(TAG, - "onDescriptorWrite() - Device=" + address + " handle=" + handle); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); - if (descriptor == null) return; - - if ((status == GATT_INSUFFICIENT_AUTHENTICATION - || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { - try { - final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) - ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - mService.writeDescriptor(mClientIf, address, handle, - authReq, value, mAttributionSource); - mAuthRetryState++; - return; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status); - } - } - }); - } - - /** - * Prepared write transaction completed (or aborted) - * @hide - */ - @Override - public void onExecuteWrite(String address, int status) { - if (VDBG) { - Log.d(TAG, "onExecuteWrite() - Device=" + address - + " status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onReliableWriteCompleted(BluetoothGatt.this, status); - } - } - }); - } - - /** - * Remote device RSSI has been read - * @hide - */ - @Override - public void onReadRemoteRssi(String address, int rssi, int status) { - if (VDBG) { - Log.d(TAG, "onReadRemoteRssi() - Device=" + address - + " rssi=" + rssi + " status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status); - } - } - }); - } - - /** - * Callback invoked when the MTU for a given connection changes - * @hide - */ - @Override - public void onConfigureMTU(String address, int mtu, int status) { - if (DBG) { - Log.d(TAG, "onConfigureMTU() - Device=" + address - + " mtu=" + mtu + " status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onMtuChanged(BluetoothGatt.this, mtu, status); - } - } - }); - } - - /** - * Callback invoked when the given connection is updated - * @hide - */ - @Override - public void onConnectionUpdated(String address, int interval, int latency, - int timeout, int status) { - if (DBG) { - Log.d(TAG, "onConnectionUpdated() - Device=" + address - + " interval=" + interval + " latency=" + latency - + " timeout=" + timeout + " status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onConnectionUpdated(BluetoothGatt.this, interval, latency, - timeout, status); - } - } - }); - } - - /** - * Callback invoked when service changed event is received - * @hide - */ - @Override - public void onServiceChanged(String address) { - if (DBG) { - Log.d(TAG, "onServiceChanged() - Device=" + address); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onServiceChanged(BluetoothGatt.this); - } - } - }); - } - }; - - /* package */ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device, int transport, - boolean opportunistic, int phy, AttributionSource attributionSource) { - mService = iGatt; - mDevice = device; - mTransport = transport; - mPhy = phy; - mOpportunistic = opportunistic; - mAttributionSource = attributionSource; - mServices = new ArrayList<BluetoothGattService>(); - - mConnState = CONN_STATE_IDLE; - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - } - - /** - * Close this Bluetooth GATT client. - * - * Application should call this method as early as possible after it is done with - * this GATT client. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void close() { - if (DBG) Log.d(TAG, "close()"); - - unregisterApp(); - mConnState = CONN_STATE_CLOSED; - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - } - - /** - * Returns a service by UUID, instance and type. - * - * @hide - */ - /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid, - int instanceId) { - for (BluetoothGattService svc : mServices) { - if (svc.getDevice().equals(device) - && svc.getInstanceId() == instanceId - && svc.getUuid().equals(uuid)) { - return svc; - } - } - return null; - } - - - /** - * Returns a characteristic with id equal to instanceId. - * - * @hide - */ - /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device, - int instanceId) { - for (BluetoothGattService svc : mServices) { - for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - if (charac.getInstanceId() == instanceId) { - return charac; - } - } - } - return null; - } - - /** - * Returns a descriptor with id equal to instanceId. - * - * @hide - */ - /*package*/ BluetoothGattDescriptor getDescriptorById(BluetoothDevice device, int instanceId) { - for (BluetoothGattService svc : mServices) { - for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - for (BluetoothGattDescriptor desc : charac.getDescriptors()) { - if (desc.getInstanceId() == instanceId) { - return desc; - } - } - } - } - return null; - } - - /** - * Queue the runnable on a {@link Handler} provided by the user, or execute the runnable - * immediately if no Handler was provided. - */ - private void runOrQueueCallback(final Runnable cb) { - if (mHandler == null) { - try { - cb.run(); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } else { - mHandler.post(cb); - } - } - - /** - * Register an application callback to start using GATT. - * - * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} - * is used to notify success or failure if the function returns true. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @return If true, the callback will be called to notify success or failure, false on immediate - * error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean registerApp(BluetoothGattCallback callback, Handler handler) { - return registerApp(callback, handler, false); - } - - /** - * Register an application callback to start using GATT. - * - * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} - * is used to notify success or failure if the function returns true. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param eatt_support indicate to allow for eatt support - * @return If true, the callback will be called to notify success or failure, false on immediate - * error - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean registerApp(BluetoothGattCallback callback, Handler handler, - boolean eatt_support) { - if (DBG) Log.d(TAG, "registerApp()"); - if (mService == null) return false; - - mCallback = callback; - mHandler = handler; - UUID uuid = UUID.randomUUID(); - if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid); - - try { - mService.registerClient( - new ParcelUuid(uuid), mBluetoothGattCallback, eatt_support, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Unregister the current application and callbacks. - */ - @UnsupportedAppUsage - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void unregisterApp() { - if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf); - if (mService == null || mClientIf == 0) return; - - try { - mCallback = null; - mService.unregisterClient(mClientIf, mAttributionSource); - mClientIf = 0; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Initiate a connection to a Bluetooth GATT capable device. - * - * <p>The connection may not be established right away, but will be - * completed when the remote device is available. A - * {@link BluetoothGattCallback#onConnectionStateChange} callback will be - * invoked when the connection state changes as a result of this function. - * - * <p>The autoConnect parameter determines whether to actively connect to - * the remote device, or rather passively scan and finalize the connection - * when the remote device is in range/available. Generally, the first ever - * connection to a device should be direct (autoConnect set to false) and - * subsequent connections to known devices should be invoked with the - * autoConnect parameter set to true. - * - * @param device Remote device to connect to - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @return true, if the connection attempt was initiated successfully - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback, - Handler handler) { - if (DBG) { - Log.d(TAG, - "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect); - } - synchronized (mStateLock) { - if (mConnState != CONN_STATE_IDLE) { - throw new IllegalStateException("Not idle"); - } - mConnState = CONN_STATE_CONNECTING; - } - - mAutoConnect = autoConnect; - - if (!registerApp(callback, handler)) { - synchronized (mStateLock) { - mConnState = CONN_STATE_IDLE; - } - Log.e(TAG, "Failed to register callback"); - return false; - } - - // The connection will continue in the onClientRegistered callback - return true; - } - - /** - * Disconnects an established connection, or cancels a connection attempt - * currently in progress. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void disconnect() { - if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return; - - try { - mService.clientDisconnect(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Connect back to remote device. - * - * <p>This method is used to re-connect to a remote device after the - * connection has been dropped. If the device is not in range, the - * re-connection will be triggered once the device is back in range. - * - * @return true, if the connection attempt was initiated successfully - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect() { - try { - // autoConnect is inverse of "isDirect" - mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport, - mOpportunistic, mPhy, mAttributionSource); - return true; - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - } - - /** - * Set the preferred connection PHY for this app. Please note that this is just a - * recommendation, whether the PHY change will happen depends on other applications preferences, - * local and remote controller capabilities. Controller can override these settings. - * <p> - * {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even - * if no PHY change happens. It is also triggered when remote device updates the PHY. - * - * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. - * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. - * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one - * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or - * {@link BluetoothDevice#PHY_OPTION_S8} - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) { - try { - mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy, - phyOptions, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Read the current transmitter PHY and receiver PHY of the connection. The values are returned - * in {@link BluetoothGattCallback#onPhyRead} - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void readPhy() { - try { - mService.clientReadPhy(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Return the remote bluetooth device this GATT client targets to - * - * @return remote bluetooth device - */ - @RequiresNoPermission - public BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Discovers services offered by a remote device as well as their - * characteristics and descriptors. - * - * <p>This is an asynchronous operation. Once service discovery is completed, - * the {@link BluetoothGattCallback#onServicesDiscovered} callback is - * triggered. If the discovery was successful, the remote services can be - * retrieved using the {@link #getServices} function. - * - * @return true, if the remote service discovery has been started - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean discoverServices() { - if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - mServices.clear(); - - try { - mService.discoverServices(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Discovers a service by UUID. This is exposed only for passing PTS tests. - * It should never be used by real applications. The service is not searched - * for characteristics and descriptors, or returned in any callback. - * - * @return true, if the remote service discovery has been started - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean discoverServiceByUuid(UUID uuid) { - if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - mServices.clear(); - - try { - mService.discoverServiceByUuid( - mClientIf, mDevice.getAddress(), new ParcelUuid(uuid), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - return true; - } - - /** - * Returns a list of GATT services offered by the remote device. - * - * <p>This function requires that service discovery has been completed - * for the given device. - * - * @return List of services on the remote device. Returns an empty list if service discovery has - * not yet been performed. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public List<BluetoothGattService> getServices() { - List<BluetoothGattService> result = - new ArrayList<BluetoothGattService>(); - - for (BluetoothGattService service : mServices) { - if (service.getDevice().equals(mDevice)) { - result.add(service); - } - } - - return result; - } - - /** - * Returns a {@link BluetoothGattService}, if the requested UUID is - * supported by the remote device. - * - * <p>This function requires that service discovery has been completed - * for the given device. - * - * <p>If multiple instances of the same service (as identified by UUID) - * exist, the first instance of the service is returned. - * - * @param uuid UUID of the requested service - * @return BluetoothGattService if supported, or null if the requested service is not offered by - * the remote device. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public BluetoothGattService getService(UUID uuid) { - for (BluetoothGattService service : mServices) { - if (service.getDevice().equals(mDevice) && service.getUuid().equals(uuid)) { - return service; - } - } - - return null; - } - - /** - * Reads the requested characteristic from the associated remote device. - * - * <p>This is an asynchronous operation. The result of the read operation - * is reported by the {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, - * BluetoothGattCharacteristic, byte[], int)} callback. - * - * @param characteristic Characteristic to read from the remote device - * @return true, if the read operation was initiated successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { - if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) { - return false; - } - - if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid()); - if (mService == null || mClientIf == 0) return false; - - BluetoothGattService service = characteristic.getService(); - if (service == null) return false; - - BluetoothDevice device = service.getDevice(); - if (device == null) return false; - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return false; - mDeviceBusy = true; - } - - try { - mService.readCharacteristic(mClientIf, device.getAddress(), - characteristic.getInstanceId(), AUTHENTICATION_NONE, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - return false; - } - - return true; - } - - /** - * Reads the characteristic using its UUID from the associated remote device. - * - * <p>This is an asynchronous operation. The result of the read operation - * is reported by the {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, - * BluetoothGattCharacteristic, byte[], int)} callback. - * - * @param uuid UUID of characteristic to read from the remote device - * @return true, if the read operation was initiated successfully - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) { - if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid); - if (mService == null || mClientIf == 0) return false; - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return false; - mDeviceBusy = true; - } - - try { - mService.readUsingCharacteristicUuid(mClientIf, mDevice.getAddress(), - new ParcelUuid(uuid), startHandle, endHandle, AUTHENTICATION_NONE, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - return false; - } - - return true; - } - - - /** - * Writes a given characteristic and its values to the associated remote device. - * - * <p>Once the write operation has been completed, the - * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked, - * reporting the result of the operation. - * - * @param characteristic Characteristic to write on the remote device - * @return true, if the write operation was initiated successfully - * @throws IllegalArgumentException if characteristic or its value are null - * - * @deprecated Use {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], - * int)} as this is not memory safe. - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { - try { - return writeCharacteristic(characteristic, characteristic.getValue(), - characteristic.getWriteType()) == BluetoothStatusCodes.SUCCESS; - } catch (Exception e) { - return false; - } - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION, - BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED, - BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, - BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED, - BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY, - BluetoothStatusCodes.ERROR_UNKNOWN - }) - public @interface WriteOperationReturnValues{} - - /** - * Writes a given characteristic and its values to the associated remote device. - * - * <p>Once the write operation has been completed, the - * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked, - * reporting the result of the operation. - * - * @param characteristic Characteristic to write on the remote device - * @return whether the characteristic was successfully written to - * @throws IllegalArgumentException if characteristic or value are null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @WriteOperationReturnValues - public int writeCharacteristic(@NonNull BluetoothGattCharacteristic characteristic, - @NonNull byte[] value, int writeType) { - if (characteristic == null) { - throw new IllegalArgumentException("characteristic must not be null"); - } - if (value == null) { - throw new IllegalArgumentException("value must not be null"); - } - if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid()); - if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 - && (characteristic.getProperties() - & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) { - return BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED; - } - if (mService == null || mClientIf == 0) { - return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; - } - - BluetoothGattService service = characteristic.getService(); - if (service == null) { - throw new IllegalArgumentException("Characteristic must have a non-null service"); - } - - BluetoothDevice device = service.getDevice(); - if (device == null) { - throw new IllegalArgumentException("Service must have a non-null device"); - } - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) { - return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY; - } - mDeviceBusy = true; - } - - int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN; - try { - for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) { - requestStatus = mService.writeCharacteristic(mClientIf, device.getAddress(), - characteristic.getInstanceId(), writeType, AUTHENTICATION_NONE, value, - mAttributionSource); - if (requestStatus != BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY) { - break; - } - try { - Thread.sleep(WRITE_CHARACTERISTIC_TIME_TO_WAIT); - } catch (InterruptedException e) { - } - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - throw e.rethrowFromSystemServer(); - } - - return requestStatus; - } - - /** - * Reads the value for a given descriptor from the associated remote device. - * - * <p>Once the read operation has been completed, the - * {@link BluetoothGattCallback#onDescriptorRead} callback is - * triggered, signaling the result of the operation. - * - * @param descriptor Descriptor value to read from the remote device - * @return true, if the read operation was initiated successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean readDescriptor(BluetoothGattDescriptor descriptor) { - if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid()); - if (mService == null || mClientIf == 0) return false; - - BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); - if (characteristic == null) return false; - - BluetoothGattService service = characteristic.getService(); - if (service == null) return false; - - BluetoothDevice device = service.getDevice(); - if (device == null) return false; - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return false; - mDeviceBusy = true; - } - - try { - mService.readDescriptor(mClientIf, device.getAddress(), - descriptor.getInstanceId(), AUTHENTICATION_NONE, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - return false; - } - - return true; - } - - /** - * Write the value of a given descriptor to the associated remote device. - * - * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is triggered to report the - * result of the write operation. - * - * @param descriptor Descriptor to write to the associated remote device - * @return true, if the write operation was initiated successfully - * @throws IllegalArgumentException if descriptor or its value are null - * - * @deprecated Use {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, byte[])} as - * this is not memory safe. - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean writeDescriptor(BluetoothGattDescriptor descriptor) { - try { - return writeDescriptor(descriptor, descriptor.getValue()) - == BluetoothStatusCodes.SUCCESS; - } catch (Exception e) { - return false; - } - } - - /** - * Write the value of a given descriptor to the associated remote device. - * - * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is triggered to report the - * result of the write operation. - * - * @param descriptor Descriptor to write to the associated remote device - * @return true, if the write operation was initiated successfully - * @throws IllegalArgumentException if descriptor or value are null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @WriteOperationReturnValues - public int writeDescriptor(@NonNull BluetoothGattDescriptor descriptor, - @NonNull byte[] value) { - if (descriptor == null) { - throw new IllegalArgumentException("descriptor must not be null"); - } - if (value == null) { - throw new IllegalArgumentException("value must not be null"); - } - if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid()); - if (mService == null || mClientIf == 0) { - return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; - } - - BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); - if (characteristic == null) { - throw new IllegalArgumentException("Descriptor must have a non-null characteristic"); - } - - BluetoothGattService service = characteristic.getService(); - if (service == null) { - throw new IllegalArgumentException("Characteristic must have a non-null service"); - } - - BluetoothDevice device = service.getDevice(); - if (device == null) { - throw new IllegalArgumentException("Service must have a non-null device"); - } - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY; - mDeviceBusy = true; - } - - try { - return mService.writeDescriptor(mClientIf, device.getAddress(), - descriptor.getInstanceId(), AUTHENTICATION_NONE, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - e.rethrowFromSystemServer(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Initiates a reliable write transaction for a given remote device. - * - * <p>Once a reliable write transaction has been initiated, all calls - * to {@link #writeCharacteristic} are sent to the remote device for - * verification and queued up for atomic execution. The application will - * receive a {@link BluetoothGattCallback#onCharacteristicWrite} callback in response to every - * {@link #writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} call and is - * responsible for verifying if the value has been transmitted accurately. - * - * <p>After all characteristics have been queued up and verified, - * {@link #executeReliableWrite} will execute all writes. If a characteristic - * was not written correctly, calling {@link #abortReliableWrite} will - * cancel the current transaction without committing any values on the - * remote device. - * - * @return true, if the reliable write transaction has been initiated - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean beginReliableWrite() { - if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - try { - mService.beginReliableWrite(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Executes a reliable write transaction for a given remote device. - * - * <p>This function will commit all queued up characteristic write - * operations for a given remote device. - * - * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is - * invoked to indicate whether the transaction has been executed correctly. - * - * @return true, if the request to execute the transaction has been sent - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean executeReliableWrite() { - if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return false; - mDeviceBusy = true; - } - - try { - mService.endReliableWrite(mClientIf, mDevice.getAddress(), true, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - return false; - } - - return true; - } - - /** - * Cancels a reliable write transaction for a given device. - * - * <p>Calling this function will discard all queued characteristic write - * operations for a given remote device. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void abortReliableWrite() { - if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return; - - try { - mService.endReliableWrite(mClientIf, mDevice.getAddress(), false, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * @deprecated Use {@link #abortReliableWrite()} - */ - @Deprecated - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void abortReliableWrite(BluetoothDevice mDevice) { - abortReliableWrite(); - } - - /** - * Enable or disable notifications/indications for a given characteristic. - * - * <p>Once notifications are enabled for a characteristic, a - * {@link BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt, - * BluetoothGattCharacteristic, byte[])} callback will be triggered if the remote device - * indicates that the given characteristic has changed. - * - * @param characteristic The characteristic for which to enable notifications - * @param enable Set to true to enable notifications/indications - * @return true, if the requested notification status was set successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic, - boolean enable) { - if (DBG) { - Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid() - + " enable: " + enable); - } - if (mService == null || mClientIf == 0) return false; - - BluetoothGattService service = characteristic.getService(); - if (service == null) return false; - - BluetoothDevice device = service.getDevice(); - if (device == null) return false; - - try { - mService.registerForNotification(mClientIf, device.getAddress(), - characteristic.getInstanceId(), enable, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Clears the internal cache and forces a refresh of the services from the - * remote device. - * - * @hide - */ - @UnsupportedAppUsage - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean refresh() { - if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - try { - mService.refreshDevice(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Read the RSSI for a connected remote device. - * - * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be - * invoked when the RSSI value has been read. - * - * @return true, if the RSSI value has been requested successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean readRemoteRssi() { - if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - try { - mService.readRemoteRssi(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Request an MTU size used for a given connection. - * - * <p>When performing a write request operation (write without response), - * the data sent is truncated to the MTU size. This function may be used - * to request a larger MTU size to be able to send more data at once. - * - * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate - * whether this operation was successful. - * - * @return true, if the new MTU value has been requested successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean requestMtu(int mtu) { - if (DBG) { - Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress() - + " mtu: " + mtu); - } - if (mService == null || mClientIf == 0) return false; - - try { - mService.configureMTU(mClientIf, mDevice.getAddress(), mtu, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Request a connection parameter update. - * - * <p>This function will send a connection parameter update request to the - * remote device. - * - * @param connectionPriority Request a specific connection priority. Must be one of {@link - * BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} - * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. - * @throws IllegalArgumentException If the parameters are outside of their specified range. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean requestConnectionPriority(int connectionPriority) { - if (connectionPriority < CONNECTION_PRIORITY_BALANCED - || connectionPriority > CONNECTION_PRIORITY_LOW_POWER) { - throw new IllegalArgumentException("connectionPriority not within valid range"); - } - - if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority); - if (mService == null || mClientIf == 0) return false; - - try { - mService.connectionParameterUpdate( - mClientIf, mDevice.getAddress(), connectionPriority, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Request an LE connection parameter update. - * - * <p>This function will send an LE connection parameters update request to the remote device. - * - * @return true, if the request is send to the Bluetooth stack. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval, - int slaveLatency, int supervisionTimeout, - int minConnectionEventLen, int maxConnectionEventLen) { - if (DBG) { - Log.d(TAG, "requestLeConnectionUpdate() - min=(" + minConnectionInterval - + ")" + (1.25 * minConnectionInterval) - + "msec, max=(" + maxConnectionInterval + ")" - + (1.25 * maxConnectionInterval) + "msec, latency=" + slaveLatency - + ", timeout=" + supervisionTimeout + "msec" + ", min_ce=" - + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen); - } - if (mService == null || mClientIf == 0) return false; - - try { - mService.leConnectionUpdate(mClientIf, mDevice.getAddress(), - minConnectionInterval, maxConnectionInterval, - slaveLatency, supervisionTimeout, - minConnectionEventLen, maxConnectionEventLen, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} - * with {@link BluetoothProfile#GATT} as argument - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - @Deprecated - public int getConnectionState(BluetoothDevice device) { - throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); - } - - /** - * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} - * with {@link BluetoothProfile#GATT} as argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - @Deprecated - public List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException( - "Use BluetoothManager#getConnectedDevices instead."); - } - - /** - * @deprecated Not supported - please use - * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])} - * with {@link BluetoothProfile#GATT} as first argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - @Deprecated - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - throw new UnsupportedOperationException( - "Use BluetoothManager#getDevicesMatchingConnectionStates instead."); - } -} diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java deleted file mode 100644 index d0a5a1e729fe..000000000000 --- a/core/java/android/bluetooth/BluetoothGattCallback.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.NonNull; - -/** - * This abstract class is used to implement {@link BluetoothGatt} callbacks. - */ -public abstract class BluetoothGattCallback { - - /** - * Callback triggered as result of {@link BluetoothGatt#setPreferredPhy}, or as a result of - * remote device changing the PHY. - * - * @param gatt GATT client - * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}. - * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}. - * @param status Status of the PHY update operation. {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { - } - - /** - * Callback triggered as result of {@link BluetoothGatt#readPhy} - * - * @param gatt GATT client - * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}. - * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}. - * @param status Status of the PHY read operation. {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { - } - - /** - * Callback indicating when GATT client has connected/disconnected to/from a remote - * GATT server. - * - * @param gatt GATT client - * @param status Status of the connect or disconnect operation. {@link - * BluetoothGatt#GATT_SUCCESS} if the operation succeeds. - * @param newState Returns the new connection state. Can be one of {@link - * BluetoothProfile#STATE_DISCONNECTED} or {@link BluetoothProfile#STATE_CONNECTED} - */ - public void onConnectionStateChange(BluetoothGatt gatt, int status, - int newState) { - } - - /** - * Callback invoked when the list of remote services, characteristics and descriptors - * for the remote device have been updated, ie new services have been discovered. - * - * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices} - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device has been explored - * successfully. - */ - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - } - - /** - * Callback reporting the result of a characteristic read operation. - * - * @param gatt GATT client invoked - * {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} - * @param characteristic Characteristic that was read from the associated remote device. - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed - * successfully. - * @deprecated Use {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, - * BluetoothGattCharacteristic, byte[], int)} as it is memory safe - */ - @Deprecated - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, - int status) { - } - - /** - * Callback reporting the result of a characteristic read operation. - * - * @param gatt GATT client invoked - * {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} - * @param characteristic Characteristic that was read from the associated remote device. - * @param value the value of the characteristic - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed - * successfully. - */ - public void onCharacteristicRead(@NonNull BluetoothGatt gatt, @NonNull - BluetoothGattCharacteristic characteristic, @NonNull byte[] value, int status) { - } - - /** - * Callback indicating the result of a characteristic write operation. - * - * <p>If this callback is invoked while a reliable write transaction is - * in progress, the value of the characteristic represents the value - * reported by the remote device. An application should compare this - * value to the desired value to be written. If the values don't match, - * the application must abort the reliable write transaction. - * - * @param gatt GATT client that invoked - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, - * byte[], int)} - * @param characteristic Characteristic that was written to the associated remote device. - * @param status The result of the write operation {@link BluetoothGatt#GATT_SUCCESS} if - * the - * operation succeeds. - */ - public void onCharacteristicWrite(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic, int status) { - } - - /** - * Callback triggered as a result of a remote characteristic notification. - * - * @param gatt GATT client the characteristic is associated with - * @param characteristic Characteristic that has been updated as a result of a remote - * notification event. - * @deprecated Use {@link BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt, - * BluetoothGattCharacteristic, byte[])} as it is memory safe by providing the characteristic - * value at the time of notification. - */ - @Deprecated - public void onCharacteristicChanged(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic) { - } - - /** - * Callback triggered as a result of a remote characteristic notification. Note that the value - * within the characteristic object may have changed since receiving the remote characteristic - * notification, so check the parameter value for the value at the time of notification. - * - * @param gatt GATT client the characteristic is associated with - * @param characteristic Characteristic that has been updated as a result of a remote - * notification event. - * @param value notified characteristic value - */ - public void onCharacteristicChanged(@NonNull BluetoothGatt gatt, - @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) { - } - - /** - * Callback reporting the result of a descriptor read operation. - * - * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor} - * @param descriptor Descriptor that was read from the associated remote device. - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed - * successfully - * @deprecated Use {@link BluetoothGattCallback#onDescriptorRead(BluetoothGatt, - * BluetoothGattDescriptor, int, byte[])} as it is memory safe by providing the descriptor - * value at the time it was read. - */ - @Deprecated - public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, - int status) { - } - - /** - * Callback reporting the result of a descriptor read operation. - * - * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor} - * @param descriptor Descriptor that was read from the associated remote device. - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed - * successfully - * @param value the descriptor value at the time of the read operation - */ - public void onDescriptorRead(@NonNull BluetoothGatt gatt, - @NonNull BluetoothGattDescriptor descriptor, int status, @NonNull byte[] value) { - } - - /** - * Callback indicating the result of a descriptor write operation. - * - * @param gatt GATT client invoked {@link BluetoothGatt#writeDescriptor} - * @param descriptor Descriptor that was writte to the associated remote device. - * @param status The result of the write operation {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, - int status) { - } - - /** - * Callback invoked when a reliable write transaction has been completed. - * - * @param gatt GATT client invoked {@link BluetoothGatt#executeReliableWrite} - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write transaction was - * executed successfully - */ - public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { - } - - /** - * Callback reporting the RSSI for a remote device connection. - * - * This callback is triggered in response to the - * {@link BluetoothGatt#readRemoteRssi} function. - * - * @param gatt GATT client invoked {@link BluetoothGatt#readRemoteRssi} - * @param rssi The RSSI value for the remote device - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the RSSI was read successfully - */ - public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { - } - - /** - * Callback indicating the MTU for a given device connection has changed. - * - * This callback is triggered in response to the - * {@link BluetoothGatt#requestMtu} function, or in response to a connection - * event. - * - * @param gatt GATT client invoked {@link BluetoothGatt#requestMtu} - * @param mtu The new MTU size - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the MTU has been changed successfully - */ - public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { - } - - /** - * Callback indicating the connection parameters were updated. - * - * @param gatt GATT client involved - * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from - * 6 (7.5ms) to 3200 (4000ms). - * @param latency Worker latency for the connection in number of connection events. Valid range - * is from 0 to 499 - * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10 - * (0.1s) to 3200 (32s) - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated - * successfully - * @hide - */ - public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout, - int status) { - } - - /** - * Callback indicating service changed event is received - * - * <p>Receiving this event means that the GATT database is out of sync with - * the remote device. {@link BluetoothGatt#discoverServices} should be - * called to re-discover the services. - * - * @param gatt GATT client involved - */ - public void onServiceChanged(@NonNull BluetoothGatt gatt) { - } -} diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java deleted file mode 100644 index 053e0db3d8a6..000000000000 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ /dev/null @@ -1,806 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.bluetooth; - -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Represents a Bluetooth GATT Characteristic - * - * <p>A GATT characteristic is a basic data element used to construct a GATT service, - * {@link BluetoothGattService}. The characteristic contains a value as well as - * additional information and optional GATT descriptors, {@link BluetoothGattDescriptor}. - */ -public class BluetoothGattCharacteristic implements Parcelable { - - /** - * Characteristic proprty: Characteristic is broadcastable. - */ - public static final int PROPERTY_BROADCAST = 0x01; - - /** - * Characteristic property: Characteristic is readable. - */ - public static final int PROPERTY_READ = 0x02; - - /** - * Characteristic property: Characteristic can be written without response. - */ - public static final int PROPERTY_WRITE_NO_RESPONSE = 0x04; - - /** - * Characteristic property: Characteristic can be written. - */ - public static final int PROPERTY_WRITE = 0x08; - - /** - * Characteristic property: Characteristic supports notification - */ - public static final int PROPERTY_NOTIFY = 0x10; - - /** - * Characteristic property: Characteristic supports indication - */ - public static final int PROPERTY_INDICATE = 0x20; - - /** - * Characteristic property: Characteristic supports write with signature - */ - public static final int PROPERTY_SIGNED_WRITE = 0x40; - - /** - * Characteristic property: Characteristic has extended properties - */ - public static final int PROPERTY_EXTENDED_PROPS = 0x80; - - /** - * Characteristic read permission - */ - public static final int PERMISSION_READ = 0x01; - - /** - * Characteristic permission: Allow encrypted read operations - */ - public static final int PERMISSION_READ_ENCRYPTED = 0x02; - - /** - * Characteristic permission: Allow reading with person-in-the-middle protection - */ - public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04; - - /** - * Characteristic write permission - */ - public static final int PERMISSION_WRITE = 0x10; - - /** - * Characteristic permission: Allow encrypted writes - */ - public static final int PERMISSION_WRITE_ENCRYPTED = 0x20; - - /** - * Characteristic permission: Allow encrypted writes with person-in-the-middle - * protection - */ - public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40; - - /** - * Characteristic permission: Allow signed write operations - */ - public static final int PERMISSION_WRITE_SIGNED = 0x80; - - /** - * Characteristic permission: Allow signed write operations with - * person-in-the-middle protection - */ - public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100; - - /** - * Write characteristic, requesting acknoledgement by the remote device - */ - public static final int WRITE_TYPE_DEFAULT = 0x02; - - /** - * Write characteristic without requiring a response by the remote device - */ - public static final int WRITE_TYPE_NO_RESPONSE = 0x01; - - /** - * Write characteristic including authentication signature - */ - public static final int WRITE_TYPE_SIGNED = 0x04; - - /** - * Characteristic value format type uint8 - */ - public static final int FORMAT_UINT8 = 0x11; - - /** - * Characteristic value format type uint16 - */ - public static final int FORMAT_UINT16 = 0x12; - - /** - * Characteristic value format type uint32 - */ - public static final int FORMAT_UINT32 = 0x14; - - /** - * Characteristic value format type sint8 - */ - public static final int FORMAT_SINT8 = 0x21; - - /** - * Characteristic value format type sint16 - */ - public static final int FORMAT_SINT16 = 0x22; - - /** - * Characteristic value format type sint32 - */ - public static final int FORMAT_SINT32 = 0x24; - - /** - * Characteristic value format type sfloat (16-bit float) - */ - public static final int FORMAT_SFLOAT = 0x32; - - /** - * Characteristic value format type float (32-bit float) - */ - public static final int FORMAT_FLOAT = 0x34; - - - /** - * The UUID of this characteristic. - * - * @hide - */ - protected UUID mUuid; - - /** - * Instance ID for this characteristic. - * - * @hide - */ - @UnsupportedAppUsage - protected int mInstance; - - /** - * Characteristic properties. - * - * @hide - */ - protected int mProperties; - - /** - * Characteristic permissions. - * - * @hide - */ - protected int mPermissions; - - /** - * Key size (default = 16). - * - * @hide - */ - protected int mKeySize = 16; - - /** - * Write type for this characteristic. - * See WRITE_TYPE_* constants. - * - * @hide - */ - protected int mWriteType; - - /** - * Back-reference to the service this characteristic belongs to. - * - * @hide - */ - @UnsupportedAppUsage - protected BluetoothGattService mService; - - /** - * The cached value of this characteristic. - * - * @hide - */ - protected byte[] mValue; - - /** - * List of descriptors included in this characteristic. - */ - protected List<BluetoothGattDescriptor> mDescriptors; - - /** - * Create a new BluetoothGattCharacteristic. - * - * @param uuid The UUID for this characteristic - * @param properties Properties of this characteristic - * @param permissions Permissions for this characteristic - */ - public BluetoothGattCharacteristic(UUID uuid, int properties, int permissions) { - initCharacteristic(null, uuid, 0, properties, permissions); - } - - /** - * Create a new BluetoothGattCharacteristic - * - * @hide - */ - /*package*/ BluetoothGattCharacteristic(BluetoothGattService service, - UUID uuid, int instanceId, - int properties, int permissions) { - initCharacteristic(service, uuid, instanceId, properties, permissions); - } - - /** - * Create a new BluetoothGattCharacteristic - * - * @hide - */ - public BluetoothGattCharacteristic(UUID uuid, int instanceId, - int properties, int permissions) { - initCharacteristic(null, uuid, instanceId, properties, permissions); - } - - private void initCharacteristic(BluetoothGattService service, - UUID uuid, int instanceId, - int properties, int permissions) { - mUuid = uuid; - mInstance = instanceId; - mProperties = properties; - mPermissions = permissions; - mService = service; - mValue = null; - mDescriptors = new ArrayList<BluetoothGattDescriptor>(); - - if ((mProperties & PROPERTY_WRITE_NO_RESPONSE) != 0) { - mWriteType = WRITE_TYPE_NO_RESPONSE; - } else { - mWriteType = WRITE_TYPE_DEFAULT; - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeInt(mInstance); - out.writeInt(mProperties); - out.writeInt(mPermissions); - out.writeInt(mKeySize); - out.writeInt(mWriteType); - out.writeTypedList(mDescriptors); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattCharacteristic> CREATOR = - new Parcelable.Creator<BluetoothGattCharacteristic>() { - public BluetoothGattCharacteristic createFromParcel(Parcel in) { - return new BluetoothGattCharacteristic(in); - } - - public BluetoothGattCharacteristic[] newArray(int size) { - return new BluetoothGattCharacteristic[size]; - } - }; - - private BluetoothGattCharacteristic(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid(); - mInstance = in.readInt(); - mProperties = in.readInt(); - mPermissions = in.readInt(); - mKeySize = in.readInt(); - mWriteType = in.readInt(); - - mDescriptors = new ArrayList<BluetoothGattDescriptor>(); - - ArrayList<BluetoothGattDescriptor> descs = - in.createTypedArrayList(BluetoothGattDescriptor.CREATOR); - if (descs != null) { - for (BluetoothGattDescriptor desc : descs) { - desc.setCharacteristic(this); - mDescriptors.add(desc); - } - } - } - - /** - * Returns the desired key size. - * - * @hide - */ - public int getKeySize() { - return mKeySize; - } - - /** - * Adds a descriptor to this characteristic. - * - * @param descriptor Descriptor to be added to this characteristic. - * @return true, if the descriptor was added to the characteristic - */ - public boolean addDescriptor(BluetoothGattDescriptor descriptor) { - mDescriptors.add(descriptor); - descriptor.setCharacteristic(this); - return true; - } - - /** - * Get a descriptor by UUID and isntance id. - * - * @hide - */ - /*package*/ BluetoothGattDescriptor getDescriptor(UUID uuid, int instanceId) { - for (BluetoothGattDescriptor descriptor : mDescriptors) { - if (descriptor.getUuid().equals(uuid) - && descriptor.getInstanceId() == instanceId) { - return descriptor; - } - } - return null; - } - - /** - * Returns the service this characteristic belongs to. - * - * @return The asscociated service - */ - public BluetoothGattService getService() { - return mService; - } - - /** - * Sets the service associated with this device. - * - * @hide - */ - @UnsupportedAppUsage - /*package*/ void setService(BluetoothGattService service) { - mService = service; - } - - /** - * Returns the UUID of this characteristic - * - * @return UUID of this characteristic - */ - public UUID getUuid() { - return mUuid; - } - - /** - * Returns the instance ID for this characteristic. - * - * <p>If a remote device offers multiple characteristics with the same UUID, - * the instance ID is used to distuinguish between characteristics. - * - * @return Instance ID of this characteristic - */ - public int getInstanceId() { - return mInstance; - } - - /** - * Force the instance ID. - * - * @hide - */ - public void setInstanceId(int instanceId) { - mInstance = instanceId; - } - - /** - * Returns the properties of this characteristic. - * - * <p>The properties contain a bit mask of property flags indicating - * the features of this characteristic. - * - * @return Properties of this characteristic - */ - public int getProperties() { - return mProperties; - } - - /** - * Returns the permissions for this characteristic. - * - * @return Permissions of this characteristic - */ - public int getPermissions() { - return mPermissions; - } - - /** - * Gets the write type for this characteristic. - * - * @return Write type for this characteristic - */ - public int getWriteType() { - return mWriteType; - } - - /** - * Set the write type for this characteristic - * - * <p>Setting the write type of a characteristic determines how the - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} function - * write this characteristic. - * - * @param writeType The write type to for this characteristic. Can be one of: {@link - * #WRITE_TYPE_DEFAULT}, {@link #WRITE_TYPE_NO_RESPONSE} or {@link #WRITE_TYPE_SIGNED}. - */ - public void setWriteType(int writeType) { - mWriteType = writeType; - } - - /** - * Set the desired key size. - * - * @hide - */ - @UnsupportedAppUsage - public void setKeySize(int keySize) { - mKeySize = keySize; - } - - /** - * Returns a list of descriptors for this characteristic. - * - * @return Descriptors for this characteristic - */ - public List<BluetoothGattDescriptor> getDescriptors() { - return mDescriptors; - } - - /** - * Returns a descriptor with a given UUID out of the list of - * descriptors for this characteristic. - * - * @return GATT descriptor object or null if no descriptor with the given UUID was found. - */ - public BluetoothGattDescriptor getDescriptor(UUID uuid) { - for (BluetoothGattDescriptor descriptor : mDescriptors) { - if (descriptor.getUuid().equals(uuid)) { - return descriptor; - } - } - return null; - } - - /** - * Get the stored value for this characteristic. - * - * <p>This function returns the stored value for this characteristic as - * retrieved by calling {@link BluetoothGatt#readCharacteristic}. The cached - * value of the characteristic is updated as a result of a read characteristic - * operation or if a characteristic update notification has been received. - * - * @return Cached value of the characteristic - * - * @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} instead - */ - @Deprecated - public byte[] getValue() { - return mValue; - } - - /** - * Return the stored value of this characteristic. - * - * <p>The formatType parameter determines how the characteristic value - * is to be interpreted. For example, settting formatType to - * {@link #FORMAT_UINT16} specifies that the first two bytes of the - * characteristic value at the given offset are interpreted to generate the - * return value. - * - * @param formatType The format type used to interpret the characteristic value. - * @param offset Offset at which the integer value can be found. - * @return Cached value of the characteristic or null of offset exceeds value size. - * - * @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get - * the characteristic value - */ - @Deprecated - public Integer getIntValue(int formatType, int offset) { - if ((offset + getTypeLen(formatType)) > mValue.length) return null; - - switch (formatType) { - case FORMAT_UINT8: - return unsignedByteToInt(mValue[offset]); - - case FORMAT_UINT16: - return unsignedBytesToInt(mValue[offset], mValue[offset + 1]); - - case FORMAT_UINT32: - return unsignedBytesToInt(mValue[offset], mValue[offset + 1], - mValue[offset + 2], mValue[offset + 3]); - case FORMAT_SINT8: - return unsignedToSigned(unsignedByteToInt(mValue[offset]), 8); - - case FORMAT_SINT16: - return unsignedToSigned(unsignedBytesToInt(mValue[offset], - mValue[offset + 1]), 16); - - case FORMAT_SINT32: - return unsignedToSigned(unsignedBytesToInt(mValue[offset], - mValue[offset + 1], mValue[offset + 2], mValue[offset + 3]), 32); - } - - return null; - } - - /** - * Return the stored value of this characteristic. - * <p>See {@link #getValue} for details. - * - * @param formatType The format type used to interpret the characteristic value. - * @param offset Offset at which the float value can be found. - * @return Cached value of the characteristic at a given offset or null if the requested offset - * exceeds the value size. - * - * @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get - * the characteristic value - */ - @Deprecated - public Float getFloatValue(int formatType, int offset) { - if ((offset + getTypeLen(formatType)) > mValue.length) return null; - - switch (formatType) { - case FORMAT_SFLOAT: - return bytesToFloat(mValue[offset], mValue[offset + 1]); - - case FORMAT_FLOAT: - return bytesToFloat(mValue[offset], mValue[offset + 1], - mValue[offset + 2], mValue[offset + 3]); - } - - return null; - } - - /** - * Return the stored value of this characteristic. - * <p>See {@link #getValue} for details. - * - * @param offset Offset at which the string value can be found. - * @return Cached value of the characteristic - * - * @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get - * the characteristic value - */ - @Deprecated - public String getStringValue(int offset) { - if (mValue == null || offset > mValue.length) return null; - byte[] strBytes = new byte[mValue.length - offset]; - for (int i = 0; i != (mValue.length - offset); ++i) strBytes[i] = mValue[offset + i]; - return new String(strBytes); - } - - /** - * Updates the locally stored value of this characteristic. - * - * <p>This function modifies the locally stored cached value of this - * characteristic. To send the value to the remote device, call - * {@link BluetoothGatt#writeCharacteristic} to send the value to the - * remote device. - * - * @param value New value for this characteristic - * @return true if the locally stored value has been set, false if the requested value could not - * be stored locally. - * - * @deprecated Pass the characteristic value directly into - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} - */ - @Deprecated - public boolean setValue(byte[] value) { - mValue = value; - return true; - } - - /** - * Set the locally stored value of this characteristic. - * <p>See {@link #setValue(byte[])} for details. - * - * @param value New value for this characteristic - * @param formatType Integer format type used to transform the value parameter - * @param offset Offset at which the value should be placed - * @return true if the locally stored value has been set - * - * @deprecated Pass the characteristic value directly into - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} - */ - @Deprecated - public boolean setValue(int value, int formatType, int offset) { - int len = offset + getTypeLen(formatType); - if (mValue == null) mValue = new byte[len]; - if (len > mValue.length) return false; - - switch (formatType) { - case FORMAT_SINT8: - value = intToSignedBits(value, 8); - // Fall-through intended - case FORMAT_UINT8: - mValue[offset] = (byte) (value & 0xFF); - break; - - case FORMAT_SINT16: - value = intToSignedBits(value, 16); - // Fall-through intended - case FORMAT_UINT16: - mValue[offset++] = (byte) (value & 0xFF); - mValue[offset] = (byte) ((value >> 8) & 0xFF); - break; - - case FORMAT_SINT32: - value = intToSignedBits(value, 32); - // Fall-through intended - case FORMAT_UINT32: - mValue[offset++] = (byte) (value & 0xFF); - mValue[offset++] = (byte) ((value >> 8) & 0xFF); - mValue[offset++] = (byte) ((value >> 16) & 0xFF); - mValue[offset] = (byte) ((value >> 24) & 0xFF); - break; - - default: - return false; - } - return true; - } - - /** - * Set the locally stored value of this characteristic. - * <p>See {@link #setValue(byte[])} for details. - * - * @param mantissa Mantissa for this characteristic - * @param exponent exponent value for this characteristic - * @param formatType Float format type used to transform the value parameter - * @param offset Offset at which the value should be placed - * @return true if the locally stored value has been set - * - * @deprecated Pass the characteristic value directly into - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} - */ - @Deprecated - public boolean setValue(int mantissa, int exponent, int formatType, int offset) { - int len = offset + getTypeLen(formatType); - if (mValue == null) mValue = new byte[len]; - if (len > mValue.length) return false; - - switch (formatType) { - case FORMAT_SFLOAT: - mantissa = intToSignedBits(mantissa, 12); - exponent = intToSignedBits(exponent, 4); - mValue[offset++] = (byte) (mantissa & 0xFF); - mValue[offset] = (byte) ((mantissa >> 8) & 0x0F); - mValue[offset] += (byte) ((exponent & 0x0F) << 4); - break; - - case FORMAT_FLOAT: - mantissa = intToSignedBits(mantissa, 24); - exponent = intToSignedBits(exponent, 8); - mValue[offset++] = (byte) (mantissa & 0xFF); - mValue[offset++] = (byte) ((mantissa >> 8) & 0xFF); - mValue[offset++] = (byte) ((mantissa >> 16) & 0xFF); - mValue[offset] += (byte) (exponent & 0xFF); - break; - - default: - return false; - } - - return true; - } - - /** - * Set the locally stored value of this characteristic. - * <p>See {@link #setValue(byte[])} for details. - * - * @param value New value for this characteristic - * @return true if the locally stored value has been set - * - * @deprecated Pass the characteristic value directly into - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} - */ - @Deprecated - public boolean setValue(String value) { - mValue = value.getBytes(); - return true; - } - - /** - * Returns the size of a give value type. - */ - private int getTypeLen(int formatType) { - return formatType & 0xF; - } - - /** - * Convert a signed byte to an unsigned int. - */ - private int unsignedByteToInt(byte b) { - return b & 0xFF; - } - - /** - * Convert signed bytes to a 16-bit unsigned int. - */ - private int unsignedBytesToInt(byte b0, byte b1) { - return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8)); - } - - /** - * Convert signed bytes to a 32-bit unsigned int. - */ - private int unsignedBytesToInt(byte b0, byte b1, byte b2, byte b3) { - return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8)) - + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24); - } - - /** - * Convert signed bytes to a 16-bit short float value. - */ - private float bytesToFloat(byte b0, byte b1) { - int mantissa = unsignedToSigned(unsignedByteToInt(b0) - + ((unsignedByteToInt(b1) & 0x0F) << 8), 12); - int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4); - return (float) (mantissa * Math.pow(10, exponent)); - } - - /** - * Convert signed bytes to a 32-bit short float value. - */ - private float bytesToFloat(byte b0, byte b1, byte b2, byte b3) { - int mantissa = unsignedToSigned(unsignedByteToInt(b0) - + (unsignedByteToInt(b1) << 8) - + (unsignedByteToInt(b2) << 16), 24); - return (float) (mantissa * Math.pow(10, b3)); - } - - /** - * Convert an unsigned integer value to a two's-complement encoded - * signed value. - */ - private int unsignedToSigned(int unsigned, int size) { - if ((unsigned & (1 << size - 1)) != 0) { - unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1))); - } - return unsigned; - } - - /** - * Convert an integer into the signed bits of a given length. - */ - private int intToSignedBits(int i, int size) { - if (i < 0) { - i = (1 << size - 1) + (i & ((1 << size - 1) - 1)); - } - return i; - } -} diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java deleted file mode 100644 index 6ed4706b5fba..000000000000 --- a/core/java/android/bluetooth/BluetoothGattDescriptor.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.UUID; - -/** - * Represents a Bluetooth GATT Descriptor - * - * <p> GATT Descriptors contain additional information and attributes of a GATT - * characteristic, {@link BluetoothGattCharacteristic}. They can be used to describe - * the characteristic's features or to control certain behaviours of the characteristic. - */ -public class BluetoothGattDescriptor implements Parcelable { - - /** - * Value used to enable notification for a client configuration descriptor - */ - public static final byte[] ENABLE_NOTIFICATION_VALUE = {0x01, 0x00}; - - /** - * Value used to enable indication for a client configuration descriptor - */ - public static final byte[] ENABLE_INDICATION_VALUE = {0x02, 0x00}; - - /** - * Value used to disable notifications or indicatinos - */ - public static final byte[] DISABLE_NOTIFICATION_VALUE = {0x00, 0x00}; - - /** - * Descriptor read permission - */ - public static final int PERMISSION_READ = 0x01; - - /** - * Descriptor permission: Allow encrypted read operations - */ - public static final int PERMISSION_READ_ENCRYPTED = 0x02; - - /** - * Descriptor permission: Allow reading with person-in-the-middle protection - */ - public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04; - - /** - * Descriptor write permission - */ - public static final int PERMISSION_WRITE = 0x10; - - /** - * Descriptor permission: Allow encrypted writes - */ - public static final int PERMISSION_WRITE_ENCRYPTED = 0x20; - - /** - * Descriptor permission: Allow encrypted writes with person-in-the-middle - * protection - */ - public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40; - - /** - * Descriptor permission: Allow signed write operations - */ - public static final int PERMISSION_WRITE_SIGNED = 0x80; - - /** - * Descriptor permission: Allow signed write operations with - * person-in-the-middle protection - */ - public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100; - - /** - * The UUID of this descriptor. - * - * @hide - */ - protected UUID mUuid; - - /** - * Instance ID for this descriptor. - * - * @hide - */ - @UnsupportedAppUsage - protected int mInstance; - - /** - * Permissions for this descriptor - * - * @hide - */ - protected int mPermissions; - - /** - * Back-reference to the characteristic this descriptor belongs to. - * - * @hide - */ - @UnsupportedAppUsage - protected BluetoothGattCharacteristic mCharacteristic; - - /** - * The value for this descriptor. - * - * @hide - */ - protected byte[] mValue; - - /** - * Create a new BluetoothGattDescriptor. - * - * @param uuid The UUID for this descriptor - * @param permissions Permissions for this descriptor - */ - public BluetoothGattDescriptor(UUID uuid, int permissions) { - initDescriptor(null, uuid, 0, permissions); - } - - /** - * Create a new BluetoothGattDescriptor. - * - * @param characteristic The characteristic this descriptor belongs to - * @param uuid The UUID for this descriptor - * @param permissions Permissions for this descriptor - */ - /*package*/ BluetoothGattDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid, - int instance, int permissions) { - initDescriptor(characteristic, uuid, instance, permissions); - } - - /** - * @hide - */ - public BluetoothGattDescriptor(UUID uuid, int instance, int permissions) { - initDescriptor(null, uuid, instance, permissions); - } - - private void initDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid, - int instance, int permissions) { - mCharacteristic = characteristic; - mUuid = uuid; - mInstance = instance; - mPermissions = permissions; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeInt(mInstance); - out.writeInt(mPermissions); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattDescriptor> CREATOR = - new Parcelable.Creator<BluetoothGattDescriptor>() { - public BluetoothGattDescriptor createFromParcel(Parcel in) { - return new BluetoothGattDescriptor(in); - } - - public BluetoothGattDescriptor[] newArray(int size) { - return new BluetoothGattDescriptor[size]; - } - }; - - private BluetoothGattDescriptor(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid(); - mInstance = in.readInt(); - mPermissions = in.readInt(); - } - - /** - * Returns the characteristic this descriptor belongs to. - * - * @return The characteristic. - */ - public BluetoothGattCharacteristic getCharacteristic() { - return mCharacteristic; - } - - /** - * Set the back-reference to the associated characteristic - * - * @hide - */ - @UnsupportedAppUsage - /*package*/ void setCharacteristic(BluetoothGattCharacteristic characteristic) { - mCharacteristic = characteristic; - } - - /** - * Returns the UUID of this descriptor. - * - * @return UUID of this descriptor - */ - public UUID getUuid() { - return mUuid; - } - - /** - * Returns the instance ID for this descriptor. - * - * <p>If a remote device offers multiple descriptors with the same UUID, - * the instance ID is used to distuinguish between descriptors. - * - * @return Instance ID of this descriptor - * @hide - */ - public int getInstanceId() { - return mInstance; - } - - /** - * Force the instance ID. - * - * @hide - */ - public void setInstanceId(int instanceId) { - mInstance = instanceId; - } - - /** - * Returns the permissions for this descriptor. - * - * @return Permissions of this descriptor - */ - public int getPermissions() { - return mPermissions; - } - - /** - * Returns the stored value for this descriptor - * - * <p>This function returns the stored value for this descriptor as - * retrieved by calling {@link BluetoothGatt#readDescriptor}. The cached - * value of the descriptor is updated as a result of a descriptor read - * operation. - * - * @return Cached value of the descriptor - * - * @deprecated Use {@link BluetoothGatt#readDescriptor(BluetoothGattDescriptor)} instead - */ - @Deprecated - public byte[] getValue() { - return mValue; - } - - /** - * Updates the locally stored value of this descriptor. - * - * <p>This function modifies the locally stored cached value of this - * descriptor. To send the value to the remote device, call - * {@link BluetoothGatt#writeDescriptor} to send the value to the - * remote device. - * - * @param value New value for this descriptor - * @return true if the locally stored value has been set, false if the requested value could not - * be stored locally. - * - * @deprecated Pass the descriptor value directly into - * {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, byte[])} - */ - @Deprecated - public boolean setValue(byte[] value) { - mValue = value; - return true; - } -} diff --git a/core/java/android/bluetooth/BluetoothGattIncludedService.java b/core/java/android/bluetooth/BluetoothGattIncludedService.java deleted file mode 100644 index 1ae2ca0a92e1..000000000000 --- a/core/java/android/bluetooth/BluetoothGattIncludedService.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.bluetooth; - -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.UUID; - -/** - * Represents a Bluetooth GATT Included Service - * - * @hide - */ -public class BluetoothGattIncludedService implements Parcelable { - - /** - * The UUID of this service. - */ - protected UUID mUuid; - - /** - * Instance ID for this service. - */ - protected int mInstanceId; - - /** - * Service type (Primary/Secondary). - */ - protected int mServiceType; - - /** - * Create a new BluetoothGattIncludedService - */ - public BluetoothGattIncludedService(UUID uuid, int instanceId, int serviceType) { - mUuid = uuid; - mInstanceId = instanceId; - mServiceType = serviceType; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeInt(mInstanceId); - out.writeInt(mServiceType); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattIncludedService> CREATOR = - new Parcelable.Creator<BluetoothGattIncludedService>() { - public BluetoothGattIncludedService createFromParcel(Parcel in) { - return new BluetoothGattIncludedService(in); - } - - public BluetoothGattIncludedService[] newArray(int size) { - return new BluetoothGattIncludedService[size]; - } - }; - - private BluetoothGattIncludedService(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid(); - mInstanceId = in.readInt(); - mServiceType = in.readInt(); - } - - /** - * Returns the UUID of this service - * - * @return UUID of this service - */ - public UUID getUuid() { - return mUuid; - } - - /** - * Returns the instance ID for this service - * - * <p>If a remote device offers multiple services with the same UUID - * (ex. multiple battery services for different batteries), the instance - * ID is used to distuinguish services. - * - * @return Instance ID of this service - */ - public int getInstanceId() { - return mInstanceId; - } - - /** - * Get the type of this service (primary/secondary) - */ - public int getType() { - return mServiceType; - } -} diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java deleted file mode 100644 index 08e0178403f1..000000000000 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ /dev/null @@ -1,954 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Public API for the Bluetooth GATT Profile server role. - * - * <p>This class provides Bluetooth GATT server role functionality, - * allowing applications to create Bluetooth Smart services and - * characteristics. - * - * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service - * via IPC. Use {@link BluetoothManager#openGattServer} to get an instance - * of this class. - */ -public final class BluetoothGattServer implements BluetoothProfile { - private static final String TAG = "BluetoothGattServer"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - private final IBluetoothGatt mService; - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - - private BluetoothGattServerCallback mCallback; - - private Object mServerIfLock = new Object(); - private int mServerIf; - private int mTransport; - private BluetoothGattService mPendingService; - private List<BluetoothGattService> mServices; - - private static final int CALLBACK_REG_TIMEOUT = 10000; - - /** - * Bluetooth GATT interface callbacks - */ - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothGattServerCallback mBluetoothGattServerCallback = - new IBluetoothGattServerCallback.Stub() { - /** - * Application interface registered - app is ready to go - * @hide - */ - @Override - public void onServerRegistered(int status, int serverIf) { - if (DBG) { - Log.d(TAG, "onServerRegistered() - status=" + status - + " serverIf=" + serverIf); - } - synchronized (mServerIfLock) { - if (mCallback != null) { - mServerIf = serverIf; - mServerIfLock.notify(); - } else { - // registration timeout - Log.e(TAG, "onServerRegistered: mCallback is null"); - } - } - } - - /** - * Server connection state changed - * @hide - */ - @Override - public void onServerConnectionState(int status, int serverIf, - boolean connected, String address) { - if (DBG) { - Log.d(TAG, "onServerConnectionState() - status=" + status - + " serverIf=" + serverIf + " device=" + address); - } - try { - mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status, - connected ? BluetoothProfile.STATE_CONNECTED : - BluetoothProfile.STATE_DISCONNECTED); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Service has been added - * @hide - */ - @Override - public void onServiceAdded(int status, BluetoothGattService service) { - if (DBG) { - Log.d(TAG, "onServiceAdded() - handle=" + service.getInstanceId() - + " uuid=" + service.getUuid() + " status=" + status); - } - - if (mPendingService == null) { - return; - } - - BluetoothGattService tmp = mPendingService; - mPendingService = null; - - // Rewrite newly assigned handles to existing service. - tmp.setInstanceId(service.getInstanceId()); - List<BluetoothGattCharacteristic> temp_chars = tmp.getCharacteristics(); - List<BluetoothGattCharacteristic> svc_chars = service.getCharacteristics(); - for (int i = 0; i < svc_chars.size(); i++) { - BluetoothGattCharacteristic temp_char = temp_chars.get(i); - BluetoothGattCharacteristic svc_char = svc_chars.get(i); - - temp_char.setInstanceId(svc_char.getInstanceId()); - - List<BluetoothGattDescriptor> temp_descs = temp_char.getDescriptors(); - List<BluetoothGattDescriptor> svc_descs = svc_char.getDescriptors(); - for (int j = 0; j < svc_descs.size(); j++) { - temp_descs.get(j).setInstanceId(svc_descs.get(j).getInstanceId()); - } - } - - mServices.add(tmp); - - try { - mCallback.onServiceAdded((int) status, tmp); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Remote client characteristic read request. - * @hide - */ - @Override - public void onCharacteristicReadRequest(String address, int transId, - int offset, boolean isLong, int handle) { - if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle); - if (characteristic == null) { - Log.w(TAG, "onCharacteristicReadRequest() no char for handle " + handle); - return; - } - - try { - mCallback.onCharacteristicReadRequest(device, transId, offset, - characteristic); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Remote client descriptor read request. - * @hide - */ - @Override - public void onDescriptorReadRequest(String address, int transId, - int offset, boolean isLong, int handle) { - if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle); - if (descriptor == null) { - Log.w(TAG, "onDescriptorReadRequest() no desc for handle " + handle); - return; - } - - try { - mCallback.onDescriptorReadRequest(device, transId, offset, descriptor); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Remote client characteristic write request. - * @hide - */ - @Override - public void onCharacteristicWriteRequest(String address, int transId, - int offset, int length, boolean isPrep, boolean needRsp, - int handle, byte[] value) { - if (VDBG) Log.d(TAG, "onCharacteristicWriteRequest() - handle=" + handle); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle); - if (characteristic == null) { - Log.w(TAG, "onCharacteristicWriteRequest() no char for handle " + handle); - return; - } - - try { - mCallback.onCharacteristicWriteRequest(device, transId, characteristic, - isPrep, needRsp, offset, value); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - - } - - /** - * Remote client descriptor write request. - * @hide - */ - @Override - public void onDescriptorWriteRequest(String address, int transId, int offset, - int length, boolean isPrep, boolean needRsp, int handle, byte[] value) { - if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - handle=" + handle); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle); - if (descriptor == null) { - Log.w(TAG, "onDescriptorWriteRequest() no desc for handle " + handle); - return; - } - - try { - mCallback.onDescriptorWriteRequest(device, transId, descriptor, - isPrep, needRsp, offset, value); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Execute pending writes. - * @hide - */ - @Override - public void onExecuteWrite(String address, int transId, - boolean execWrite) { - if (DBG) { - Log.d(TAG, "onExecuteWrite() - " - + "device=" + address + ", transId=" + transId - + "execWrite=" + execWrite); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onExecuteWrite(device, transId, execWrite); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * A notification/indication has been sent. - * @hide - */ - @Override - public void onNotificationSent(String address, int status) { - if (VDBG) { - Log.d(TAG, "onNotificationSent() - " - + "device=" + address + ", status=" + status); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onNotificationSent(device, status); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - /** - * The MTU for a connection has changed - * @hide - */ - @Override - public void onMtuChanged(String address, int mtu) { - if (DBG) { - Log.d(TAG, "onMtuChanged() - " - + "device=" + address + ", mtu=" + mtu); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onMtuChanged(device, mtu); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - /** - * The PHY for a connection was updated - * @hide - */ - @Override - public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) { - if (DBG) { - Log.d(TAG, - "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy - + ", rxPHy=" + rxPhy); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onPhyUpdate(device, txPhy, rxPhy, status); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - /** - * The PHY for a connection was read - * @hide - */ - @Override - public void onPhyRead(String address, int txPhy, int rxPhy, int status) { - if (DBG) { - Log.d(TAG, - "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy - + ", rxPHy=" + rxPhy); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onPhyRead(device, txPhy, rxPhy, status); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - /** - * Callback invoked when the given connection is updated - * @hide - */ - @Override - public void onConnectionUpdated(String address, int interval, int latency, - int timeout, int status) { - if (DBG) { - Log.d(TAG, "onConnectionUpdated() - Device=" + address - + " interval=" + interval + " latency=" + latency - + " timeout=" + timeout + " status=" + status); - } - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onConnectionUpdated(device, interval, latency, - timeout, status); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - }; - - /** - * Create a BluetoothGattServer proxy object. - */ - /* package */ BluetoothGattServer(IBluetoothGatt iGatt, int transport, - BluetoothAdapter adapter) { - mService = iGatt; - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mCallback = null; - mServerIf = 0; - mTransport = transport; - mServices = new ArrayList<BluetoothGattService>(); - } - - /** - * Returns a characteristic with given handle. - * - * @hide - */ - /*package*/ BluetoothGattCharacteristic getCharacteristicByHandle(int handle) { - for (BluetoothGattService svc : mServices) { - for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - if (charac.getInstanceId() == handle) { - return charac; - } - } - } - return null; - } - - /** - * Returns a descriptor with given handle. - * - * @hide - */ - /*package*/ BluetoothGattDescriptor getDescriptorByHandle(int handle) { - for (BluetoothGattService svc : mServices) { - for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - for (BluetoothGattDescriptor desc : charac.getDescriptors()) { - if (desc.getInstanceId() == handle) { - return desc; - } - } - } - } - return null; - } - - /** - * Close this GATT server instance. - * - * Application should call this method as early as possible after it is done with - * this GATT server. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void close() { - if (DBG) Log.d(TAG, "close()"); - unregisterCallback(); - } - - /** - * Register an application callback to start using GattServer. - * - * <p>This is an asynchronous call. The callback is used to notify - * success or failure if the function returns true. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @return true, the callback will be called to notify success or failure, false on immediate - * error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) { - return registerCallback(callback, false); - } - - /** - * Register an application callback to start using GattServer. - * - * <p>This is an asynchronous call. The callback is used to notify - * success or failure if the function returns true. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param eatt_support indicates if server can use eatt - * @return true, the callback will be called to notify success or failure, false on immediate - * error - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - /*package*/ boolean registerCallback(BluetoothGattServerCallback callback, - boolean eatt_support) { - if (DBG) Log.d(TAG, "registerCallback()"); - if (mService == null) { - Log.e(TAG, "GATT service not available"); - return false; - } - UUID uuid = UUID.randomUUID(); - if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid); - - synchronized (mServerIfLock) { - if (mCallback != null) { - Log.e(TAG, "App can register callback only once"); - return false; - } - - mCallback = callback; - try { - mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback, - eatt_support, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - mCallback = null; - return false; - } - - try { - mServerIfLock.wait(CALLBACK_REG_TIMEOUT); - } catch (InterruptedException e) { - Log.e(TAG, "" + e); - mCallback = null; - } - - if (mServerIf == 0) { - mCallback = null; - return false; - } else { - return true; - } - } - } - - /** - * Unregister the current application and callbacks. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void unregisterCallback() { - if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf); - if (mService == null || mServerIf == 0) return; - - try { - mCallback = null; - mService.unregisterServer(mServerIf, mAttributionSource); - mServerIf = 0; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Returns a service by UUID, instance and type. - * - * @hide - */ - /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) { - for (BluetoothGattService svc : mServices) { - if (svc.getType() == type - && svc.getInstanceId() == instanceId - && svc.getUuid().equals(uuid)) { - return svc; - } - } - return null; - } - - /** - * Initiate a connection to a Bluetooth GATT capable device. - * - * <p>The connection may not be established right away, but will be - * completed when the remote device is available. A - * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be - * invoked when the connection state changes as a result of this function. - * - * <p>The autoConnect parameter determines whether to actively connect to - * the remote device, or rather passively scan and finalize the connection - * when the remote device is in range/available. Generally, the first ever - * connection to a device should be direct (autoConnect set to false) and - * subsequent connections to known devices should be invoked with the - * autoConnect parameter set to true. - * - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @return true, if the connection attempt was initiated successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect(BluetoothDevice device, boolean autoConnect) { - if (DBG) { - Log.d(TAG, - "connect() - device: " + device.getAddress() + ", auto: " + autoConnect); - } - if (mService == null || mServerIf == 0) return false; - - try { - // autoConnect is inverse of "isDirect" - mService.serverConnect( - mServerIf, device.getAddress(), !autoConnect, mTransport, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Disconnects an established connection, or cancels a connection attempt - * currently in progress. - * - * @param device Remote device - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void cancelConnection(BluetoothDevice device) { - if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress()); - if (mService == null || mServerIf == 0) return; - - try { - mService.serverDisconnect(mServerIf, device.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Set the preferred connection PHY for this app. Please note that this is just a - * recommendation, whether the PHY change will happen depends on other applications peferences, - * local and remote controller capabilities. Controller can override these settings. <p> {@link - * BluetoothGattServerCallback#onPhyUpdate} will be triggered as a result of this call, even if - * no PHY change happens. It is also triggered when remote device updates the PHY. - * - * @param device The remote device to send this response to - * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. - * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. - * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one - * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or - * {@link BluetoothDevice#PHY_OPTION_S8} - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) { - try { - mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy, - phyOptions, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Read the current transmitter PHY and receiver PHY of the connection. The values are returned - * in {@link BluetoothGattServerCallback#onPhyRead} - * - * @param device The remote device to send this response to - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void readPhy(BluetoothDevice device) { - try { - mService.serverReadPhy(mServerIf, device.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Send a response to a read or write request to a remote device. - * - * <p>This function must be invoked in when a remote read/write request - * is received by one of these callback methods: - * - * <ul> - * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest} - * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest} - * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest} - * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest} - * </ul> - * - * @param device The remote device to send this response to - * @param requestId The ID of the request that was received with the callback - * @param status The status of the request to be sent to the remote devices - * @param offset Value offset for partial read/write response - * @param value The value of the attribute that was read/written (optional) - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendResponse(BluetoothDevice device, int requestId, - int status, int offset, byte[] value) { - if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress()); - if (mService == null || mServerIf == 0) return false; - - try { - mService.sendResponse(mServerIf, device.getAddress(), requestId, - status, offset, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - return true; - } - - /** - * Send a notification or indication that a local characteristic has been - * updated. - * - * <p>A notification or indication is sent to the remote device to signal - * that the characteristic has been updated. This function should be invoked - * for every client that requests notifications/indications by writing - * to the "Client Configuration" descriptor for the given characteristic. - * - * @param device The remote device to receive the notification/indication - * @param characteristic The local characteristic that has been updated - * @param confirm true to request confirmation from the client (indication), false to send a - * notification - * @return true, if the notification has been triggered successfully - * @throws IllegalArgumentException - * - * @deprecated Use {@link BluetoothGattServer#notifyCharacteristicChanged(BluetoothDevice, - * BluetoothGattCharacteristic, boolean, byte[])} as this is not memory safe. - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean notifyCharacteristicChanged(BluetoothDevice device, - BluetoothGattCharacteristic characteristic, boolean confirm) { - return notifyCharacteristicChanged(device, characteristic, confirm, - characteristic.getValue()) == BluetoothStatusCodes.SUCCESS; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION, - BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED, - BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, - BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED, - BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY, - BluetoothStatusCodes.ERROR_UNKNOWN - }) - public @interface NotifyCharacteristicReturnValues{} - - /** - * Send a notification or indication that a local characteristic has been - * updated. - * - * <p>A notification or indication is sent to the remote device to signal - * that the characteristic has been updated. This function should be invoked - * for every client that requests notifications/indications by writing - * to the "Client Configuration" descriptor for the given characteristic. - * - * @param device the remote device to receive the notification/indication - * @param characteristic the local characteristic that has been updated - * @param confirm {@code true} to request confirmation from the client (indication) or - * {@code false} to send a notification - * @param value the characteristic value - * @return whether the notification has been triggered successfully - * @throws IllegalArgumentException if the characteristic value or service is null - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @NotifyCharacteristicReturnValues - public int notifyCharacteristicChanged(@NonNull BluetoothDevice device, - @NonNull BluetoothGattCharacteristic characteristic, boolean confirm, - @NonNull byte[] value) { - if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress()); - if (mService == null || mServerIf == 0) { - return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; - } - - if (characteristic == null) { - throw new IllegalArgumentException("characteristic must not be null"); - } - if (device == null) { - throw new IllegalArgumentException("device must not be null"); - } - BluetoothGattService service = characteristic.getService(); - if (service == null) { - throw new IllegalArgumentException("Characteristic must have a non-null service"); - } - if (value == null) { - throw new IllegalArgumentException("Characteristic value must not be null"); - } - - try { - return mService.sendNotification(mServerIf, device.getAddress(), - characteristic.getInstanceId(), confirm, - value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Add a service to the list of services to be hosted. - * - * <p>Once a service has been addded to the list, the service and its - * included characteristics will be provided by the local device. - * - * <p>If the local device has already exposed services when this function - * is called, a service update notification will be sent to all clients. - * - * <p>The {@link BluetoothGattServerCallback#onServiceAdded} callback will indicate - * whether this service has been added successfully. Do not add another service - * before this callback. - * - * @param service Service to be added to the list of services provided by this device. - * @return true, if the request to add service has been initiated - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean addService(BluetoothGattService service) { - if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid()); - if (mService == null || mServerIf == 0) return false; - - mPendingService = service; - - try { - mService.addService(mServerIf, service, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Removes a service from the list of services to be provided. - * - * @param service Service to be removed. - * @return true, if the service has been removed - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean removeService(BluetoothGattService service) { - if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid()); - if (mService == null || mServerIf == 0) return false; - - BluetoothGattService intService = getService(service.getUuid(), - service.getInstanceId(), service.getType()); - if (intService == null) return false; - - try { - mService.removeService(mServerIf, service.getInstanceId(), mAttributionSource); - mServices.remove(intService); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Remove all services from the list of provided services. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void clearServices() { - if (DBG) Log.d(TAG, "clearServices()"); - if (mService == null || mServerIf == 0) return; - - try { - mService.clearServices(mServerIf, mAttributionSource); - mServices.clear(); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Returns a list of GATT services offered by this device. - * - * <p>An application must call {@link #addService} to add a serice to the - * list of services offered by this device. - * - * @return List of services. Returns an empty list if no services have been added yet. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public List<BluetoothGattService> getServices() { - return mServices; - } - - /** - * Returns a {@link BluetoothGattService} from the list of services offered - * by this device. - * - * <p>If multiple instances of the same service (as identified by UUID) - * exist, the first instance of the service is returned. - * - * @param uuid UUID of the requested service - * @return BluetoothGattService if supported, or null if the requested service is not offered by - * this device. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public BluetoothGattService getService(UUID uuid) { - for (BluetoothGattService service : mServices) { - if (service.getUuid().equals(uuid)) { - return service; - } - } - - return null; - } - - - /** - * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} - * with {@link BluetoothProfile#GATT} as argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - public int getConnectionState(BluetoothDevice device) { - throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); - } - - /** - * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} - * with {@link BluetoothProfile#GATT} as argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - public List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException( - "Use BluetoothManager#getConnectedDevices instead."); - } - - /** - * Not supported - please use - * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])} - * with {@link BluetoothProfile#GATT} as first argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - throw new UnsupportedOperationException( - "Use BluetoothManager#getDevicesMatchingConnectionStates instead."); - } -} diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java deleted file mode 100644 index 0ead5f57e86c..000000000000 --- a/core/java/android/bluetooth/BluetoothGattServerCallback.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -/** - * This abstract class is used to implement {@link BluetoothGattServer} callbacks. - */ -public abstract class BluetoothGattServerCallback { - - /** - * Callback indicating when a remote device has been connected or disconnected. - * - * @param device Remote device that has been connected or disconnected. - * @param status Status of the connect or disconnect operation. - * @param newState Returns the new connection state. Can be one of {@link - * BluetoothProfile#STATE_DISCONNECTED} or {@link BluetoothProfile#STATE_CONNECTED} - */ - public void onConnectionStateChange(BluetoothDevice device, int status, - int newState) { - } - - /** - * Indicates whether a local service has been added successfully. - * - * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the service was added - * successfully. - * @param service The service that has been added - */ - public void onServiceAdded(int status, BluetoothGattService service) { - } - - /** - * A remote client has requested to read a local characteristic. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the read operation - * @param requestId The Id of the request - * @param offset Offset into the value of the characteristic - * @param characteristic Characteristic to be read - */ - public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, - int offset, BluetoothGattCharacteristic characteristic) { - } - - /** - * A remote client has requested to write to a local characteristic. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the write operation - * @param requestId The Id of the request - * @param characteristic Characteristic to be written to. - * @param preparedWrite true, if this write operation should be queued for later execution. - * @param responseNeeded true, if the remote device requires a response - * @param offset The offset given for the value - * @param value The value the client wants to assign to the characteristic - */ - public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, - BluetoothGattCharacteristic characteristic, - boolean preparedWrite, boolean responseNeeded, - int offset, byte[] value) { - } - - /** - * A remote client has requested to read a local descriptor. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the read operation - * @param requestId The Id of the request - * @param offset Offset into the value of the characteristic - * @param descriptor Descriptor to be read - */ - public void onDescriptorReadRequest(BluetoothDevice device, int requestId, - int offset, BluetoothGattDescriptor descriptor) { - } - - /** - * A remote client has requested to write to a local descriptor. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the write operation - * @param requestId The Id of the request - * @param descriptor Descriptor to be written to. - * @param preparedWrite true, if this write operation should be queued for later execution. - * @param responseNeeded true, if the remote device requires a response - * @param offset The offset given for the value - * @param value The value the client wants to assign to the descriptor - */ - public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, - BluetoothGattDescriptor descriptor, - boolean preparedWrite, boolean responseNeeded, - int offset, byte[] value) { - } - - /** - * Execute all pending write operations for this device. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the write operations - * @param requestId The Id of the request - * @param execute Whether the pending writes should be executed (true) or cancelled (false) - */ - public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { - } - - /** - * Callback invoked when a notification or indication has been sent to - * a remote device. - * - * <p>When multiple notifications are to be sent, an application must - * wait for this callback to be received before sending additional - * notifications. - * - * @param device The remote device the notification has been sent to - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the operation was successful - */ - public void onNotificationSent(BluetoothDevice device, int status) { - } - - /** - * Callback indicating the MTU for a given device connection has changed. - * - * <p>This callback will be invoked if a remote client has requested to change - * the MTU for a given connection. - * - * @param device The remote device that requested the MTU change - * @param mtu The new MTU size - */ - public void onMtuChanged(BluetoothDevice device, int mtu) { - } - - /** - * Callback triggered as result of {@link BluetoothGattServer#setPreferredPhy}, or as a result - * of remote device changing the PHY. - * - * @param device The remote device - * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED} - * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED} - * @param status Status of the PHY update operation. {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) { - } - - /** - * Callback triggered as result of {@link BluetoothGattServer#readPhy} - * - * @param device The remote device that requested the PHY read - * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED} - * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED} - * @param status Status of the PHY read operation. {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) { - } - - /** - * Callback indicating the connection parameters were updated. - * - * @param device The remote device involved - * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from - * 6 (7.5ms) to 3200 (4000ms). - * @param latency Worker latency for the connection in number of connection events. Valid range - * is from 0 to 499 - * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10 - * (0.1s) to 3200 (32s) - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated - * successfully - * @hide - */ - public void onConnectionUpdated(BluetoothDevice device, int interval, int latency, int timeout, - int status) { - } - -} diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java deleted file mode 100644 index 36bc4772e016..000000000000 --- a/core/java/android/bluetooth/BluetoothGattService.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.bluetooth; - -import android.annotation.RequiresPermission; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Represents a Bluetooth GATT Service - * - * <p> Gatt Service contains a collection of {@link BluetoothGattCharacteristic}, - * as well as referenced services. - */ -public class BluetoothGattService implements Parcelable { - - /** - * Primary service - */ - public static final int SERVICE_TYPE_PRIMARY = 0; - - /** - * Secondary service (included by primary services) - */ - public static final int SERVICE_TYPE_SECONDARY = 1; - - - /** - * The remote device this service is associated with. - * This applies to client applications only. - * - * @hide - */ - @UnsupportedAppUsage - protected BluetoothDevice mDevice; - - /** - * The UUID of this service. - * - * @hide - */ - protected UUID mUuid; - - /** - * Instance ID for this service. - * - * @hide - */ - protected int mInstanceId; - - /** - * Handle counter override (for conformance testing). - * - * @hide - */ - protected int mHandles = 0; - - /** - * Service type (Primary/Secondary). - * - * @hide - */ - protected int mServiceType; - - /** - * List of characteristics included in this service. - */ - protected List<BluetoothGattCharacteristic> mCharacteristics; - - /** - * List of included services for this service. - */ - protected List<BluetoothGattService> mIncludedServices; - - /** - * Whether the service uuid should be advertised. - */ - private boolean mAdvertisePreferred; - - /** - * Create a new BluetoothGattService. - * - * @param uuid The UUID for this service - * @param serviceType The type of this service, - * {@link BluetoothGattService#SERVICE_TYPE_PRIMARY} - * or {@link BluetoothGattService#SERVICE_TYPE_SECONDARY} - */ - public BluetoothGattService(UUID uuid, int serviceType) { - mDevice = null; - mUuid = uuid; - mInstanceId = 0; - mServiceType = serviceType; - mCharacteristics = new ArrayList<BluetoothGattCharacteristic>(); - mIncludedServices = new ArrayList<BluetoothGattService>(); - } - - /** - * Create a new BluetoothGattService - * - * @hide - */ - /*package*/ BluetoothGattService(BluetoothDevice device, UUID uuid, - int instanceId, int serviceType) { - mDevice = device; - mUuid = uuid; - mInstanceId = instanceId; - mServiceType = serviceType; - mCharacteristics = new ArrayList<BluetoothGattCharacteristic>(); - mIncludedServices = new ArrayList<BluetoothGattService>(); - } - - /** - * Create a new BluetoothGattService - * - * @hide - */ - public BluetoothGattService(UUID uuid, int instanceId, int serviceType) { - mDevice = null; - mUuid = uuid; - mInstanceId = instanceId; - mServiceType = serviceType; - mCharacteristics = new ArrayList<BluetoothGattCharacteristic>(); - mIncludedServices = new ArrayList<BluetoothGattService>(); - } - - /** - * @hide - */ - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeInt(mInstanceId); - out.writeInt(mServiceType); - out.writeTypedList(mCharacteristics); - - ArrayList<BluetoothGattIncludedService> includedServices = - new ArrayList<BluetoothGattIncludedService>(mIncludedServices.size()); - for (BluetoothGattService s : mIncludedServices) { - includedServices.add(new BluetoothGattIncludedService(s.getUuid(), - s.getInstanceId(), s.getType())); - } - out.writeTypedList(includedServices); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattService> CREATOR = - new Parcelable.Creator<BluetoothGattService>() { - public BluetoothGattService createFromParcel(Parcel in) { - return new BluetoothGattService(in); - } - - public BluetoothGattService[] newArray(int size) { - return new BluetoothGattService[size]; - } - }; - - private BluetoothGattService(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid(); - mInstanceId = in.readInt(); - mServiceType = in.readInt(); - - mCharacteristics = new ArrayList<BluetoothGattCharacteristic>(); - - ArrayList<BluetoothGattCharacteristic> chrcs = - in.createTypedArrayList(BluetoothGattCharacteristic.CREATOR); - if (chrcs != null) { - for (BluetoothGattCharacteristic chrc : chrcs) { - chrc.setService(this); - mCharacteristics.add(chrc); - } - } - - mIncludedServices = new ArrayList<BluetoothGattService>(); - - ArrayList<BluetoothGattIncludedService> inclSvcs = - in.createTypedArrayList(BluetoothGattIncludedService.CREATOR); - if (chrcs != null) { - for (BluetoothGattIncludedService isvc : inclSvcs) { - mIncludedServices.add(new BluetoothGattService(null, isvc.getUuid(), - isvc.getInstanceId(), isvc.getType())); - } - } - } - - /** - * Returns the device associated with this service. - * - * @hide - */ - /*package*/ BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Returns the device associated with this service. - * - * @hide - */ - /*package*/ void setDevice(BluetoothDevice device) { - mDevice = device; - } - - /** - * Add an included service to this service. - * - * @param service The service to be added - * @return true, if the included service was added to the service - */ - @RequiresLegacyBluetoothPermission - public boolean addService(BluetoothGattService service) { - mIncludedServices.add(service); - return true; - } - - /** - * Add a characteristic to this service. - * - * @param characteristic The characteristics to be added - * @return true, if the characteristic was added to the service - */ - @RequiresLegacyBluetoothPermission - public boolean addCharacteristic(BluetoothGattCharacteristic characteristic) { - mCharacteristics.add(characteristic); - characteristic.setService(this); - return true; - } - - /** - * Get characteristic by UUID and instanceId. - * - * @hide - */ - /*package*/ BluetoothGattCharacteristic getCharacteristic(UUID uuid, int instanceId) { - for (BluetoothGattCharacteristic characteristic : mCharacteristics) { - if (uuid.equals(characteristic.getUuid()) - && characteristic.getInstanceId() == instanceId) { - return characteristic; - } - } - return null; - } - - /** - * Force the instance ID. - * - * @hide - */ - @UnsupportedAppUsage - public void setInstanceId(int instanceId) { - mInstanceId = instanceId; - } - - /** - * Get the handle count override (conformance testing. - * - * @hide - */ - /*package*/ int getHandles() { - return mHandles; - } - - /** - * Force the number of handles to reserve for this service. - * This is needed for conformance testing only. - * - * @hide - */ - public void setHandles(int handles) { - mHandles = handles; - } - - /** - * Add an included service to the internal map. - * - * @hide - */ - public void addIncludedService(BluetoothGattService includedService) { - mIncludedServices.add(includedService); - } - - /** - * Returns the UUID of this service - * - * @return UUID of this service - */ - public UUID getUuid() { - return mUuid; - } - - /** - * Returns the instance ID for this service - * - * <p>If a remote device offers multiple services with the same UUID - * (ex. multiple battery services for different batteries), the instance - * ID is used to distuinguish services. - * - * @return Instance ID of this service - */ - public int getInstanceId() { - return mInstanceId; - } - - /** - * Get the type of this service (primary/secondary) - */ - public int getType() { - return mServiceType; - } - - /** - * Get the list of included GATT services for this service. - * - * @return List of included services or empty list if no included services were discovered. - */ - public List<BluetoothGattService> getIncludedServices() { - return mIncludedServices; - } - - /** - * Returns a list of characteristics included in this service. - * - * @return Characteristics included in this service - */ - public List<BluetoothGattCharacteristic> getCharacteristics() { - return mCharacteristics; - } - - /** - * Returns a characteristic with a given UUID out of the list of - * characteristics offered by this service. - * - * <p>This is a convenience function to allow access to a given characteristic - * without enumerating over the list returned by {@link #getCharacteristics} - * manually. - * - * <p>If a remote service offers multiple characteristics with the same - * UUID, the first instance of a characteristic with the given UUID - * is returned. - * - * @return GATT characteristic object or null if no characteristic with the given UUID was - * found. - */ - public BluetoothGattCharacteristic getCharacteristic(UUID uuid) { - for (BluetoothGattCharacteristic characteristic : mCharacteristics) { - if (uuid.equals(characteristic.getUuid())) { - return characteristic; - } - } - return null; - } - - /** - * Returns whether the uuid of the service should be advertised. - * - * @hide - */ - public boolean isAdvertisePreferred() { - return mAdvertisePreferred; - } - - /** - * Set whether the service uuid should be advertised. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setAdvertisePreferred(boolean advertisePreferred) { - mAdvertisePreferred = advertisePreferred; - } -} diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java deleted file mode 100644 index f2a6276ce8fe..000000000000 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ /dev/null @@ -1,1478 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * Public API for controlling the Bluetooth Headset Service. This includes both - * Bluetooth Headset and Handsfree (v1.5) profiles. - * - * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset - * Service via IPC. - * - * <p> Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothHeadset proxy object. Use - * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. - * - * <p> Android only supports one connected Bluetooth Headset at a time. - * Each method is protected with its appropriate permission. - */ -public final class BluetoothHeadset implements BluetoothProfile { - private static final String TAG = "BluetoothHeadset"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the Headset - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the change in the Audio Connection state of the - * HFP profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_AUDIO_STATE_CHANGED = - "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; - - /** - * Intent used to broadcast the selection of a connected device as active. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage(trackingBug = 171933273) - public static final String ACTION_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED"; - - /** - * Intent used to broadcast that the headset has posted a - * vendor-specific event. - * - * <p>This intent will have 4 extras and 1 category. - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device - * </li> - * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor - * specific command </li> - * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT - * command type which can be one of {@link #AT_CMD_TYPE_READ}, - * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET}, - * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li> - * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command - * arguments. </li> - * </ul> - * - * <p> The category is the Company ID of the vendor defining the - * vendor-specific command. {@link BluetoothAssignedNumbers} - * - * For example, for Plantronics specific events - * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55 - * - * <p> For example, an AT+XEVENT=foo,3 will get translated into - * <ul> - * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li> - * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li> - * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li> - * </ul> - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = - "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; - - /** - * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} - * intents that contains the name of the vendor-specific command. - */ - public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = - "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; - - /** - * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} - * intents that contains the AT command type of the vendor-specific command. - */ - public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = - "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE"; - - /** - * AT command type READ used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, AT+VGM?. There are no arguments for this command type. - */ - public static final int AT_CMD_TYPE_READ = 0; - - /** - * AT command type TEST used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, AT+VGM=?. There are no arguments for this command type. - */ - public static final int AT_CMD_TYPE_TEST = 1; - - /** - * AT command type SET used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, AT+VGM=<args>. - */ - public static final int AT_CMD_TYPE_SET = 2; - - /** - * AT command type BASIC used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, ATD. Single character commands and everything following the - * character are arguments. - */ - public static final int AT_CMD_TYPE_BASIC = 3; - - /** - * AT command type ACTION used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, AT+CHUP. There are no arguments for action commands. - */ - public static final int AT_CMD_TYPE_ACTION = 4; - - /** - * A Parcelable String array extra field in - * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains - * the arguments to the vendor-specific command. - */ - public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = - "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; - - /** - * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} - * for the companyId - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = - "android.bluetooth.headset.intent.category.companyid"; - - /** - * A vendor-specific command for unsolicited result code. - */ - public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID"; - - /** - * A vendor-specific AT command - * - * @hide - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL"; - - /** - * A vendor-specific AT command - * - * @hide - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV"; - - /** - * Battery level indicator associated with - * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV} - * - * @hide - */ - public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1; - - /** - * A vendor-specific AT command - * - * @hide - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT"; - - /** - * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT} - * - * @hide - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY"; - - /** - * Headset state when SCO audio is not connected. - * This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_AUDIO_STATE_CHANGED} intent. - */ - public static final int STATE_AUDIO_DISCONNECTED = 10; - - /** - * Headset state when SCO audio is connecting. - * This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_AUDIO_STATE_CHANGED} intent. - */ - public static final int STATE_AUDIO_CONNECTING = 11; - - /** - * Headset state when SCO audio is connected. - * This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_AUDIO_STATE_CHANGED} intent. - */ - public static final int STATE_AUDIO_CONNECTED = 12; - - /** - * Intent used to broadcast the headset's indicator status - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which - * is supported by the headset ( as indicated by AT+BIND command in the SLC - * sequence) or whose value is changed (indicated by AT+BIEV command) </li> - * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li> - * </ul> - * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators - * are given an assigned number. Below shows the assigned number of Indicator added so far - * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled - * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_HF_INDICATORS_VALUE_CHANGED = - "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED"; - - /** - * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} - * intents that contains the assigned number of the headset indicator as defined by - * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7 - * - * @hide - */ - public static final String EXTRA_HF_INDICATORS_IND_ID = - "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID"; - - /** - * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} - * intents that contains the value of the Headset indicator that is being sent. - * - * @hide - */ - public static final String EXTRA_HF_INDICATORS_IND_VALUE = - "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE"; - - private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100; - private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101; - - private final CloseGuard mCloseGuard = new CloseGuard(); - - private Context mContext; - private ServiceListener mServiceListener; - private volatile IBluetoothHeadset mService; - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = - new IBluetoothStateChangeCallback.Stub() { - public void onBluetoothStateChange(boolean up) { - if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); - if (!up) { - doUnbind(); - } else { - doBind(); - } - } - }; - - /** - * Create a BluetoothHeadset proxy object. - */ - /* package */ BluetoothHeadset(Context context, ServiceListener l, BluetoothAdapter adapter) { - mContext = context; - mServiceListener = l; - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - - // Preserve legacy compatibility where apps were depending on - // registerStateChangeCallback() performing a permissions check which - // has been relaxed in modern platform versions - if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R - && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Need BLUETOOTH permission"); - } - - IBluetoothManager mgr = mAdapter.getBluetoothManager(); - if (mgr != null) { - try { - mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - doBind(); - mCloseGuard.open("close"); - } - - private boolean doBind() { - synchronized (mConnection) { - if (mService == null) { - if (VDBG) Log.d(TAG, "Binding service..."); - try { - return mAdapter.getBluetoothManager().bindBluetoothProfileService( - BluetoothProfile.HEADSET, mConnection); - } catch (RemoteException e) { - Log.e(TAG, "Unable to bind HeadsetService", e); - } - } - } - return false; - } - - private void doUnbind() { - synchronized (mConnection) { - if (mService != null) { - if (VDBG) Log.d(TAG, "Unbinding service..."); - try { - mAdapter.getBluetoothManager().unbindBluetoothProfileService( - BluetoothProfile.HEADSET, mConnection); - } catch (RemoteException e) { - Log.e(TAG, "Unable to unbind HeadsetService", e); - } finally { - mService = null; - } - } - } - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothHeadset will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - */ - @UnsupportedAppUsage - /*package*/ void close() { - if (VDBG) log("close()"); - - IBluetoothManager mgr = mAdapter.getBluetoothManager(); - if (mgr != null) { - try { - mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException re) { - Log.e(TAG, "", re); - } - } - mServiceListener = null; - doUnbind(); - mCloseGuard.close(); - } - - /** {@hide} */ - @Override - protected void finalize() throws Throwable { - mCloseGuard.warnIfOpen(); - close(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> Currently, the system supports only 1 connection to the - * headset/handsfree profile. The API will automatically disconnect connected - * devices before connecting. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connectWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnectWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothHeadset service = mService; - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevicesWithAttribution(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothHeadset service = mService; - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getConnectionState(" + device + ")"); - final IBluetoothHeadset service = mService; - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionStateWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, - * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothHeadset service = mService; - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Checks whether the headset supports some form of noise reduction - * - * @param device Bluetooth device - * @return true if echo cancellation and/or noise reduction is supported, false otherwise - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) { - if (DBG) log("isNoiseReductionSupported()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isNoiseReductionSupported(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Checks whether the headset supports voice recognition - * - * @param device Bluetooth device - * @return true if voice recognition is supported, false otherwise - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) { - if (DBG) log("isVoiceRecognitionSupported()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isVoiceRecognitionSupported(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Start Bluetooth voice recognition. This methods sends the voice - * recognition AT command to the headset and establishes the - * audio connection. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. - * - * <p> {@link #EXTRA_STATE} will transition from - * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when - * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} - * in case of failure to establish the audio connection. - * - * @param device Bluetooth headset - * @return false if there is no headset connected, or the connected headset doesn't support - * voice recognition, or voice recognition is already started, or audio channel is occupied, - * or on error, true otherwise - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean startVoiceRecognition(BluetoothDevice device) { - if (DBG) log("startVoiceRecognition()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.startVoiceRecognition(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Stop Bluetooth Voice Recognition mode, and shut down the - * Bluetooth audio path. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. - * - * @param device Bluetooth headset - * @return false if there is no headset connected, or voice recognition has not started, - * or voice recognition has ended on this headset, or on error, true otherwise - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean stopVoiceRecognition(BluetoothDevice device) { - if (DBG) log("stopVoiceRecognition()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.stopVoiceRecognition(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check if Bluetooth SCO audio is connected. - * - * @param device Bluetooth headset - * @return true if SCO is connected, false otherwise or on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isAudioConnected(BluetoothDevice device) { - if (VDBG) log("isAudioConnected()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isAudioConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Indicates if current platform supports voice dialing over bluetooth SCO. - * - * @return true if voice dialing over bluetooth is supported, false otherwise. - * @hide - */ - public static boolean isBluetoothVoiceDialingEnabled(Context context) { - return context.getResources().getBoolean( - com.android.internal.R.bool.config_bluetooth_sco_off_call); - } - - /** - * Get the current audio state of the Headset. - * Note: This is an internal function and shouldn't be exposed - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getAudioState(BluetoothDevice device) { - if (VDBG) log("getAudioState"); - final IBluetoothHeadset service = mService; - final int defaultValue = BluetoothHeadset.STATE_AUDIO_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (!isDisabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getAudioState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any - * audio to the HF unless explicitly told to. - * This method should be used in cases where the SCO channel is shared between multiple profiles - * and must be delegated by a source knowledgeable - * Note: This is an internal function and shouldn't be exposed - * - * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setAudioRouteAllowed(boolean allowed) { - if (VDBG) log("setAudioRouteAllowed"); - final IBluetoothHeadset service = mService; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setAudioRouteAllowed(allowed, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}. - * Note: This is an internal function and shouldn't be exposed - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getAudioRouteAllowed() { - if (VDBG) log("getAudioRouteAllowed"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getAudioRouteAllowed(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Force SCO audio to be opened regardless any other restrictions - * - * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio - * False to use SCO audio in normal manner - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setForceScoAudio(boolean forced) { - if (VDBG) log("setForceScoAudio " + String.valueOf(forced)); - final IBluetoothHeadset service = mService; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setForceScoAudio(forced, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Check if at least one headset's SCO audio is connected or connecting - * - * @return true if at least one device's SCO audio is connected or connecting, false otherwise - * or on error - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isAudioOn() { - if (VDBG) log("isAudioOn()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isAudioOn(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiates a connection of headset audio to the current active device - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. - * - * <p> {@link #EXTRA_STATE} will transition from - * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when - * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} - * in case of failure to establish the audio connection. - * - * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true - * before calling this method - * - * @return false if there was some error such as there is no active headset - * @hide - */ - @UnsupportedAppUsage - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connectAudio() { - if (VDBG) log("connectAudio()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connectAudio(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiates a disconnection of HFP SCO audio. - * Tear down voice recognition or virtual voice call if any. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. - * - * @return false if audio is not connected, or on error, true otherwise - * @hide - */ - @UnsupportedAppUsage - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnectAudio() { - if (VDBG) log("disconnectAudio()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnectAudio(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiates a SCO channel connection as a virtual voice call to the current active device - * Active handsfree device will be notified of incoming call and connected call. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. - * - * <p> {@link #EXTRA_STATE} will transition from - * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when - * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} - * in case of failure to establish the audio connection. - * - * @return true if successful, false if one of the following case applies - * - SCO audio is not idle (connecting or connected) - * - virtual call has already started - * - there is no active device - * - a Telecom managed call is going on - * - binder is dead or Bluetooth is disabled or other error - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean startScoUsingVirtualVoiceCall() { - if (DBG) log("startScoUsingVirtualVoiceCall()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.startScoUsingVirtualVoiceCall(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Terminates an ongoing SCO connection and the associated virtual call. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. - * - * @return true if successful, false if one of the following case applies - * - virtual voice call is not started or has ended - * - binder is dead or Bluetooth is disabled or other error - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean stopScoUsingVirtualVoiceCall() { - if (DBG) log("stopScoUsingVirtualVoiceCall()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.stopScoUsingVirtualVoiceCall(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Notify Headset of phone state change. - * This is a backdoor for phone app to call BluetoothHeadset since - * there is currently not a good way to get precise call state change outside - * of phone app. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public void phoneStateChanged(int numActive, int numHeld, int callState, String number, - int type, String name) { - final IBluetoothHeadset service = mService; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - service.phoneStateChanged(numActive, numHeld, callState, number, type, name, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Send Headset of CLCC response - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public void clccResponse(int index, int direction, int status, int mode, boolean mpty, - String number, int type) { - final IBluetoothHeadset service = mService; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.clccResponse(index, direction, status, mode, mpty, number, type, - mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Sends a vendor-specific unsolicited result code to the headset. - * - * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code - * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the - * string <code>"+ANDROID: 0"</code> will be sent. - * - * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. - * - * @param device Bluetooth headset. - * @param command A vendor-specific command. - * @param arg The argument that will be attached to the command. - * @return {@code false} if there is no headset connected, or if the command is not an allowed - * vendor-specific unsolicited result code, or on error. {@code true} otherwise. - * @throws IllegalArgumentException if {@code command} is {@code null}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, - String arg) { - if (DBG) { - log("sendVendorSpecificResultCode()"); - } - if (command == null) { - throw new IllegalArgumentException("command is null"); - } - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendVendorSpecificResultCode(device, command, arg, - mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Select a connected device as active. - * - * The active device selection is per profile. An active device's - * purpose is profile-specific. For example, in HFP and HSP profiles, - * it is the device used for phone call audio. If a remote device is not - * connected, it cannot be selected as active. - * - * <p> This API returns false in scenarios like the profile on the - * device is not connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that the - * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted - * with the active device. - * - * @param device Remote Bluetooth Device, could be null if phone call audio should not be - * streamed to a headset - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - @UnsupportedAppUsage(trackingBug = 171933273) - public boolean setActiveDevice(@Nullable BluetoothDevice device) { - if (DBG) { - Log.d(TAG, "setActiveDevice: " + device); - } - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && (device == null || isValidDevice(device))) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setActiveDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connected device that is active. - * - * @return the connected device that is active or null if no device - * is active. - * @hide - */ - @UnsupportedAppUsage(trackingBug = 171933273) - @Nullable - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothDevice getActiveDevice() { - if (VDBG) Log.d(TAG, "getActiveDevice"); - final IBluetoothHeadset service = mService; - final BluetoothDevice defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothDevice> recv = - new SynchronousResultReceiver(); - service.getActiveDevice(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an - * active connection. - * - * @return true if in-band ringing is enabled, false if in-band ringing is disabled - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean isInbandRingingEnabled() { - if (DBG) log("isInbandRingingEnabled()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isInbandRingingEnabled(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check if in-band ringing is supported for this platform. - * - * @return true if in-band ringing is supported, false if in-band ringing is not supported - * @hide - */ - public static boolean isInbandRingingSupported(Context context) { - return context.getResources().getBoolean( - com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothProfileServiceConnection mConnection = - new IBluetoothProfileServiceConnection.Stub() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - if (DBG) Log.d(TAG, "Proxy object connected"); - mService = IBluetoothHeadset.Stub.asInterface(service); - mHandler.sendMessage(mHandler.obtainMessage( - MESSAGE_HEADSET_SERVICE_CONNECTED)); - } - - @Override - public void onServiceDisconnected(ComponentName className) { - if (DBG) Log.d(TAG, "Proxy object disconnected"); - doUnbind(); - mHandler.sendMessage(mHandler.obtainMessage( - MESSAGE_HEADSET_SERVICE_DISCONNECTED)); - } - }; - - @UnsupportedAppUsage - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private boolean isDisabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_OFF; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_HEADSET_SERVICE_CONNECTED: { - if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, - BluetoothHeadset.this); - } - break; - } - case MESSAGE_HEADSET_SERVICE_DISCONNECTED: { - if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); - } - break; - } - } - } - }; -} diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java deleted file mode 100644 index 7d7a7f798bb9..000000000000 --- a/core/java/android/bluetooth/BluetoothHeadsetClient.java +++ /dev/null @@ -1,1356 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * Public API to control Hands Free Profile (HFP role only). - * <p> - * This class defines methods that shall be used by application to manage profile - * connection, calls states and calls actions. - * <p> - * - * @hide - */ -public final class BluetoothHeadsetClient implements BluetoothProfile { - private static final String TAG = "BluetoothHeadsetClient"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent sent whenever connection to remote changes. - * - * <p>It includes two extras: - * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code> - * and <code>BluetoothProfile.EXTRA_STATE</code>, which - * are mandatory. - * <p>There are also non mandatory feature extras: - * {@link #EXTRA_AG_FEATURE_3WAY_CALLING}, - * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}, - * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}, - * {@link #EXTRA_AG_FEATURE_REJECT_CALL}, - * {@link #EXTRA_AG_FEATURE_ECC}, - * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD}, - * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL}, - * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL}, - * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT}, - * {@link #EXTRA_AG_FEATURE_MERGE}, - * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}, - * sent as boolean values only when <code>EXTRA_STATE</code> - * is set to <code>STATE_CONNECTED</code>.</p> - * - * <p>Note that features supported by AG are being sent as - * booleans with value <code>true</code>, - * and not supported ones are <strong>not</strong> being sent at all.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent sent whenever audio state changes. - * - * <p>It includes two mandatory extras: - * {@link BluetoothProfile#EXTRA_STATE}, - * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}, - * with possible values: - * {@link #STATE_AUDIO_CONNECTING}, - * {@link #STATE_AUDIO_CONNECTED}, - * {@link #STATE_AUDIO_DISCONNECTED}</p> - * <p>When <code>EXTRA_STATE</code> is set - * to </code>STATE_AUDIO_CONNECTED</code>, - * it also includes {@link #EXTRA_AUDIO_WBS} - * indicating wide band speech support.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_AUDIO_STATE_CHANGED = - "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED"; - - /** - * Intent sending updates of the Audio Gateway state. - * Each extra is being sent only when value it - * represents has been changed recently on AG. - * <p>It can contain one or more of the following extras: - * {@link #EXTRA_NETWORK_STATUS}, - * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH}, - * {@link #EXTRA_NETWORK_ROAMING}, - * {@link #EXTRA_BATTERY_LEVEL}, - * {@link #EXTRA_OPERATOR_NAME}, - * {@link #EXTRA_VOICE_RECOGNITION}, - * {@link #EXTRA_IN_BAND_RING}</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_AG_EVENT = - "android.bluetooth.headsetclient.profile.action.AG_EVENT"; - - /** - * Intent sent whenever state of a call changes. - * - * <p>It includes: - * {@link #EXTRA_CALL}, - * with value of {@link BluetoothHeadsetClientCall} instance, - * representing actual call state.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CALL_CHANGED = - "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED"; - - /** - * Intent that notifies about the result of the last issued action. - * Please note that not every action results in explicit action result code being sent. - * Instead other notifications about new Audio Gateway state might be sent, - * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value - * when for example user started voice recognition from HF unit. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_RESULT = - "android.bluetooth.headsetclient.profile.action.RESULT"; - - /** - * Intent that notifies about vendor specific event arrival. Events not defined in - * HFP spec will be matched with supported vendor event list and this intent will - * be broadcasted upon a match. Supported vendor events are of format of - * of "+eventCode" or "+eventCode=xxxx" or "+eventCode:=xxxx". - * Vendor event can be a response to an vendor specific command or unsolicited. - * - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT = - "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT"; - - /** - * Intent that notifies about the number attached to the last voice tag - * recorded on AG. - * - * <p>It contains: - * {@link #EXTRA_NUMBER}, - * with a <code>String</code> value representing phone number.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LAST_VTAG = - "android.bluetooth.headsetclient.profile.action.LAST_VTAG"; - - public static final int STATE_AUDIO_DISCONNECTED = 0; - public static final int STATE_AUDIO_CONNECTING = 1; - public static final int STATE_AUDIO_CONNECTED = 2; - - /** - * Extra with information if connected audio is WBS. - * <p>Possible values: <code>true</code>, - * <code>false</code>.</p> - */ - public static final String EXTRA_AUDIO_WBS = - "android.bluetooth.headsetclient.extra.AUDIO_WBS"; - - /** - * Extra for AG_EVENT indicates network status. - * <p>Value: 0 - network unavailable, - * 1 - network available </p> - */ - public static final String EXTRA_NETWORK_STATUS = - "android.bluetooth.headsetclient.extra.NETWORK_STATUS"; - /** - * Extra for AG_EVENT intent indicates network signal strength. - * <p>Value: <code>Integer</code> representing signal strength.</p> - */ - public static final String EXTRA_NETWORK_SIGNAL_STRENGTH = - "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH"; - /** - * Extra for AG_EVENT intent indicates roaming state. - * <p>Value: 0 - no roaming - * 1 - active roaming</p> - */ - public static final String EXTRA_NETWORK_ROAMING = - "android.bluetooth.headsetclient.extra.NETWORK_ROAMING"; - /** - * Extra for AG_EVENT intent indicates the battery level. - * <p>Value: <code>Integer</code> representing signal strength.</p> - */ - public static final String EXTRA_BATTERY_LEVEL = - "android.bluetooth.headsetclient.extra.BATTERY_LEVEL"; - /** - * Extra for AG_EVENT intent indicates operator name. - * <p>Value: <code>String</code> representing operator name.</p> - */ - public static final String EXTRA_OPERATOR_NAME = - "android.bluetooth.headsetclient.extra.OPERATOR_NAME"; - /** - * Extra for AG_EVENT intent indicates voice recognition state. - * <p>Value: - * 0 - voice recognition stopped, - * 1 - voice recognition started.</p> - */ - public static final String EXTRA_VOICE_RECOGNITION = - "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION"; - /** - * Extra for AG_EVENT intent indicates in band ring state. - * <p>Value: - * 0 - in band ring tone not supported, or - * 1 - in band ring tone supported.</p> - */ - public static final String EXTRA_IN_BAND_RING = - "android.bluetooth.headsetclient.extra.IN_BAND_RING"; - - /** - * Extra for AG_EVENT intent indicates subscriber info. - * <p>Value: <code>String</code> containing subscriber information.</p> - */ - public static final String EXTRA_SUBSCRIBER_INFO = - "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO"; - - /** - * Extra for AG_CALL_CHANGED intent indicates the - * {@link BluetoothHeadsetClientCall} object that has changed. - */ - public static final String EXTRA_CALL = - "android.bluetooth.headsetclient.extra.CALL"; - - /** - * Extra for ACTION_LAST_VTAG intent. - * <p>Value: <code>String</code> representing phone number - * corresponding to last voice tag recorded on AG</p> - */ - public static final String EXTRA_NUMBER = - "android.bluetooth.headsetclient.extra.NUMBER"; - - /** - * Extra for ACTION_RESULT intent that shows the result code of - * last issued action. - * <p>Possible results: - * {@link #ACTION_RESULT_OK}, - * {@link #ACTION_RESULT_ERROR}, - * {@link #ACTION_RESULT_ERROR_NO_CARRIER}, - * {@link #ACTION_RESULT_ERROR_BUSY}, - * {@link #ACTION_RESULT_ERROR_NO_ANSWER}, - * {@link #ACTION_RESULT_ERROR_DELAYED}, - * {@link #ACTION_RESULT_ERROR_BLACKLISTED}, - * {@link #ACTION_RESULT_ERROR_CME}</p> - */ - public static final String EXTRA_RESULT_CODE = - "android.bluetooth.headsetclient.extra.RESULT_CODE"; - - /** - * Extra for ACTION_RESULT intent that shows the extended result code of - * last issued action. - * <p>Value: <code>Integer</code> - error code.</p> - */ - public static final String EXTRA_CME_CODE = - "android.bluetooth.headsetclient.extra.CME_CODE"; - - /** - * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that - * indicates vendor ID. - */ - public static final String EXTRA_VENDOR_ID = - "android.bluetooth.headsetclient.extra.VENDOR_ID"; - - /** - * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that - * indicates vendor event code. - */ - public static final String EXTRA_VENDOR_EVENT_CODE = - "android.bluetooth.headsetclient.extra.VENDOR_EVENT_CODE"; - - /** - * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that - * contains full vendor event including event code and full arguments. - */ - public static final String EXTRA_VENDOR_EVENT_FULL_ARGS = - "android.bluetooth.headsetclient.extra.VENDOR_EVENT_FULL_ARGS"; - - - /* Extras for AG_FEATURES, extras type is boolean */ - // TODO verify if all of those are actually useful - /** - * AG feature: three way calling. - */ - public static final String EXTRA_AG_FEATURE_3WAY_CALLING = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING"; - /** - * AG feature: voice recognition. - */ - public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION"; - /** - * AG feature: fetching phone number for voice tagging procedure. - */ - public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT"; - /** - * AG feature: ability to reject incoming call. - */ - public static final String EXTRA_AG_FEATURE_REJECT_CALL = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL"; - /** - * AG feature: enhanced call handling (terminate specific call, private consultation). - */ - public static final String EXTRA_AG_FEATURE_ECC = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC"; - /** - * AG feature: response and hold. - */ - public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD"; - /** - * AG call handling feature: accept held or waiting call in three way calling scenarios. - */ - public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL"; - /** - * AG call handling feature: release held or waiting call in three way calling scenarios. - */ - public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL"; - /** - * AG call handling feature: release active call and accept held or waiting call in three way - * calling scenarios. - */ - public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT"; - /** - * AG call handling feature: merge two calls, held and active - multi party conference mode. - */ - public static final String EXTRA_AG_FEATURE_MERGE = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE"; - /** - * AG call handling feature: merge calls and disconnect from multi party - * conversation leaving peers connected to each other. - * Note that this feature needs to be supported by mobile network operator - * as it requires connection and billing transfer. - */ - public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH"; - - /* Action result codes */ - public static final int ACTION_RESULT_OK = 0; - public static final int ACTION_RESULT_ERROR = 1; - public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2; - public static final int ACTION_RESULT_ERROR_BUSY = 3; - public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4; - public static final int ACTION_RESULT_ERROR_DELAYED = 5; - public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6; - public static final int ACTION_RESULT_ERROR_CME = 7; - - /* Detailed CME error codes */ - public static final int CME_PHONE_FAILURE = 0; - public static final int CME_NO_CONNECTION_TO_PHONE = 1; - public static final int CME_OPERATION_NOT_ALLOWED = 3; - public static final int CME_OPERATION_NOT_SUPPORTED = 4; - public static final int CME_PHSIM_PIN_REQUIRED = 5; - public static final int CME_PHFSIM_PIN_REQUIRED = 6; - public static final int CME_PHFSIM_PUK_REQUIRED = 7; - public static final int CME_SIM_NOT_INSERTED = 10; - public static final int CME_SIM_PIN_REQUIRED = 11; - public static final int CME_SIM_PUK_REQUIRED = 12; - public static final int CME_SIM_FAILURE = 13; - public static final int CME_SIM_BUSY = 14; - public static final int CME_SIM_WRONG = 15; - public static final int CME_INCORRECT_PASSWORD = 16; - public static final int CME_SIM_PIN2_REQUIRED = 17; - public static final int CME_SIM_PUK2_REQUIRED = 18; - public static final int CME_MEMORY_FULL = 20; - public static final int CME_INVALID_INDEX = 21; - public static final int CME_NOT_FOUND = 22; - public static final int CME_MEMORY_FAILURE = 23; - public static final int CME_TEXT_STRING_TOO_LONG = 24; - public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25; - public static final int CME_DIAL_STRING_TOO_LONG = 26; - public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27; - public static final int CME_NO_NETWORK_SERVICE = 30; - public static final int CME_NETWORK_TIMEOUT = 31; - public static final int CME_EMERGENCY_SERVICE_ONLY = 32; - public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33; - public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34; - public static final int CME_SIP_RESPONSE_CODE = 35; - public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40; - public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41; - public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42; - public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43; - public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44; - public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45; - public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46; - public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47; - public static final int CME_HIDDEN_KEY_REQUIRED = 48; - public static final int CME_EAP_NOT_SUPPORTED = 49; - public static final int CME_INCORRECT_PARAMETERS = 50; - - /* Action policy for other calls when accepting call */ - public static final int CALL_ACCEPT_NONE = 0; - public static final int CALL_ACCEPT_HOLD = 1; - public static final int CALL_ACCEPT_TERMINATE = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothHeadsetClient> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.HEADSET_CLIENT, - "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) { - @Override - public IBluetoothHeadsetClient getServiceInterface(IBinder service) { - return IBluetoothHeadsetClient.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothHeadsetClient proxy object. - */ - /* package */ BluetoothHeadsetClient(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothHeadsetClient will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - */ - /*package*/ void close() { - if (VDBG) log("close()"); - mProfileConnector.disconnect(); - } - - private IBluetoothHeadsetClient getService() { - return mProfileConnector.getService(); - } - - /** - * Connects to remote device. - * - * Currently, the system supports only 1 connection. So, in case of the - * second connection, this implementation will disconnect already connected - * device automatically and will process the new one. - * - * @param device a remote device we want connect to - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Disconnects remote device - * - * @param device a remote device we want disconnect - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Return the list of connected remote devices - * - * @return list of connected devices; empty list if nothing is connected. - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothHeadsetClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns list of remote devices in a particular state - * - * @param states collection of states - * @return list of devices that state matches the states listed in <code>states</code>; empty - * list if nothing matches the <code>states</code> - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothHeadsetClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns state of the <code>device</code> - * - * @param device a remote device - * @return the state of connection of the device - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getConnectionState(" + device + ")"); - final IBluetoothHeadsetClient service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothHeadsetClient service = getService(); - final @ConnectionPolicy int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Starts voice recognition. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature - * is not supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean startVoiceRecognition(BluetoothDevice device) { - if (DBG) log("startVoiceRecognition()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.startVoiceRecognition(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send vendor specific AT command. - * - * @param device remote device - * @param vendorId vendor number by Bluetooth SIG - * @param atCommand command to be sent. It start with + prefix and only one command at one time. - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) { - if (DBG) log("sendVendorSpecificCommand()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Stops voice recognition. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature - * is not supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean stopVoiceRecognition(BluetoothDevice device) { - if (DBG) log("stopVoiceRecognition()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.stopVoiceRecognition(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns list of all calls in any state. - * - * @param device remote device - * @return list of calls; empty list if none call exists - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { - if (DBG) log("getCurrentCalls()"); - final IBluetoothHeadsetClient service = getService(); - final List<BluetoothHeadsetClientCall> defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<List<BluetoothHeadsetClientCall>> recv = - new SynchronousResultReceiver(); - service.getCurrentCalls(device, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns list of current values of AG indicators. - * - * @param device remote device - * @return bundle of AG indicators; null if device is not in CONNECTED state - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public Bundle getCurrentAgEvents(BluetoothDevice device) { - if (DBG) log("getCurrentAgEvents()"); - final IBluetoothHeadsetClient service = getService(); - final Bundle defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver(); - service.getCurrentAgEvents(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Accepts a call - * - * @param device remote device - * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE}, - * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE} - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean acceptCall(BluetoothDevice device, int flag) { - if (DBG) log("acceptCall()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.acceptCall(device, flag, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Holds a call. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean holdCall(BluetoothDevice device) { - if (DBG) log("holdCall()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.holdCall(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Rejects a call. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not - * supported.</p> - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean rejectCall(BluetoothDevice device) { - if (DBG) log("rejectCall()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.rejectCall(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Terminates a specified call. - * - * Works only when Extended Call Control is supported by Audio Gateway. - * - * @param device remote device - * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via - * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active - * calls. - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not - * supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) { - if (DBG) log("terminateCall()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.terminateCall(device, call, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Enters private mode with a specified call. - * - * Works only when Extended Call Control is supported by Audio Gateway. - * - * @param device remote device - * @param index index of the call to connect in private mode - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not - * supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean enterPrivateMode(BluetoothDevice device, int index) { - if (DBG) log("enterPrivateMode()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.enterPrivateMode(device, index, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Performs explicit call transfer. - * - * That means connect other calls and disconnect. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature - * is not supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean explicitCallTransfer(BluetoothDevice device) { - if (DBG) log("explicitCallTransfer()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.explicitCallTransfer(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Places a call with specified number. - * - * @param device remote device - * @param number valid phone number - * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued - * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link - * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise; - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) { - if (DBG) log("dial()"); - final IBluetoothHeadsetClient service = getService(); - final BluetoothHeadsetClientCall defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<BluetoothHeadsetClientCall> recv = - new SynchronousResultReceiver(); - service.dial(device, number, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sends DTMF code. - * - * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,# - * - * @param device remote device - * @param code ASCII code - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent; - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendDTMF(BluetoothDevice device, byte code) { - if (DBG) log("sendDTMF()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendDTMF(device, code, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get a number corresponding to last voice tag recorded on AG. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT} - * intent; - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when - * feature is not supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getLastVoiceTagNumber(BluetoothDevice device) { - if (DBG) log("getLastVoiceTagNumber()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getLastVoiceTagNumber(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns current audio state of Audio Gateway. - * - * Note: This is an internal function and shouldn't be exposed - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getAudioState(BluetoothDevice device) { - if (VDBG) log("getAudioState"); - final IBluetoothHeadsetClient service = getService(); - final int defaultValue = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getAudioState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } else { - return defaultValue; - } - return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; - } - - /** - * Sets whether audio routing is allowed. - * - * @param device remote device - * @param allowed if routing is allowed to the device Note: This is an internal function and - * shouldn't be exposed - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { - if (VDBG) log("setAudioRouteAllowed"); - final IBluetoothHeadsetClient service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setAudioRouteAllowed(device, allowed, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Returns whether audio routing is allowed. - * - * @param device remote device - * @return whether the command succeeded Note: This is an internal function and shouldn't be - * exposed - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getAudioRouteAllowed(BluetoothDevice device) { - if (VDBG) log("getAudioRouteAllowed"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getAudioRouteAllowed(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiates a connection of audio channel. - * - * It setup SCO channel with remote connected Handsfree AG device. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connectAudio(BluetoothDevice device) { - if (VDBG) log("connectAudio"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connectAudio(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Disconnects audio channel. - * - * It tears down the SCO channel from remote AG device. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnectAudio(BluetoothDevice device) { - if (VDBG) log("disconnectAudio"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnectAudio(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get Audio Gateway features - * - * @param device remote device - * @return bundle of AG features; null if no service or AG not connected - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public Bundle getCurrentAgFeatures(BluetoothDevice device) { - if (VDBG) log("getCurrentAgFeatures"); - final IBluetoothHeadsetClient service = getService(); - final Bundle defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver(); - service.getCurrentAgFeatures(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java deleted file mode 100644 index e9dd761efdf6..000000000000 --- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; - -import java.util.UUID; - -/** - * This class represents a single call, its state and properties. - * It implements {@link Parcelable} for inter-process message passing. - * - * @hide - */ -public final class BluetoothHeadsetClientCall implements Parcelable, Attributable { - - /* Call state */ - /** - * Call is active. - */ - public static final int CALL_STATE_ACTIVE = 0; - /** - * Call is in held state. - */ - public static final int CALL_STATE_HELD = 1; - /** - * Outgoing call that is being dialed right now. - */ - public static final int CALL_STATE_DIALING = 2; - /** - * Outgoing call that remote party has already been alerted about. - */ - public static final int CALL_STATE_ALERTING = 3; - /** - * Incoming call that can be accepted or rejected. - */ - public static final int CALL_STATE_INCOMING = 4; - /** - * Waiting call state when there is already an active call. - */ - public static final int CALL_STATE_WAITING = 5; - /** - * Call that has been held by response and hold - * (see Bluetooth specification for further references). - */ - public static final int CALL_STATE_HELD_BY_RESPONSE_AND_HOLD = 6; - /** - * Call that has been already terminated and should not be referenced as a valid call. - */ - public static final int CALL_STATE_TERMINATED = 7; - - private final BluetoothDevice mDevice; - private final int mId; - private int mState; - private String mNumber; - private boolean mMultiParty; - private final boolean mOutgoing; - private final UUID mUUID; - private final long mCreationElapsedMilli; - private final boolean mInBandRing; - - /** - * Creates BluetoothHeadsetClientCall instance. - */ - public BluetoothHeadsetClientCall(BluetoothDevice device, int id, int state, String number, - boolean multiParty, boolean outgoing, boolean inBandRing) { - this(device, id, UUID.randomUUID(), state, number, multiParty, outgoing, inBandRing); - } - - public BluetoothHeadsetClientCall(BluetoothDevice device, int id, UUID uuid, int state, - String number, boolean multiParty, boolean outgoing, boolean inBandRing) { - mDevice = device; - mId = id; - mUUID = uuid; - mState = state; - mNumber = number != null ? number : ""; - mMultiParty = multiParty; - mOutgoing = outgoing; - mInBandRing = inBandRing; - mCreationElapsedMilli = SystemClock.elapsedRealtime(); - } - - /** {@hide} */ - public void setAttributionSource(@NonNull AttributionSource attributionSource) { - Attributable.setAttributionSource(mDevice, attributionSource); - } - - /** - * Sets call's state. - * - * <p>Note: This is an internal function and shouldn't be exposed</p> - * - * @param state new call state. - */ - public void setState(int state) { - mState = state; - } - - /** - * Sets call's number. - * - * <p>Note: This is an internal function and shouldn't be exposed</p> - * - * @param number String representing phone number. - */ - public void setNumber(String number) { - mNumber = number; - } - - /** - * Sets this call as multi party call. - * - * <p>Note: This is an internal function and shouldn't be exposed</p> - * - * @param multiParty if <code>true</code> sets this call as a part of multi party conference. - */ - public void setMultiParty(boolean multiParty) { - mMultiParty = multiParty; - } - - /** - * Gets call's device. - * - * @return call device. - */ - public BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Gets call's Id. - * - * @return call id. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public int getId() { - return mId; - } - - /** - * Gets call's UUID. - * - * @return call uuid - * @hide - */ - public UUID getUUID() { - return mUUID; - } - - /** - * Gets call's current state. - * - * @return state of this particular phone call. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public int getState() { - return mState; - } - - /** - * Gets call's number. - * - * @return string representing phone number. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public String getNumber() { - return mNumber; - } - - /** - * Gets call's creation time in millis since epoch. - * - * @return long representing the creation time. - */ - public long getCreationElapsedMilli() { - return mCreationElapsedMilli; - } - - /** - * Checks if call is an active call in a conference mode (aka multi party). - * - * @return <code>true</code> if call is a multi party call, <code>false</code> otherwise. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean isMultiParty() { - return mMultiParty; - } - - /** - * Checks if this call is an outgoing call. - * - * @return <code>true</code> if its outgoing call, <code>false</code> otherwise. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean isOutgoing() { - return mOutgoing; - } - - /** - * Checks if the ringtone will be generated by the connected phone - * - * @return <code>true</code> if in band ring is enabled, <code>false</code> otherwise. - */ - public boolean isInBandRing() { - return mInBandRing; - } - - - @Override - public String toString() { - return toString(false); - } - - /** - * Generate a log string for this call - * @param loggable whether device address should be logged - * @return log string - */ - public String toString(boolean loggable) { - StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mDevice: "); - builder.append(loggable ? mDevice : mDevice.hashCode()); - builder.append(", mId: "); - builder.append(mId); - builder.append(", mUUID: "); - builder.append(mUUID); - builder.append(", mState: "); - switch (mState) { - case CALL_STATE_ACTIVE: - builder.append("ACTIVE"); - break; - case CALL_STATE_HELD: - builder.append("HELD"); - break; - case CALL_STATE_DIALING: - builder.append("DIALING"); - break; - case CALL_STATE_ALERTING: - builder.append("ALERTING"); - break; - case CALL_STATE_INCOMING: - builder.append("INCOMING"); - break; - case CALL_STATE_WAITING: - builder.append("WAITING"); - break; - case CALL_STATE_HELD_BY_RESPONSE_AND_HOLD: - builder.append("HELD_BY_RESPONSE_AND_HOLD"); - break; - case CALL_STATE_TERMINATED: - builder.append("TERMINATED"); - break; - default: - builder.append(mState); - break; - } - builder.append(", mNumber: "); - builder.append(loggable ? mNumber : mNumber.hashCode()); - builder.append(", mMultiParty: "); - builder.append(mMultiParty); - builder.append(", mOutgoing: "); - builder.append(mOutgoing); - builder.append(", mInBandRing: "); - builder.append(mInBandRing); - builder.append("}"); - return builder.toString(); - } - - /** - * {@link Parcelable.Creator} interface implementation. - */ - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHeadsetClientCall> CREATOR = - new Parcelable.Creator<BluetoothHeadsetClientCall>() { - @Override - public BluetoothHeadsetClientCall createFromParcel(Parcel in) { - return new BluetoothHeadsetClientCall((BluetoothDevice) in.readParcelable(null, android.bluetooth.BluetoothDevice.class), - in.readInt(), UUID.fromString(in.readString()), in.readInt(), - in.readString(), in.readInt() == 1, in.readInt() == 1, - in.readInt() == 1); - } - - @Override - public BluetoothHeadsetClientCall[] newArray(int size) { - return new BluetoothHeadsetClientCall[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mDevice, 0); - out.writeInt(mId); - out.writeString(mUUID.toString()); - out.writeInt(mState); - out.writeString(mNumber); - out.writeInt(mMultiParty ? 1 : 0); - out.writeInt(mOutgoing ? 1 : 0); - out.writeInt(mInBandRing ? 1 : 0); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java deleted file mode 100644 index 65f68a943e08..000000000000 --- a/core/java/android/bluetooth/BluetoothHealth.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * Public API for Bluetooth Health Profile. - * - * <p>BluetoothHealth is a proxy object for controlling the Bluetooth - * Service via IPC. - * - * <p> How to connect to a health device which is acting in the source role. - * <li> Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothHealth proxy object. </li> - * <li> Create an {@link BluetoothHealth} callback and call - * {@link #registerSinkAppConfiguration} to register an application - * configuration </li> - * <li> Pair with the remote device. This currently needs to be done manually - * from Bluetooth Settings </li> - * <li> Connect to a health device using {@link #connectChannelToSource}. Some - * devices will connect the channel automatically. The {@link BluetoothHealth} - * callback will inform the application of channel state change. </li> - * <li> Use the file descriptor provided with a connected channel to read and - * write data to the health channel. </li> - * <li> The received data needs to be interpreted using a health manager which - * implements the IEEE 11073-xxxxx specifications. - * <li> When done, close the health channel by calling {@link #disconnectChannel} - * and unregister the application configuration calling - * {@link #unregisterAppConfiguration} - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New apps - * should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ -@Deprecated -public final class BluetoothHealth implements BluetoothProfile { - private static final String TAG = "BluetoothHealth"; - /** - * Health Profile Source Role - the health device. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int SOURCE_ROLE = 1 << 0; - - /** - * Health Profile Sink Role the device talking to the health device. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int SINK_ROLE = 1 << 1; - - /** - * Health Profile - Channel Type used - Reliable - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int CHANNEL_TYPE_RELIABLE = 10; - - /** - * Health Profile - Channel Type used - Streaming - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int CHANNEL_TYPE_STREAMING = 11; - - /** - * Hide auto-created default constructor - * @hide - */ - BluetoothHealth() {} - - /** - * Register an application configuration that acts as a Health SINK. - * This is the configuration that will be used to communicate with health devices - * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so - * the callback is used to notify success or failure if the function returns true. - * - * @param name The friendly name associated with the application or configuration. - * @param dataType The dataType of the Source role of Health Profile to which the sink wants to - * connect to. - * @param callback A callback to indicate success or failure of the registration and all - * operations done on this application configuration. - * @return If true, callback will be called. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean registerSinkAppConfiguration(String name, int dataType, - BluetoothHealthCallback callback) { - Log.e(TAG, "registerSinkAppConfiguration(): BluetoothHealth is deprecated"); - return false; - } - - /** - * Unregister an application configuration that has been registered using - * {@link #registerSinkAppConfiguration} - * - * @param config The health app configuration - * @return Success or failure. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { - Log.e(TAG, "unregisterAppConfiguration(): BluetoothHealth is deprecated"); - return false; - } - - /** - * Connect to a health device which has the {@link #SOURCE_ROLE}. - * This is an asynchronous call. If this function returns true, the callback - * associated with the application configuration will be called. - * - * @param device The remote Bluetooth device. - * @param config The application configuration which has been registered using {@link - * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } - * @return If true, the callback associated with the application config will be called. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean connectChannelToSource(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - Log.e(TAG, "connectChannelToSource(): BluetoothHealth is deprecated"); - return false; - } - - /** - * Disconnect a connected health channel. - * This is an asynchronous call. If this function returns true, the callback - * associated with the application configuration will be called. - * - * @param device The remote Bluetooth device. - * @param config The application configuration which has been registered using {@link - * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } - * @param channelId The channel id associated with the channel - * @return If true, the callback associated with the application config will be called. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean disconnectChannel(BluetoothDevice device, - BluetoothHealthAppConfiguration config, int channelId) { - Log.e(TAG, "disconnectChannel(): BluetoothHealth is deprecated"); - return false; - } - - /** - * Get the file descriptor of the main channel associated with the remote device - * and application configuration. - * - * <p> Its the responsibility of the caller to close the ParcelFileDescriptor - * when done. - * - * @param device The remote Bluetooth health device - * @param config The application configuration - * @return null on failure, ParcelFileDescriptor on success. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated"); - return null; - } - - /** - * Get the current connection state of the profile. - * - * This is not specific to any application configuration but represents the connection - * state of the local Bluetooth adapter with the remote device. This can be used - * by applications like status bar which would just like to know the state of the - * local adapter. - * - * @param device Remote bluetooth device. - * @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link - * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public int getConnectionState(BluetoothDevice device) { - Log.e(TAG, "getConnectionState(): BluetoothHealth is deprecated"); - return STATE_DISCONNECTED; - } - - /** - * Get connected devices for the health profile. - * - * <p> Return the set of devices which are in state {@link #STATE_CONNECTED} - * - * This is not specific to any application configuration but represents the connection - * state of the local Bluetooth adapter for this profile. This can be used - * by applications like status bar which would just like to know the state of the - * local adapter. - * - * @return List of devices. The list will be empty on error. - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public List<BluetoothDevice> getConnectedDevices() { - Log.e(TAG, "getConnectedDevices(): BluetoothHealth is deprecated"); - return new ArrayList<>(); - } - - /** - * Get a list of devices that match any of the given connection - * states. - * - * <p> If none of the devices match any of the given states, - * an empty list will be returned. - * - * <p>This is not specific to any application configuration but represents the connection - * state of the local Bluetooth adapter for this profile. This can be used - * by applications like status bar which would just like to know the state of the - * local adapter. - * - * @param states Array of states. States can be one of {@link #STATE_CONNECTED}, {@link - * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}, - * @return List of devices. The list will be empty on error. - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - Log.e(TAG, "getDevicesMatchingConnectionStates(): BluetoothHealth is deprecated"); - return new ArrayList<>(); - } - - /** Health Channel Connection State - Disconnected - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int STATE_CHANNEL_DISCONNECTED = 0; - /** Health Channel Connection State - Connecting - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int STATE_CHANNEL_CONNECTING = 1; - /** Health Channel Connection State - Connected - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int STATE_CHANNEL_CONNECTED = 2; - /** Health Channel Connection State - Disconnecting - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int STATE_CHANNEL_DISCONNECTING = 3; - - /** Health App Configuration registration success - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0; - /** Health App Configuration registration failure - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int APP_CONFIG_REGISTRATION_FAILURE = 1; - /** Health App Configuration un-registration success - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2; - /** Health App Configuration un-registration failure - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; -} diff --git a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java deleted file mode 100644 index 2f66df258b53..000000000000 --- a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * The Bluetooth Health Application Configuration that is used in conjunction with - * the {@link BluetoothHealth} class. This class represents an application configuration - * that the Bluetooth Health third party application will register to communicate with the - * remote Bluetooth health device. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ -@Deprecated -public final class BluetoothHealthAppConfiguration implements Parcelable { - - /** - * Hide auto-created default constructor - * @hide - */ - BluetoothHealthAppConfiguration() {} - - @Override - public int describeContents() { - return 0; - } - - /** - * Return the data type associated with this application configuration. - * - * @return dataType - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public int getDataType() { - return 0; - } - - /** - * Return the name of the application configuration. - * - * @return String name - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public String getName() { - return null; - } - - /** - * Return the role associated with this application configuration. - * - * @return One of {@link BluetoothHealth#SOURCE_ROLE} or {@link BluetoothHealth#SINK_ROLE} - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public int getRole() { - return 0; - } - - /** - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHealthAppConfiguration> CREATOR = - new Parcelable.Creator<BluetoothHealthAppConfiguration>() { - @Override - public BluetoothHealthAppConfiguration createFromParcel(Parcel in) { - return new BluetoothHealthAppConfiguration(); - } - - @Override - public BluetoothHealthAppConfiguration[] newArray(int size) { - return new BluetoothHealthAppConfiguration[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) {} -} diff --git a/core/java/android/bluetooth/BluetoothHealthCallback.java b/core/java/android/bluetooth/BluetoothHealthCallback.java deleted file mode 100644 index 4769212c5361..000000000000 --- a/core/java/android/bluetooth/BluetoothHealthCallback.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package android.bluetooth; - -import android.annotation.BinderThread; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -/** - * This abstract class is used to implement {@link BluetoothHealth} callbacks. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ -@Deprecated -public abstract class BluetoothHealthCallback { - private static final String TAG = "BluetoothHealthCallback"; - - /** - * Callback to inform change in registration state of the health - * application. - * <p> This callback is called on the binder thread (not on the UI thread) - * - * @param config Bluetooth Health app configuration - * @param status Success or failure of the registration or unregistration calls. Can be one of - * {@link BluetoothHealth#APP_CONFIG_REGISTRATION_SUCCESS} or {@link - * BluetoothHealth#APP_CONFIG_REGISTRATION_FAILURE} or - * {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_SUCCESS} - * or {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_FAILURE} - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @BinderThread - @Deprecated - public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config, - int status) { - Log.d(TAG, "onHealthAppConfigurationStatusChange: " + config + "Status: " + status); - } - - /** - * Callback to inform change in channel state. - * <p> Its the responsibility of the implementor of this callback to close the - * parcel file descriptor when done. This callback is called on the Binder - * thread (not the UI thread) - * - * @param config The Health app configutation - * @param device The Bluetooth Device - * @param prevState The previous state of the channel - * @param newState The new state of the channel. - * @param fd The Parcel File Descriptor when the channel state is connected. - * @param channelId The id associated with the channel. This id will be used in future calls - * like when disconnecting the channel. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @BinderThread - @Deprecated - public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config, - BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd, - int channelId) { - Log.d(TAG, "onHealthChannelStateChange: " + config + "Device: " + device - + "prevState:" + prevState + "newState:" + newState + "ParcelFd:" + fd - + "ChannelId:" + channelId); - } -} diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java deleted file mode 100644 index 339a75fe0fbe..000000000000 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Hearing Aid profile. - * - * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothHearingAid proxy object. - * - * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each - * method is protected with its appropriate permission. - */ -public final class BluetoothHearingAid implements BluetoothProfile { - private static final String TAG = "BluetoothHearingAid"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the Hearing Aid - * profile. Please note that in the binaural case, there will be two different LE devices for - * the left and right side and each device will have their own connection state changes.S - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the selection of a connected device as active. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED"; - - /** - * This device represents Left Hearing Aid. - * - * @hide - */ - public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; - - /** - * This device represents Right Hearing Aid. - * - * @hide - */ - public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT; - - /** - * This device is Monaural. - * - * @hide - */ - public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL; - - /** - * This device is Binaural (should receive only left or right audio). - * - * @hide - */ - public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL; - - /** - * Indicates the HiSyncID could not be read and is unavailable. - * - * @hide - */ - public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothHearingAid> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.HEARING_AID, - "BluetoothHearingAid", IBluetoothHearingAid.class.getName()) { - @Override - public IBluetoothHearingAid getServiceInterface(IBinder service) { - return IBluetoothHearingAid.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothHearingAid proxy object for interacting with the local - * Bluetooth Hearing Aid service. - */ - /* package */ BluetoothHearingAid(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /*package*/ void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothHearingAid getService() { - return mProfileConnector.getService(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothHearingAid service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( - @NonNull int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothHearingAid service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @BluetoothProfile.BtProfileState int getConnectionState( - @NonNull BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Select a connected device as active. - * - * The active device selection is per profile. An active device's - * purpose is profile-specific. For example, Hearing Aid audio - * streaming is to the active Hearing Aid device. If a remote device - * is not connected, it cannot be selected as active. - * - * <p> This API returns false in scenarios like the profile on the - * device is not connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that the - * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted - * with the active device. - * - * @param device the remote Bluetooth device. Could be null to clear - * the active device and stop streaming audio to a Bluetooth device. - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean setActiveDevice(@Nullable BluetoothDevice device) { - if (DBG) log("setActiveDevice(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && ((device == null) || isValidDevice(device))) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setActiveDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connected physical Hearing Aid devices that are active - * - * @return the list of active devices. The first element is the left active - * device; the second element is the right active device. If either or both side - * is not active, it will be null on that position. Returns empty list on error. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getActiveDevices() { - if (VDBG) log("getActiveDevices()"); - final IBluetoothHearingAid service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getActiveDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - verifyDeviceNotNull(device, "setConnectionPolicy"); - final IBluetoothHearingAid service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - verifyDeviceNotNull(device, "getConnectionPolicy"); - final IBluetoothHearingAid service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Helper for converting a state to a string. - * - * For debug use only - strings are not internationalized. - * - * @hide - */ - public static String stateToString(int state) { - switch (state) { - case STATE_DISCONNECTED: - return "disconnected"; - case STATE_CONNECTING: - return "connecting"; - case STATE_CONNECTED: - return "connected"; - case STATE_DISCONNECTING: - return "disconnecting"; - default: - return "<unknown state " + state + ">"; - } - } - - /** - * Tells remote device to set an absolute volume. - * - * @param volume Absolute volume to be set on remote - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setVolume(int volume) { - if (DBG) Log.d(TAG, "setVolume(" + volume + ")"); - final IBluetoothHearingAid service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setVolume(volume, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Get the HiSyncId (unique hearing aid device identifier) of the device. - * - * <a href=https://source.android.com/devices/bluetooth/asha#hisyncid>HiSyncId documentation - * can be found here</a> - * - * @param device Bluetooth device - * @return the HiSyncId of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public long getHiSyncId(@NonNull BluetoothDevice device) { - if (VDBG) log("getHiSyncId(" + device + ")"); - verifyDeviceNotNull(device, "getConnectionPolicy"); - final IBluetoothHearingAid service = getService(); - final long defaultValue = HI_SYNC_ID_INVALID; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Long> recv = new SynchronousResultReceiver(); - service.getHiSyncId(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the side of the device. - * - * @param device Bluetooth device. - * @return SIDE_LEFT or SIDE_RIGHT - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getDeviceSide(BluetoothDevice device) { - if (VDBG) log("getDeviceSide(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final int defaultValue = SIDE_LEFT; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getDeviceSide(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the mode of the device. - * - * @param device Bluetooth device - * @return MODE_MONAURAL or MODE_BINAURAL - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getDeviceMode(BluetoothDevice device) { - if (VDBG) log("getDeviceMode(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final int defaultValue = MODE_MONAURAL; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getDeviceMode(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; - } - - private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { - if (device == null) { - Log.e(TAG, methodName + ": device param is null"); - throw new IllegalArgumentException("Device cannot be null"); - } - } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java deleted file mode 100644 index 44a355b5f75c..000000000000 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ /dev/null @@ -1,848 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeoutException; - -/** - * Provides the public APIs to control the Bluetooth HID Device profile. - * - * <p>BluetoothHidDevice is a proxy object for controlling the Bluetooth HID Device Service via IPC. - * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHidDevice proxy object. - */ -public final class BluetoothHidDevice implements BluetoothProfile { - private static final String TAG = BluetoothHidDevice.class.getSimpleName(); - private static final boolean DBG = false; - - /** - * Intent used to broadcast the change in connection state of the Input Host profile. - * - * <p>This intent will have 3 extras: - * - * <ul> - * <li>{@link #EXTRA_STATE} - The current state of the profile. - * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. - * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link - * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link - * #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Constant representing unspecified HID device subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS1_NONE = (byte) 0x00; - /** - * Constant representing keyboard subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40; - /** - * Constant representing mouse subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS1_MOUSE = (byte) 0x80; - /** - * Constant representing combo keyboard and mouse subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS1_COMBO = (byte) 0xC0; - - /** - * Constant representing uncategorized HID device subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00; - /** - * Constant representing joystick subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01; - /** - * Constant representing gamepad subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02; - /** - * Constant representing remote control subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03; - /** - * Constant representing sensing device subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04; - /** - * Constant representing digitizer tablet subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05; - /** - * Constant representing card reader subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_CARD_READER = (byte) 0x06; - - /** - * Constant representing HID Input Report type. - * - * @see Callback#onGetReport(BluetoothDevice, byte, byte, int) - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - * @see Callback#onInterruptData(BluetoothDevice, byte, byte[]) - */ - public static final byte REPORT_TYPE_INPUT = (byte) 1; - /** - * Constant representing HID Output Report type. - * - * @see Callback#onGetReport(BluetoothDevice, byte, byte, int) - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - * @see Callback#onInterruptData(BluetoothDevice, byte, byte[]) - */ - public static final byte REPORT_TYPE_OUTPUT = (byte) 2; - /** - * Constant representing HID Feature Report type. - * - * @see Callback#onGetReport(BluetoothDevice, byte, byte, int) - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - * @see Callback#onInterruptData(BluetoothDevice, byte, byte[]) - */ - public static final byte REPORT_TYPE_FEATURE = (byte) 3; - - /** - * Constant representing success response for Set Report. - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_SUCCESS = (byte) 0; - /** - * Constant representing error response for Set Report due to "not ready". - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_NOT_READY = (byte) 1; - /** - * Constant representing error response for Set Report due to "invalid report ID". - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2; - /** - * Constant representing error response for Set Report due to "unsupported request". - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3; - /** - * Constant representing error response for Set Report due to "invalid parameter". - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4; - /** - * Constant representing error response for Set Report with unknown reason. - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_UNKNOWN = (byte) 14; - - /** - * Constant representing boot protocol mode used set by host. Default is always {@link - * #PROTOCOL_REPORT_MODE} unless notified otherwise. - * - * @see Callback#onSetProtocol(BluetoothDevice, byte) - */ - public static final byte PROTOCOL_BOOT_MODE = (byte) 0; - /** - * Constant representing report protocol mode used set by host. Default is always {@link - * #PROTOCOL_REPORT_MODE} unless notified otherwise. - * - * @see Callback#onSetProtocol(BluetoothDevice, byte) - */ - public static final byte PROTOCOL_REPORT_MODE = (byte) 1; - - /** - * The template class that applications use to call callback functions on events from the HID - * host. Callback functions are wrapped in this class and registered to the Android system - * during app registration. - */ - public abstract static class Callback { - - private static final String TAG = "BluetoothHidDevCallback"; - - /** - * Callback called when application registration state changes. Usually it's called due to - * either {@link BluetoothHidDevice#registerApp (String, String, String, byte, byte[], - * Executor, Callback)} or {@link BluetoothHidDevice#unregisterApp()} , but can be also - * unsolicited in case e.g. Bluetooth was turned off in which case application is - * unregistered automatically. - * - * @param pluggedDevice {@link BluetoothDevice} object which represents host that currently - * has Virtual Cable established with device. Only valid when application is registered, - * can be <code>null</code>. - * @param registered <code>true</code> if application is registered, <code>false</code> - * otherwise. - */ - public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) { - Log.d( - TAG, - "onAppStatusChanged: pluggedDevice=" - + pluggedDevice - + " registered=" - + registered); - } - - /** - * Callback called when connection state with remote host was changed. Application can - * assume than Virtual Cable is established when called with {@link - * BluetoothProfile#STATE_CONNECTED} <code>state</code>. - * - * @param device {@link BluetoothDevice} object representing host device which connection - * state was changed. - * @param state Connection state as defined in {@link BluetoothProfile}. - */ - public void onConnectionStateChanged(BluetoothDevice device, int state) { - Log.d(TAG, "onConnectionStateChanged: device=" + device + " state=" + state); - } - - /** - * Callback called when GET_REPORT is received from remote host. Should be replied by - * application using {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte, - * byte[])}. - * - * @param type Requested Report Type. - * @param id Requested Report Id, can be 0 if no Report Id are defined in descriptor. - * @param bufferSize Requested buffer size, application shall respond with at least given - * number of bytes. - */ - public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) { - Log.d( - TAG, - "onGetReport: device=" - + device - + " type=" - + type - + " id=" - + id - + " bufferSize=" - + bufferSize); - } - - /** - * Callback called when SET_REPORT is received from remote host. In case received data are - * invalid, application shall respond with {@link - * BluetoothHidDevice#reportError(BluetoothDevice, byte)}. - * - * @param type Report Type. - * @param id Report Id. - * @param data Report data. - */ - public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) { - Log.d(TAG, "onSetReport: device=" + device + " type=" + type + " id=" + id); - } - - /** - * Callback called when SET_PROTOCOL is received from remote host. Application shall use - * this information to send only reports valid for given protocol mode. By default, {@link - * BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed. - * - * @param protocol Protocol Mode. - */ - public void onSetProtocol(BluetoothDevice device, byte protocol) { - Log.d(TAG, "onSetProtocol: device=" + device + " protocol=" + protocol); - } - - /** - * Callback called when report data is received over interrupt channel. Report Type is - * assumed to be {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}. - * - * @param reportId Report Id. - * @param data Report data. - */ - public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) { - Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId); - } - - /** - * Callback called when Virtual Cable is removed. After this callback is received connection - * will be disconnected automatically. - */ - public void onVirtualCableUnplug(BluetoothDevice device) { - Log.d(TAG, "onVirtualCableUnplug: device=" + device); - } - } - - private static class CallbackWrapper extends IBluetoothHidDeviceCallback.Stub { - - private final Executor mExecutor; - private final Callback mCallback; - private final AttributionSource mAttributionSource; - - CallbackWrapper(Executor executor, Callback callback, AttributionSource attributionSource) { - mExecutor = executor; - mCallback = callback; - mAttributionSource = attributionSource; - } - - @Override - public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) { - Attributable.setAttributionSource(pluggedDevice, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onConnectionStateChanged(BluetoothDevice device, int state) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onSetProtocol(BluetoothDevice device, byte protocol) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onVirtualCableUnplug(BluetoothDevice device) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device)); - } finally { - restoreCallingIdentity(token); - } - } - } - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothHidDevice> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.HID_DEVICE, - "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) { - @Override - public IBluetoothHidDevice getServiceInterface(IBinder service) { - return IBluetoothHidDevice.Stub.asInterface(service); - } - }; - - BluetoothHidDevice(Context context, ServiceListener listener, BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothHidDevice getService() { - return mProfileConnector.getService(); - } - - /** {@inheritDoc} */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - final IBluetoothHidDevice service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** {@inheritDoc} */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - final IBluetoothHidDevice service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** {@inheritDoc} */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - final IBluetoothHidDevice service = getService(); - final int defaultValue = STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Registers application to be used for HID device. Connections to HID Device are only possible - * when application is registered. Only one application can be registered at one time. When an - * application is registered, the HID Host service will be disabled until it is unregistered. - * When no longer used, application should be unregistered using {@link #unregisterApp()}. The - * app will be automatically unregistered if it is not foreground. The registration status - * should be tracked by the application by handling callback from Callback#onAppStatusChanged. - * The app registration status is not related to the return value of this method. - * - * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. The HID - * Device SDP record is required. - * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. The - * Incoming QoS Settings is not required. Use null or default - * BluetoothHidDeviceAppQosSettings.Builder for default values. - * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. The - * Outgoing QoS Settings is not required. Use null or default - * BluetoothHidDeviceAppQosSettings.Builder for default values. - * @param executor {@link Executor} object on which callback will be executed. The Executor - * object is required. - * @param callback {@link Callback} object to which callback messages will be sent. The Callback - * object is required. - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean registerApp( - BluetoothHidDeviceAppSdpSettings sdp, - BluetoothHidDeviceAppQosSettings inQos, - BluetoothHidDeviceAppQosSettings outQos, - Executor executor, - Callback callback) { - boolean result = false; - - if (sdp == null) { - throw new IllegalArgumentException("sdp parameter cannot be null"); - } - - if (executor == null) { - throw new IllegalArgumentException("executor parameter cannot be null"); - } - - if (callback == null) { - throw new IllegalArgumentException("callback parameter cannot be null"); - } - - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = result; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource); - service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource, recv); - result = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Unregisters application. Active connection will be disconnected and no new connections will - * be allowed until registered again using {@link #registerApp - * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback)}. The registration status should be - * tracked by the application by handling callback from Callback#onAppStatusChanged. The app - * registration status is not related to the return value of this method. - * - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean unregisterApp() { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.unregisterApp(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sends report to remote host using interrupt channel. - * - * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in - * descriptor. - * @param data Report data, not including Report Id. - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendReport(BluetoothDevice device, int id, byte[] data) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendReport(device, id, data, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sends report to remote host as reply for GET_REPORT request from {@link - * Callback#onGetReport(BluetoothDevice, byte, byte, int)}. - * - * @param type Report Type, as in request. - * @param id Report Id, as in request. - * @param data Report data, not including Report Id. - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.replyReport(device, type, id, data, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sends error handshake message as reply for invalid SET_REPORT request from {@link - * Callback#onSetReport(BluetoothDevice, byte, byte, byte[])}. - * - * @param error Error to be sent for SET_REPORT via HANDSHAKE. - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean reportError(BluetoothDevice device, byte error) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.reportError(device, error, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Gets the application name of the current HidDeviceService user. - * - * @return the current user name, or empty string if cannot get the name - * {@hide} - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public String getUserAppName() { - final IBluetoothHidDevice service = getService(); - final String defaultValue = ""; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<String> recv = new SynchronousResultReceiver(); - service.getUserAppName(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiates connection to host which is currently paired with this device. If the application - * is not registered, #connect(BluetoothDevice) will fail. The connection state should be - * tracked by the application by handling callback from Callback#onConnectionStateChanged. The - * connection state is not related to the return value of this method. - * - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect(BluetoothDevice device) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Disconnects from currently connected host. The connection state should be tracked by the - * application by handling callback from Callback#onConnectionStateChanged. The connection state - * is not related to the return value of this method. - * - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} - * and disconnects Hid device if connectionPolicy is - * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}. - * - * <p> The device should already be paired. - * Connection policy can be one of: - * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, - * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, - * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy determines whether hid device should be connected or disconnected - * @return true if hid device is connected or disconnected, false otherwise - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; - } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - if (DBG) { - Log.d(TAG, msg); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java deleted file mode 100644 index b21ebe59d816..000000000000 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device application. - * - * <p>The BluetoothHidDevice framework will update the L2CAP QoS settings for the app during - * registration. - * - * <p>{@see BluetoothHidDevice} - */ -public final class BluetoothHidDeviceAppQosSettings implements Parcelable { - - private final int mServiceType; - private final int mTokenRate; - private final int mTokenBucketSize; - private final int mPeakBandwidth; - private final int mLatency; - private final int mDelayVariation; - - public static final int SERVICE_NO_TRAFFIC = 0x00; - public static final int SERVICE_BEST_EFFORT = 0x01; - public static final int SERVICE_GUARANTEED = 0x02; - - public static final int MAX = (int) 0xffffffff; - - /** - * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel. The QoS - * Settings is optional. Please refer to Bluetooth HID Specfication v1.1.1 Section 5.2 and - * Appendix D for parameters. - * - * @param serviceType L2CAP service type, default = SERVICE_BEST_EFFORT - * @param tokenRate L2CAP token rate, default = 0 - * @param tokenBucketSize L2CAP token bucket size, default = 0 - * @param peakBandwidth L2CAP peak bandwidth, default = 0 - * @param latency L2CAP latency, default = MAX - * @param delayVariation L2CAP delay variation, default = MAX - */ - public BluetoothHidDeviceAppQosSettings( - int serviceType, - int tokenRate, - int tokenBucketSize, - int peakBandwidth, - int latency, - int delayVariation) { - mServiceType = serviceType; - mTokenRate = tokenRate; - mTokenBucketSize = tokenBucketSize; - mPeakBandwidth = peakBandwidth; - mLatency = latency; - mDelayVariation = delayVariation; - } - - public int getServiceType() { - return mServiceType; - } - - public int getTokenRate() { - return mTokenRate; - } - - public int getTokenBucketSize() { - return mTokenBucketSize; - } - - public int getPeakBandwidth() { - return mPeakBandwidth; - } - - public int getLatency() { - return mLatency; - } - - public int getDelayVariation() { - return mDelayVariation; - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHidDeviceAppQosSettings> CREATOR = - new Parcelable.Creator<BluetoothHidDeviceAppQosSettings>() { - - @Override - public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) { - - return new BluetoothHidDeviceAppQosSettings( - in.readInt(), - in.readInt(), - in.readInt(), - in.readInt(), - in.readInt(), - in.readInt()); - } - - @Override - public BluetoothHidDeviceAppQosSettings[] newArray(int size) { - return new BluetoothHidDeviceAppQosSettings[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mServiceType); - out.writeInt(mTokenRate); - out.writeInt(mTokenBucketSize); - out.writeInt(mPeakBandwidth); - out.writeInt(mLatency); - out.writeInt(mDelayVariation); - } -} diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java deleted file mode 100644 index 4e1a2aaedcf0..000000000000 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; -import android.util.EventLog; - - -/** - * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth HID Device application. - * - * <p>The BluetoothHidDevice framework adds the SDP record during app registration, so that the - * Android device can be discovered as a Bluetooth HID Device. - * - * <p>{@see BluetoothHidDevice} - */ -public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { - - private static final int MAX_DESCRIPTOR_SIZE = 2048; - - private final String mName; - private final String mDescription; - private final String mProvider; - private final byte mSubclass; - private final byte[] mDescriptors; - - /** - * Create a BluetoothHidDeviceAppSdpSettings object for the Bluetooth SDP record. - * - * @param name Name of this Bluetooth HID device. Maximum length is 50 bytes. - * @param description Description for this Bluetooth HID device. Maximum length is 50 bytes. - * @param provider Provider of this Bluetooth HID device. Maximum length is 50 bytes. - * @param subclass Subclass of this Bluetooth HID device. See <a - * href="www.usb.org/developers/hidpage/HID1_11.pdf"> - * www.usb.org/developers/hidpage/HID1_11.pdf Section 4.2</a> - * @param descriptors Descriptors of this Bluetooth HID device. See <a - * href="www.usb.org/developers/hidpage/HID1_11.pdf"> - * www.usb.org/developers/hidpage/HID1_11.pdf Chapter 6</a> Maximum length is 2048 bytes. - */ - public BluetoothHidDeviceAppSdpSettings( - String name, String description, String provider, byte subclass, byte[] descriptors) { - mName = name; - mDescription = description; - mProvider = provider; - mSubclass = subclass; - - if (descriptors == null || descriptors.length > MAX_DESCRIPTOR_SIZE) { - EventLog.writeEvent(0x534e4554, "119819889", -1, ""); - throw new IllegalArgumentException("descriptors must be not null and shorter than " - + MAX_DESCRIPTOR_SIZE); - } - mDescriptors = descriptors.clone(); - } - - public String getName() { - return mName; - } - - public String getDescription() { - return mDescription; - } - - public String getProvider() { - return mProvider; - } - - public byte getSubclass() { - return mSubclass; - } - - public byte[] getDescriptors() { - return mDescriptors; - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHidDeviceAppSdpSettings> CREATOR = - new Parcelable.Creator<BluetoothHidDeviceAppSdpSettings>() { - - @Override - public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) { - - return new BluetoothHidDeviceAppSdpSettings( - in.readString(), - in.readString(), - in.readString(), - in.readByte(), - in.createByteArray()); - } - - @Override - public BluetoothHidDeviceAppSdpSettings[] newArray(int size) { - return new BluetoothHidDeviceAppSdpSettings[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mName); - out.writeString(mDescription); - out.writeString(mProvider); - out.writeByte(mSubclass); - out.writeByteArray(mDescriptors); - } -} diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java deleted file mode 100644 index ecbeddf2b853..000000000000 --- a/core/java/android/bluetooth/BluetoothHidHost.java +++ /dev/null @@ -1,831 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - - -/** - * This class provides the public APIs to control the Bluetooth Input - * Device Profile. - * - * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothHidHost proxy object. - * - * <p>Each method is protected with its appropriate permission. - * - * @hide - */ -@SystemApi -public final class BluetoothHidHost implements BluetoothProfile { - private static final String TAG = "BluetoothHidHost"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the Input - * Device profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @SuppressLint("ActionValue") - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PROTOCOL_MODE_CHANGED = - "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_HANDSHAKE = - "android.bluetooth.input.profile.action.HANDSHAKE"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_REPORT = - "android.bluetooth.input.profile.action.REPORT"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_VIRTUAL_UNPLUG_STATUS = - "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_IDLE_TIME_CHANGED = - "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED"; - - /** - * Return codes for the connect and disconnect Bluez / Dbus calls. - * - * @hide - */ - public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000; - - /** - * @hide - */ - public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001; - - /** - * @hide - */ - public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002; - - /** - * @hide - */ - public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003; - - /** - * @hide - */ - public static final int INPUT_OPERATION_SUCCESS = 5004; - - /** - * @hide - */ - public static final int PROTOCOL_REPORT_MODE = 0; - - /** - * @hide - */ - public static final int PROTOCOL_BOOT_MODE = 1; - - /** - * @hide - */ - public static final int PROTOCOL_UNSUPPORTED_MODE = 255; - - /* int reportType, int reportType, int bufferSize */ - /** - * @hide - */ - public static final byte REPORT_TYPE_INPUT = 1; - - /** - * @hide - */ - public static final byte REPORT_TYPE_OUTPUT = 2; - - /** - * @hide - */ - public static final byte REPORT_TYPE_FEATURE = 3; - - /** - * @hide - */ - public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0; - - /** - * @hide - */ - public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1; - - /** - * @hide - */ - public static final String EXTRA_PROTOCOL_MODE = - "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE"; - - /** - * @hide - */ - public static final String EXTRA_REPORT_TYPE = - "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE"; - - /** - * @hide - */ - public static final String EXTRA_REPORT_ID = - "android.bluetooth.BluetoothHidHost.extra.REPORT_ID"; - - /** - * @hide - */ - public static final String EXTRA_REPORT_BUFFER_SIZE = - "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE"; - - /** - * @hide - */ - public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT"; - - /** - * @hide - */ - public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS"; - - /** - * @hide - */ - public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = - "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS"; - - /** - * @hide - */ - public static final String EXTRA_IDLE_TIME = - "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME"; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothHidHost> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.HID_HOST, - "BluetoothHidHost", IBluetoothHidHost.class.getName()) { - @Override - public IBluetoothHidHost getServiceInterface(IBinder service) { - return IBluetoothHidHost.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothHidHost proxy object for interacting with the local - * Bluetooth Service which handles the InputDevice profile - */ - /* package */ BluetoothHidHost(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /*package*/ void close() { - if (VDBG) log("close()"); - mProfileConnector.disconnect(); - } - - private IBluetoothHidHost getService() { - return mProfileConnector.getService(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> The system supports connection to multiple input devices. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothHidHost service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothHidHost service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(@NonNull BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - if (device == null) { - throw new IllegalArgumentException("device must not be null"); - } - final IBluetoothHidHost service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - if (device == null) { - throw new IllegalArgumentException("device must not be null"); - } - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - if (device == null) { - throw new IllegalArgumentException("device must not be null"); - } - final IBluetoothHidHost service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - /** - * Initiate virtual unplug for a HID input device. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean virtualUnplug(BluetoothDevice device) { - if (DBG) log("virtualUnplug(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.virtualUnplug(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Get_Protocol_Mode command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getProtocolMode(BluetoothDevice device) { - if (VDBG) log("getProtocolMode(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getProtocolMode(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Set_Protocol_Mode command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { - if (DBG) log("setProtocolMode(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setProtocolMode(device, protocolMode, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Get_Report command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @param reportType Report type - * @param reportId Report ID - * @param bufferSize Report receiving buffer size - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, - int bufferSize) { - if (VDBG) { - log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId - + "bufferSize=" + bufferSize); - } - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getReport(device, reportType, reportId, bufferSize, mAttributionSource, - recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Set_Report command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @param reportType Report type - * @param report Report receiving buffer size - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setReport(BluetoothDevice device, byte reportType, String report) { - if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setReport(device, reportType, report, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Send_Data command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @param report Report to send - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendData(BluetoothDevice device, String report) { - if (DBG) log("sendData(" + device + "), report=" + report); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendData(device, report, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Get_Idle_Time command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getIdleTime(BluetoothDevice device) { - if (DBG) log("getIdletime(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getIdleTime(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Set_Idle_Time command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @param idleTime Idle time to be set on HID Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setIdleTime(BluetoothDevice device, byte idleTime) { - if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setIdleTime(device, idleTime, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothInputStream.java b/core/java/android/bluetooth/BluetoothInputStream.java deleted file mode 100644 index 95f9229f0446..000000000000 --- a/core/java/android/bluetooth/BluetoothInputStream.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.SuppressLint; - -import java.io.IOException; -import java.io.InputStream; - -/** - * BluetoothInputStream. - * - * Used to write to a Bluetooth socket. - * - * @hide - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -/*package*/ final class BluetoothInputStream extends InputStream { - private BluetoothSocket mSocket; - - /*package*/ BluetoothInputStream(BluetoothSocket s) { - mSocket = s; - } - - /** - * Return number of bytes available before this stream will block. - */ - public int available() throws IOException { - return mSocket.available(); - } - - public void close() throws IOException { - mSocket.close(); - } - - /** - * Reads a single byte from this stream and returns it as an integer in the - * range from 0 to 255. Returns -1 if the end of the stream has been - * reached. Blocks until one byte has been read, the end of the source - * stream is detected or an exception is thrown. - * - * @return the byte read or -1 if the end of stream has been reached. - * @throws IOException if the stream is closed or another IOException occurs. - * @since Android 1.5 - */ - public int read() throws IOException { - byte[] b = new byte[1]; - int ret = mSocket.read(b, 0, 1); - if (ret == 1) { - return (int) b[0] & 0xff; - } else { - return -1; - } - } - - /** - * Reads at most {@code length} bytes from this stream and stores them in - * the byte array {@code b} starting at {@code offset}. - * - * @param b the byte array in which to store the bytes read. - * @param offset the initial position in {@code buffer} to store the bytes read from this - * stream. - * @param length the maximum number of bytes to store in {@code b}. - * @return the number of bytes actually read or -1 if the end of the stream has been reached. - * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code - * offset + length} is greater than the length of {@code b}. - * @throws IOException if the stream is closed or another IOException occurs. - * @since Android 1.5 - */ - public int read(byte[] b, int offset, int length) throws IOException { - if (b == null) { - throw new NullPointerException("byte array is null"); - } - if ((offset | length) < 0 || length > b.length - offset) { - throw new ArrayIndexOutOfBoundsException("invalid offset or length"); - } - return mSocket.read(b, offset, length); - } -} diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java deleted file mode 100644 index 15db686b3be4..000000000000 --- a/core/java/android/bluetooth/BluetoothLeAudio.java +++ /dev/null @@ -1,829 +0,0 @@ -/* - * Copyright 2020 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the LeAudio profile. - * - * <p>BluetoothLeAudio is a proxy object for controlling the Bluetooth LE Audio - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothLeAudio proxy object. - * - * <p> Android only supports one set of connected Bluetooth LeAudio device at a time. Each - * method is protected with its appropriate permission. - */ -public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { - private static final String TAG = "BluetoothLeAudio"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - - private CloseGuard mCloseGuard; - - /** - * Intent used to broadcast the change in connection state of the LeAudio - * profile. Please note that in the binaural case, there will be two different LE devices for - * the left and right side and each device will have their own connection state changes. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = - "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the selection of a connected device as active. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED"; - - /** - * Intent used to broadcast group node status information. - * - * <p>This intent will have 3 extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_NODE_STATUS} - Group node status. </li> - * </ul> - * - * @hide - */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED = - "android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED"; - - - /** - * Intent used to broadcast group status information. - * - * <p>This intent will have 4 extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_STATUS} - Group status. </li> - * </ul> - * - * @hide - */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_GROUP_STATUS_CHANGED = - "android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED"; - - /** - * Intent used to broadcast group audio configuration changed information. - * - * <p>This intent will have 5 extra: - * <ul> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> - * <li> {@link #EXTRA_LE_AUDIO_DIRECTION} - Direction as bit mask. </li> - * <li> {@link #EXTRA_LE_AUDIO_SINK_LOCATION} - Sink location as per Bluetooth Assigned - * Numbers </li> - * <li> {@link #EXTRA_LE_AUDIO_SOURCE_LOCATION} - Source location as per Bluetooth Assigned - * Numbers </li> - * <li> {@link #EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS} - Available contexts for group as per - * Bluetooth Assigned Numbers </li> - * </ul> - * - * @hide - */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_CONF_CHANGED = - "android.bluetooth.action.LE_AUDIO_CONF_CHANGED"; - - /** - * Indicates unspecified audio content. - * @hide - */ - public static final int CONTEXT_TYPE_UNSPECIFIED = 0x0001; - - /** - * Indicates conversation between humans as, for example, in telephony or video calls. - * @hide - */ - public static final int CONTEXT_TYPE_COMMUNICATION = 0x0002; - - /** - * Indicates media as, for example, in music, public radio, podcast or video soundtrack. - * @hide - */ - public static final int CONTEXT_TYPE_MEDIA = 0x0004; - - /** - * Indicates instructional audio as, for example, in navigation, traffic announcements - * or user guidance. - * @hide - */ - public static final int CONTEXT_TYPE_INSTRUCTIONAL = 0x0008; - - /** - * Indicates attention seeking audio as, for example, in beeps signalling arrival of a message - * or keyboard clicks. - * @hide - */ - public static final int CONTEXT_TYPE_ATTENTION_SEEKING = 0x0010; - - /** - * Indicates immediate alerts as, for example, in a low battery alarm, timer expiry or alarm - * clock. - * @hide - */ - public static final int CONTEXT_TYPE_IMMEDIATE_ALERT = 0x0020; - - /** - * Indicates man machine communication as, for example, with voice recognition or virtual - * assistant. - * @hide - */ - public static final int CONTEXT_TYPE_MAN_MACHINE = 0x0040; - - /** - * Indicates emergency alerts as, for example, with fire alarms or other urgent alerts. - * @hide - */ - public static final int CONTEXT_TYPE_EMERGENCY_ALERT = 0x0080; - - /** - * Indicates ringtone as in a call alert. - * @hide - */ - public static final int CONTEXT_TYPE_RINGTONE = 0x0100; - - /** - * Indicates audio associated with a television program and/or with metadata conforming to the - * Bluetooth Broadcast TV profile. - * @hide - */ - public static final int CONTEXT_TYPE_TV = 0x0200; - - /** - * Indicates audio associated with a low latency live audio stream. - * - * @hide - */ - public static final int CONTEXT_TYPE_LIVE = 0x0400; - - /** - * Indicates audio associated with a video game stream. - * @hide - */ - public static final int CONTEXT_TYPE_GAME = 0x0800; - - /** - * This represents an invalid group ID. - * - * @hide - */ - public static final int GROUP_ID_INVALID = IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; - - /** - * Contains group id. - * @hide - */ - public static final String EXTRA_LE_AUDIO_GROUP_ID = - "android.bluetooth.extra.LE_AUDIO_GROUP_ID"; - - /** - * Contains group node status, can be any of - * <p> - * <ul> - * <li> {@link #GROUP_NODE_ADDED} </li> - * <li> {@link #GROUP_NODE_REMOVED} </li> - * </ul> - * <p> - * @hide - */ - public static final String EXTRA_LE_AUDIO_GROUP_NODE_STATUS = - "android.bluetooth.extra.LE_AUDIO_GROUP_NODE_STATUS"; - - /** - * Contains group status, can be any of - * - * <p> - * <ul> - * <li> {@link #GROUP_STATUS_ACTIVE} </li> - * <li> {@link #GROUP_STATUS_INACTIVE} </li> - * </ul> - * <p> - * @hide - */ - public static final String EXTRA_LE_AUDIO_GROUP_STATUS = - "android.bluetooth.extra.LE_AUDIO_GROUP_STATUS"; - - /** - * Contains bit mask for direction, bit 0 set when Sink, bit 1 set when Source. - * @hide - */ - public static final String EXTRA_LE_AUDIO_DIRECTION = - "android.bluetooth.extra.LE_AUDIO_DIRECTION"; - - /** - * Contains source location as per Bluetooth Assigned Numbers - * @hide - */ - public static final String EXTRA_LE_AUDIO_SOURCE_LOCATION = - "android.bluetooth.extra.LE_AUDIO_SOURCE_LOCATION"; - - /** - * Contains sink location as per Bluetooth Assigned Numbers - * @hide - */ - public static final String EXTRA_LE_AUDIO_SINK_LOCATION = - "android.bluetooth.extra.LE_AUDIO_SINK_LOCATION"; - - /** - * Contains available context types for group as per Bluetooth Assigned Numbers - * @hide - */ - public static final String EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS = - "android.bluetooth.extra.LE_AUDIO_AVAILABLE_CONTEXTS"; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - /** - * Indicating that group is Active ( Audio device is available ) - * @hide - */ - public static final int GROUP_STATUS_ACTIVE = IBluetoothLeAudio.GROUP_STATUS_ACTIVE; - - /** - * Indicating that group is Inactive ( Audio device is not available ) - * @hide - */ - public static final int GROUP_STATUS_INACTIVE = IBluetoothLeAudio.GROUP_STATUS_INACTIVE; - - /** - * Indicating that node has been added to the group. - * @hide - */ - public static final int GROUP_NODE_ADDED = IBluetoothLeAudio.GROUP_NODE_ADDED; - - /** - * Indicating that node has been removed from the group. - * @hide - */ - public static final int GROUP_NODE_REMOVED = IBluetoothLeAudio.GROUP_NODE_REMOVED; - - private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio", - IBluetoothLeAudio.class.getName()) { - @Override - public IBluetoothLeAudio getServiceInterface(IBinder service) { - return IBluetoothLeAudio.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothLeAudio proxy object for interacting with the local - * Bluetooth LeAudio service. - */ - /* package */ BluetoothLeAudio(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - mCloseGuard = new CloseGuard(); - mCloseGuard.open("close"); - } - - /** - * @hide - */ - public void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothLeAudio getService() { - return mProfileConnector.getService(); - } - - protected void finalize() { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - close(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect(@Nullable BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(@Nullable BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothLeAudio service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( - @NonNull int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothLeAudio service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Select a connected device as active. - * - * The active device selection is per profile. An active device's - * purpose is profile-specific. For example, LeAudio audio - * streaming is to the active LeAudio device. If a remote device - * is not connected, it cannot be selected as active. - * - * <p> This API returns false in scenarios like the profile on the - * device is not connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that the - * {@link #ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED} intent will be broadcasted - * with the active device. - * - * - * @param device the remote Bluetooth device. Could be null to clear - * the active device and stop streaming audio to a Bluetooth device. - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setActiveDevice(@Nullable BluetoothDevice device) { - if (DBG) log("setActiveDevice(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && ((device == null) || isValidDevice(device))) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setActiveDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connected LeAudio devices that are active - * - * @return the list of active devices. Returns empty list on error. - * @hide - */ - @NonNull - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getActiveDevices() { - if (VDBG) log("getActiveDevice()"); - final IBluetoothLeAudio service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getActiveDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get device group id. Devices with same group id belong to same group (i.e left and right - * earbud) - * @param device LE Audio capable device - * @return group id that this device currently belongs to - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getGroupId(@NonNull BluetoothDevice device) { - if (VDBG) log("getGroupId()"); - final IBluetoothLeAudio service = getService(); - final int defaultValue = GROUP_ID_INVALID; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getGroupId(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set volume for the streaming devices - * - * @param volume volume to set - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) - public void setVolume(int volume) { - if (VDBG) log("setVolume(vol: " + volume + " )"); - final IBluetoothLeAudio service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setVolume(volume, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Add device to the given group. - * @param group_id group ID the device is being added to - * @param device the active device - * @return true on success, otherwise false - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED - }) - public boolean groupAddNode(int group_id, @NonNull BluetoothDevice device) { - if (VDBG) log("groupAddNode()"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.groupAddNode(group_id, device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Remove device from a given group. - * @param group_id group ID the device is being removed from - * @param device the active device - * @return true on success, otherwise false - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED - }) - public boolean groupRemoveNode(int group_id, @NonNull BluetoothDevice device) { - if (VDBG) log("groupRemoveNode()"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.groupRemoveNode(group_id, device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - - /** - * Helper for converting a state to a string. - * - * For debug use only - strings are not internationalized. - * - * @hide - */ - public static String stateToString(int state) { - switch (state) { - case STATE_DISCONNECTED: - return "disconnected"; - case STATE_CONNECTING: - return "connecting"; - case STATE_CONNECTED: - return "connected"; - case STATE_DISCONNECTING: - return "disconnecting"; - default: - return "<unknown state " + state + ">"; - } - } - - private boolean isValidDevice(@Nullable BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java deleted file mode 100644 index dcaf4b682f44..000000000000 --- a/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Represents the codec configuration for a Bluetooth LE Audio source device. - * <p>Contains the source codec type. - * <p>The source codec type values are the same as those supported by the - * device hardware. - * - * {@see BluetoothLeAudioCodecConfig} - */ -public final class BluetoothLeAudioCodecConfig { - // Add an entry for each source codec here. - - /** @hide */ - @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = { - SOURCE_CODEC_TYPE_LC3, - SOURCE_CODEC_TYPE_INVALID - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SourceCodecType {}; - - public static final int SOURCE_CODEC_TYPE_LC3 = 0; - public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; - - /** - * Represents the count of valid source codec types. Can be accessed via - * {@link #getMaxCodecType}. - */ - private static final int SOURCE_CODEC_TYPE_MAX = 1; - - private final @SourceCodecType int mCodecType; - - /** - * Creates a new BluetoothLeAudioCodecConfig. - * - * @param codecType the source codec type - */ - private BluetoothLeAudioCodecConfig(@SourceCodecType int codecType) { - mCodecType = codecType; - } - - @Override - public String toString() { - return "{codecName:" + getCodecName() + "}"; - } - - /** - * Gets the codec type. - * - * @return the codec type - */ - public @SourceCodecType int getCodecType() { - return mCodecType; - } - - /** - * Returns the valid codec types count. - */ - public static int getMaxCodecType() { - return SOURCE_CODEC_TYPE_MAX; - } - - /** - * Gets the codec name. - * - * @return the codec name - */ - public @NonNull String getCodecName() { - switch (mCodecType) { - case SOURCE_CODEC_TYPE_LC3: - return "LC3"; - case SOURCE_CODEC_TYPE_INVALID: - return "INVALID CODEC"; - default: - break; - } - return "UNKNOWN CODEC(" + mCodecType + ")"; - } - - /** - * Builder for {@link BluetoothLeAudioCodecConfig}. - * <p> By default, the codec type will be set to - * {@link BluetoothLeAudioCodecConfig#SOURCE_CODEC_TYPE_INVALID} - */ - public static final class Builder { - private int mCodecType = BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID; - - /** - * Set codec type for Bluetooth codec config. - * - * @param codecType of this codec - * @return the same Builder instance - */ - public @NonNull Builder setCodecType(@SourceCodecType int codecType) { - mCodecType = codecType; - return this; - } - - /** - * Build {@link BluetoothLeAudioCodecConfig}. - * @return new BluetoothLeAudioCodecConfig built - */ - public @NonNull BluetoothLeAudioCodecConfig build() { - return new BluetoothLeAudioCodecConfig(mCodecType); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothLeBroadcast.java b/core/java/android/bluetooth/BluetoothLeBroadcast.java deleted file mode 100644 index fed9f911d5b3..000000000000 --- a/core/java/android/bluetooth/BluetoothLeBroadcast.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.content.Context; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -/** - * This class provides the public APIs to control the Bluetooth LE Broadcast Source profile. - * - * <p>BluetoothLeBroadcast is a proxy object for controlling the Bluetooth LE Broadcast - * Source Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} - * to get the BluetoothLeBroadcast proxy object. - * - * @hide - */ -public final class BluetoothLeBroadcast implements BluetoothProfile { - private static final String TAG = "BluetoothLeBroadcast"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Constants used by the LE Audio Broadcast profile for the Broadcast state - * - * @hide - */ - @IntDef(prefix = {"LE_AUDIO_BROADCAST_STATE_"}, value = { - LE_AUDIO_BROADCAST_STATE_DISABLED, - LE_AUDIO_BROADCAST_STATE_ENABLING, - LE_AUDIO_BROADCAST_STATE_ENABLED, - LE_AUDIO_BROADCAST_STATE_DISABLING, - LE_AUDIO_BROADCAST_STATE_PLAYING, - LE_AUDIO_BROADCAST_STATE_NOT_PLAYING - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastState {} - - /** - * Indicates that LE Audio Broadcast mode is currently disabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_DISABLED = 10; - - /** - * Indicates that LE Audio Broadcast mode is being enabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_ENABLING = 11; - - /** - * Indicates that LE Audio Broadcast mode is currently enabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_ENABLED = 12; - /** - * Indicates that LE Audio Broadcast mode is being disabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_DISABLING = 13; - - /** - * Indicates that an LE Audio Broadcast mode is currently playing - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_PLAYING = 14; - - /** - * Indicates that LE Audio Broadcast is currently not playing - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_NOT_PLAYING = 15; - - /** - * Constants used by the LE Audio Broadcast profile for encryption key length - * - * @hide - */ - @IntDef(prefix = {"LE_AUDIO_BROADCAST_ENCRYPTION_KEY_"}, value = { - LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT, - LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioEncryptionKeyLength {} - - /** - * Indicates that the LE Audio Broadcast encryption key size is 32 bits. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT = 16; - - /** - * Indicates that the LE Audio Broadcast encryption key size is 128 bits. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT = 17; - - /** - * Interface for receiving events related to broadcasts - */ - public interface Callback { - /** - * Called when broadcast state has changed - * - * @param prevState broadcast state before the change - * @param newState broadcast state after the change - */ - @LeAudioBroadcastState - void onBroadcastStateChange(int prevState, int newState); - /** - * Called when encryption key has been updated - * - * @param success true if the key was updated successfully, false otherwise - */ - void onEncryptionKeySet(boolean success); - } - - /** - * Create a BluetoothLeBroadcast proxy object for interacting with the local - * LE Audio Broadcast Source service. - * - * @hide - */ - /*package*/ BluetoothLeBroadcast(Context context, - BluetoothProfile.ServiceListener listener) { - } - - /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException - * - * @hide - */ - @Override - public int getConnectionState(BluetoothDevice device) { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); - } - - /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException - * - * @hide - */ - @Override - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); - } - - /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException - * - * @hide - */ - @Override - public List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); - } - - /** - * Enable LE Audio Broadcast mode. - * - * Generates a new broadcast ID and enables sending of encrypted or unencrypted - * isochronous PDUs - * - * @hide - */ - public int enableBroadcastMode() { - if (DBG) log("enableBroadcastMode"); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED; - } - - /** - * Disable LE Audio Broadcast mode. - * - * @hide - */ - public int disableBroadcastMode() { - if (DBG) log("disableBroadcastMode"); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED; - } - - /** - * Get the current LE Audio broadcast state - * - * @hide - */ - @LeAudioBroadcastState - public int getBroadcastState() { - if (DBG) log("getBroadcastState"); - return LE_AUDIO_BROADCAST_STATE_DISABLED; - } - - /** - * Enable LE Audio broadcast encryption - * - * @param keyLength if useExisting is true, this specifies the length of the key that should - * be generated - * @param useExisting true, if an existing key should be used - * false, if a new key should be generated - * - * @hide - */ - @LeAudioEncryptionKeyLength - public int enableEncryption(boolean useExisting, int keyLength) { - if (DBG) log("enableEncryption useExisting=" + useExisting + " keyLength=" + keyLength); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED; - } - - /** - * Disable LE Audio broadcast encryption - * - * @param removeExisting true, if the existing key should be removed - * false, otherwise - * - * @hide - */ - public int disableEncryption(boolean removeExisting) { - if (DBG) log("disableEncryption removeExisting=" + removeExisting); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED; - } - - /** - * Enable or disable LE Audio broadcast encryption - * - * @param key use the provided key if non-null, generate a new key if null - * @param keyLength 0 if encryption is disabled, 4 bytes (low security), - * 16 bytes (high security) - * - * @hide - */ - @LeAudioEncryptionKeyLength - public int setEncryptionKey(byte[] key, int keyLength) { - if (DBG) log("setEncryptionKey key=" + key + " keyLength=" + keyLength); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED; - } - - - /** - * Get the encryption key that was set before - * - * @return encryption key as a byte array or null if no encryption key was set - * - * @hide - */ - public byte[] getEncryptionKey() { - if (DBG) log("getEncryptionKey"); - return null; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java b/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java deleted file mode 100644 index b866cce22470..000000000000 --- a/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.bluetooth.le.ScanResult; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * This class provides a set of callbacks that are invoked when scanning for Broadcast Sources is - * offloaded to a Broadcast Assistant. - * - * <p>An LE Audio Broadcast Assistant can help a Broadcast Sink to scan for available Broadcast - * Sources. The Broadcast Sink achieves this by offloading the scan to a Broadcast Assistant. This - * is facilitated by the Broadcast Audio Scan Service (BASS). A BASS server is a GATT server that is - * part of the Scan Delegator on a Broadcast Sink. A BASS client instead runs on the Broadcast - * Assistant. - * - * <p>Once a GATT connection is established between the BASS client and the BASS server, the - * Broadcast Sink can offload the scans to the Broadcast Assistant. Upon finding new Broadcast - * Sources, the Broadcast Assistant then notifies the Broadcast Sink about these over the - * established GATT connection. The Scan Delegator on the Broadcast Sink can also notify the - * Assistant about changes such as addition and removal of Broadcast Sources. - * - * @hide - */ -public abstract class BluetoothLeBroadcastAssistantCallback { - - /** - * Broadcast Audio Scan Service (BASS) codes returned by a BASS Server - * - * @hide - */ - @IntDef( - prefix = "BASS_STATUS_", - value = { - BASS_STATUS_SUCCESS, - BASS_STATUS_FAILURE, - BASS_STATUS_INVALID_GATT_HANDLE, - BASS_STATUS_TXN_TIMEOUT, - BASS_STATUS_INVALID_SOURCE_ID, - BASS_STATUS_COLOCATED_SRC_UNAVAILABLE, - BASS_STATUS_INVALID_SOURCE_SELECTED, - BASS_STATUS_SOURCE_UNAVAILABLE, - BASS_STATUS_DUPLICATE_ADDITION, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BassStatus {} - - public static final int BASS_STATUS_SUCCESS = 0x00; - public static final int BASS_STATUS_FAILURE = 0x01; - public static final int BASS_STATUS_INVALID_GATT_HANDLE = 0x02; - public static final int BASS_STATUS_TXN_TIMEOUT = 0x03; - - public static final int BASS_STATUS_INVALID_SOURCE_ID = 0x04; - public static final int BASS_STATUS_COLOCATED_SRC_UNAVAILABLE = 0x05; - public static final int BASS_STATUS_INVALID_SOURCE_SELECTED = 0x06; - public static final int BASS_STATUS_SOURCE_UNAVAILABLE = 0x07; - public static final int BASS_STATUS_DUPLICATE_ADDITION = 0x08; - public static final int BASS_STATUS_NO_EMPTY_SLOT = 0x09; - public static final int BASS_STATUS_INVALID_GROUP_OP = 0x10; - - /** - * Callback invoked when a new LE Audio Broadcast Source is found. - * - * @param result {@link ScanResult} scan result representing a Broadcast Source - */ - public void onBluetoothLeBroadcastSourceFound(@NonNull ScanResult result) {} - - /** - * Callback invoked when the Broadcast Assistant synchronizes with Periodic Advertisements (PAs) - * of an LE Audio Broadcast Source. - * - * @param source the selected Broadcast Source - */ - public void onBluetoothLeBroadcastSourceSelected( - @NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {} - - /** - * Callback invoked when the Broadcast Assistant loses synchronization with an LE Audio - * Broadcast Source. - * - * @param source the Broadcast Source with which synchronization was lost - */ - public void onBluetoothLeBroadcastSourceLost( - @NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {} - - /** - * Callback invoked when a new LE Audio Broadcast Source has been successfully added to the Scan - * Delegator (within a Broadcast Sink, for example). - * - * @param sink Scan Delegator device on which a new Broadcast Source has been added - * @param source the added Broadcast Source - */ - public void onBluetoothLeBroadcastSourceAdded( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} - - /** - * Callback invoked when an existing LE Audio Broadcast Source within a remote Scan Delegator - * has been updated. - * - * @param sink Scan Delegator device on which a Broadcast Source has been updated - * @param source the updated Broadcast Source - */ - public void onBluetoothLeBroadcastSourceUpdated( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} - - /** - * Callback invoked when an LE Audio Broadcast Source has been successfully removed from the - * Scan Delegator (within a Broadcast Sink, for example). - * - * @param sink Scan Delegator device from which a Broadcast Source has been removed - * @param source the removed Broadcast Source - */ - public void onBluetoothLeBroadcastSourceRemoved( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} -} diff --git a/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java b/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java deleted file mode 100644 index cb47280acc7e..000000000000 --- a/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This class represents an LE Audio Broadcast Source and the associated information that is needed - * by Broadcast Audio Scan Service (BASS) residing on a Scan Delegator. - * - * <p>For example, the Scan Delegator on an LE Audio Broadcast Sink can use the information - * contained within an instance of this class to synchronize with an LE Audio Broadcast Source in - * order to listen to a Broadcast Audio Stream. - * - * <p>BroadcastAssistant has a BASS client which facilitates scanning and discovery of Broadcast - * Sources on behalf of say a Broadcast Sink. Upon successful discovery of one or more Broadcast - * sources, this information needs to be communicated to the BASS Server residing within the Scan - * Delegator on a Broadcast Sink. This is achieved using the Periodic Advertising Synchronization - * Transfer (PAST) procedure. This procedure uses information contained within an instance of this - * class. - * - * @hide - */ -public final class BluetoothLeBroadcastSourceInfo implements Parcelable { - private static final String TAG = "BluetoothLeBroadcastSourceInfo"; - private static final boolean DBG = true; - - /** - * Constants representing Broadcast Source address types - * - * @hide - */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_", - value = { - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC, - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM, - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSourceAddressType {} - - /** - * Represents a public address used by an LE Audio Broadcast Source - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC = 0; - - /** - * Represents a random address used by an LE Audio Broadcast Source - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM = 1; - - /** - * Represents an invalid address used by an LE Audio Broadcast Seurce - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID = 0xFFFF; - - /** - * Periodic Advertising Synchronization state - * - * <p>Periodic Advertising (PA) enables the LE Audio Broadcast Assistant to discover broadcast - * audio streams as well as the audio stream configuration on behalf of an LE Audio Broadcast - * Sink. This information can then be transferred to the LE Audio Broadcast Sink using the - * Periodic Advertising Synchronizaton Transfer (PAST) procedure. - * - * @hide - */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkPaSyncState {} - - /** - * Indicates that the Broadcast Sink is not synchronized with the Periodic Advertisements (PA) - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE = 0; - - /** - * Indicates that the Broadcast Sink requested the Broadcast Assistant to synchronize with the - * Periodic Advertisements (PA). - * - * <p>This is also known as scan delegation or scan offloading. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ = 1; - - /** - * Indicates that the Broadcast Sink is synchronized with the Periodic Advertisements (PA). - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC = 2; - - /** - * Indicates that the Broadcast Sink was unable to synchronize with the Periodic Advertisements - * (PA). - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL = 3; - - /** - * Indicates that the Broadcast Sink should be synchronized with the Periodic Advertisements - * (PA) using the Periodic Advertisements Synchronization Transfert (PAST) procedure. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST = 4; - - /** - * Indicates that the Broadcast Sink synchornization state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID = 0xFFFF; - - /** @hide */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, - LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkAudioSyncState {} - - /** - * Indicates that the Broadcast Sink is not synchronized with a Broadcast Audio Stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED = 0; - - /** - * Indicates that the Broadcast Sink is synchronized with a Broadcast Audio Stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED = 1; - - /** - * Indicates that the Broadcast Sink audio synchronization state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID = 0xFFFF; - - /** @hide */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_ENC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkEncryptionState {} - - /** - * Indicates that the Broadcast Sink is synchronized with an unencrypted audio stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED = 0; - - /** - * Indicates that the Broadcast Sink needs a Broadcast Code to synchronize with the audio - * stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED = 1; - - /** - * Indicates that the Broadcast Sink is synchronized with an encrypted audio stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING = 2; - - /** - * Indicates that the Broadcast Sink is unable to decrypt an audio stream due to an incorrect - * Broadcast Code - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE = 3; - - /** - * Indicates that the Broadcast Sink encryption state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID = 0xFF; - - /** - * Represents an invalid LE Audio Broadcast Source ID - * - * @hide - */ - public static final byte LE_AUDIO_BROADCAST_SINK_INVALID_SOURCE_ID = (byte) 0x00; - - /** - * Represents an invalid Broadcast ID of a Broadcast Source - * - * @hide - */ - public static final int INVALID_BROADCAST_ID = 0xFFFFFF; - - private byte mSourceId; - private @LeAudioBroadcastSourceAddressType int mSourceAddressType; - private BluetoothDevice mSourceDevice; - private byte mSourceAdvSid; - private int mBroadcastId; - private @LeAudioBroadcastSinkPaSyncState int mPaSyncState; - private @LeAudioBroadcastSinkEncryptionState int mEncryptionStatus; - private @LeAudioBroadcastSinkAudioSyncState int mAudioSyncState; - private byte[] mBadBroadcastCode; - private byte mNumSubGroups; - private Map<Integer, Integer> mSubgroupBisSyncState = new HashMap<Integer, Integer>(); - private Map<Integer, byte[]> mSubgroupMetadata = new HashMap<Integer, byte[]>(); - - private String mBroadcastCode; - private static final int BIS_NO_PREF = 0xFFFFFFFF; - private static final int BROADCAST_CODE_SIZE = 16; - - /** - * Constructor to create an Empty object of {@link BluetoothLeBroadcastSourceInfo } with the - * given Source Id. - * - * <p>This is mainly used to represent the Empty Broadcast Source entries - * - * @param sourceId Source Id for this Broadcast Source info object - * @hide - */ - public BluetoothLeBroadcastSourceInfo(byte sourceId) { - mSourceId = sourceId; - mSourceAddressType = LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID; - mSourceDevice = null; - mSourceAdvSid = (byte) 0x00; - mBroadcastId = INVALID_BROADCAST_ID; - mPaSyncState = LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID; - mAudioSyncState = LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID; - mEncryptionStatus = LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID; - mBadBroadcastCode = null; - mNumSubGroups = 0; - mBroadcastCode = null; - } - - /*package*/ BluetoothLeBroadcastSourceInfo( - byte sourceId, - @LeAudioBroadcastSourceAddressType int addressType, - @NonNull BluetoothDevice device, - byte advSid, - int broadcastId, - @LeAudioBroadcastSinkPaSyncState int paSyncstate, - @LeAudioBroadcastSinkEncryptionState int encryptionStatus, - @LeAudioBroadcastSinkAudioSyncState int audioSyncstate, - @Nullable byte[] badCode, - byte numSubGroups, - @NonNull Map<Integer, Integer> bisSyncState, - @Nullable Map<Integer, byte[]> subgroupMetadata, - @NonNull String broadcastCode) { - mSourceId = sourceId; - mSourceAddressType = addressType; - mSourceDevice = device; - mSourceAdvSid = advSid; - mBroadcastId = broadcastId; - mPaSyncState = paSyncstate; - mEncryptionStatus = encryptionStatus; - mAudioSyncState = audioSyncstate; - - if (badCode != null && badCode.length != 0) { - mBadBroadcastCode = new byte[badCode.length]; - System.arraycopy(badCode, 0, mBadBroadcastCode, 0, badCode.length); - } - mNumSubGroups = numSubGroups; - mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); - mSubgroupMetadata = new HashMap<Integer, byte[]>(subgroupMetadata); - mBroadcastCode = broadcastCode; - } - - @Override - public boolean equals(Object o) { - if (o instanceof BluetoothLeBroadcastSourceInfo) { - BluetoothLeBroadcastSourceInfo other = (BluetoothLeBroadcastSourceInfo) o; - return (other.mSourceId == mSourceId - && other.mSourceAddressType == mSourceAddressType - && other.mSourceDevice == mSourceDevice - && other.mSourceAdvSid == mSourceAdvSid - && other.mBroadcastId == mBroadcastId - && other.mPaSyncState == mPaSyncState - && other.mEncryptionStatus == mEncryptionStatus - && other.mAudioSyncState == mAudioSyncState - && Arrays.equals(other.mBadBroadcastCode, mBadBroadcastCode) - && other.mNumSubGroups == mNumSubGroups - && mSubgroupBisSyncState.equals(other.mSubgroupBisSyncState) - && mSubgroupMetadata.equals(other.mSubgroupMetadata) - && other.mBroadcastCode == mBroadcastCode); - } - return false; - } - - /** - * Checks if an instance of {@link BluetoothLeBroadcastSourceInfo} is empty. - * - * @hide - */ - public boolean isEmpty() { - boolean ret = false; - if (mSourceAddressType == LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID - && mSourceDevice == null - && mSourceAdvSid == (byte) 0 - && mPaSyncState == LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID - && mEncryptionStatus == LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID - && mAudioSyncState == LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID - && mBadBroadcastCode == null - && mNumSubGroups == 0 - && mSubgroupBisSyncState.size() == 0 - && mSubgroupMetadata.size() == 0 - && mBroadcastCode == null) { - ret = true; - } - return ret; - } - - /** - * Compares an instance of {@link BluetoothLeBroadcastSourceInfo} with the provided instance. - * - * @hide - */ - public boolean matches(BluetoothLeBroadcastSourceInfo srcInfo) { - boolean ret = false; - if (srcInfo == null) { - ret = false; - } else { - if (mSourceDevice == null) { - if (mSourceAdvSid == srcInfo.getAdvertisingSid() - && mSourceAddressType == srcInfo.getAdvAddressType()) { - ret = true; - } - } else { - if (mSourceDevice.equals(srcInfo.getSourceDevice()) - && mSourceAdvSid == srcInfo.getAdvertisingSid() - && mSourceAddressType == srcInfo.getAdvAddressType() - && mBroadcastId == srcInfo.getBroadcastId()) { - ret = true; - } - } - } - return ret; - } - - @Override - public int hashCode() { - return Objects.hash( - mSourceId, - mSourceAddressType, - mSourceDevice, - mSourceAdvSid, - mBroadcastId, - mPaSyncState, - mEncryptionStatus, - mAudioSyncState, - mBadBroadcastCode, - mNumSubGroups, - mSubgroupBisSyncState, - mSubgroupMetadata, - mBroadcastCode); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public String toString() { - return "{BluetoothLeBroadcastSourceInfo : mSourceId" - + mSourceId - + " addressType: " - + mSourceAddressType - + " sourceDevice: " - + mSourceDevice - + " mSourceAdvSid:" - + mSourceAdvSid - + " mBroadcastId:" - + mBroadcastId - + " mPaSyncState:" - + mPaSyncState - + " mEncryptionStatus:" - + mEncryptionStatus - + " mAudioSyncState:" - + mAudioSyncState - + " mBadBroadcastCode:" - + mBadBroadcastCode - + " mNumSubGroups:" - + mNumSubGroups - + " mSubgroupBisSyncState:" - + mSubgroupBisSyncState - + " mSubgroupMetadata:" - + mSubgroupMetadata - + " mBroadcastCode:" - + mBroadcastCode - + "}"; - } - - /** - * Get the Source Id - * - * @return byte representing the Source Id, {@link - * #LE_AUDIO_BROADCAST_ASSISTANT_INVALID_SOURCE_ID} if invalid - * @hide - */ - public byte getSourceId() { - return mSourceId; - } - - /** - * Set the Source Id - * - * @param sourceId source Id - * @hide - */ - public void setSourceId(byte sourceId) { - mSourceId = sourceId; - } - - /** - * Set the Broadcast Source device - * - * @param sourceDevice the Broadcast Source BluetoothDevice - * @hide - */ - public void setSourceDevice(@NonNull BluetoothDevice sourceDevice) { - mSourceDevice = sourceDevice; - } - - /** - * Get the Broadcast Source BluetoothDevice - * - * @return Broadcast Source BluetoothDevice - * @hide - */ - public @NonNull BluetoothDevice getSourceDevice() { - return mSourceDevice; - } - - /** - * Set the address type of the Broadcast Source advertisements - * - * @hide - */ - public void setAdvAddressType(@LeAudioBroadcastSourceAddressType int addressType) { - mSourceAddressType = addressType; - } - - /** - * Get the address type used by advertisements from the Broadcast Source. - * BluetoothLeBroadcastSourceInfo Object - * - * @hide - */ - @LeAudioBroadcastSourceAddressType - public int getAdvAddressType() { - return mSourceAddressType; - } - - /** - * Set the advertising SID of the Broadcast Source advertisement. - * - * @param advSid advertising SID of the Broadcast Source - * @hide - */ - public void setAdvertisingSid(byte advSid) { - mSourceAdvSid = advSid; - } - - /** - * Get the advertising SID of the Broadcast Source advertisement. - * - * @return advertising SID of the Broadcast Source - * @hide - */ - public byte getAdvertisingSid() { - return mSourceAdvSid; - } - - /** - * Get the Broadcast ID of the Broadcast Source. - * - * @return broadcast ID - * @hide - */ - public int getBroadcastId() { - return mBroadcastId; - } - - /** - * Set the Periodic Advertising (PA) Sync State. - * - * @hide - */ - /*package*/ void setPaSyncState(@LeAudioBroadcastSinkPaSyncState int paSyncState) { - mPaSyncState = paSyncState; - } - - /** - * Get the Periodic Advertising (PA) Sync State - * - * @hide - */ - public @LeAudioBroadcastSinkPaSyncState int getMetadataSyncState() { - return mPaSyncState; - } - - /** - * Set the audio sync state - * - * @hide - */ - /*package*/ void setAudioSyncState(@LeAudioBroadcastSinkAudioSyncState int audioSyncState) { - mAudioSyncState = audioSyncState; - } - - /** - * Get the audio sync state - * - * @hide - */ - public @LeAudioBroadcastSinkAudioSyncState int getAudioSyncState() { - return mAudioSyncState; - } - - /** - * Set the encryption status - * - * @hide - */ - /*package*/ void setEncryptionStatus( - @LeAudioBroadcastSinkEncryptionState int encryptionStatus) { - mEncryptionStatus = encryptionStatus; - } - - /** - * Get the encryption status - * - * @hide - */ - public @LeAudioBroadcastSinkEncryptionState int getEncryptionStatus() { - return mEncryptionStatus; - } - - /** - * Get the incorrect broadcast code that the Scan delegator used to decrypt the Broadcast Audio - * Stream and failed. - * - * <p>This code is valid only if {@link #getEncryptionStatus} returns {@link - * #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} - * - * @return byte array containing bad broadcast value, null if the current encryption status is - * not {@link #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} - * @hide - */ - public @Nullable byte[] getBadBroadcastCode() { - return mBadBroadcastCode; - } - - /** - * Get the number of subgroups. - * - * @return number of subgroups - * @hide - */ - public byte getNumberOfSubGroups() { - return mNumSubGroups; - } - - public @NonNull Map<Integer, Integer> getSubgroupBisSyncState() { - return mSubgroupBisSyncState; - } - - public void setSubgroupBisSyncState(@NonNull Map<Integer, Integer> bisSyncState) { - mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); - } - - /*package*/ void setBroadcastCode(@NonNull String broadcastCode) { - mBroadcastCode = broadcastCode; - } - - /** - * Get the broadcast code - * - * @return - * @hide - */ - public @NonNull String getBroadcastCode() { - return mBroadcastCode; - } - - /** - * Set the broadcast ID - * - * @param broadcastId broadcast ID of the Broadcast Source - * @hide - */ - public void setBroadcastId(int broadcastId) { - mBroadcastId = broadcastId; - } - - private void writeSubgroupBisSyncStateToParcel( - @NonNull Parcel dest, @NonNull Map<Integer, Integer> subgroupBisSyncState) { - dest.writeInt(subgroupBisSyncState.size()); - for (Map.Entry<Integer, Integer> entry : subgroupBisSyncState.entrySet()) { - dest.writeInt(entry.getKey()); - dest.writeInt(entry.getValue()); - } - } - - private static void readSubgroupBisSyncStateFromParcel( - @NonNull Parcel in, @NonNull Map<Integer, Integer> subgroupBisSyncState) { - int size = in.readInt(); - - for (int i = 0; i < size; i++) { - Integer key = in.readInt(); - Integer value = in.readInt(); - subgroupBisSyncState.put(key, value); - } - } - - private void writeSubgroupMetadataToParcel( - @NonNull Parcel dest, @Nullable Map<Integer, byte[]> subgroupMetadata) { - if (subgroupMetadata == null) { - dest.writeInt(0); - return; - } - - dest.writeInt(subgroupMetadata.size()); - for (Map.Entry<Integer, byte[]> entry : subgroupMetadata.entrySet()) { - dest.writeInt(entry.getKey()); - byte[] metadata = entry.getValue(); - if (metadata != null) { - dest.writeInt(metadata.length); - dest.writeByteArray(metadata); - } - } - } - - private static void readSubgroupMetadataFromParcel( - @NonNull Parcel in, @NonNull Map<Integer, byte[]> subgroupMetadata) { - int size = in.readInt(); - - for (int i = 0; i < size; i++) { - Integer key = in.readInt(); - Integer metaDataLen = in.readInt(); - byte[] metadata = null; - if (metaDataLen != 0) { - metadata = new byte[metaDataLen]; - in.readByteArray(metadata); - } - subgroupMetadata.put(key, metadata); - } - } - - public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastSourceInfo> CREATOR = - new Parcelable.Creator<BluetoothLeBroadcastSourceInfo>() { - public @NonNull BluetoothLeBroadcastSourceInfo createFromParcel( - @NonNull Parcel in) { - final byte sourceId = in.readByte(); - final int sourceAddressType = in.readInt(); - final BluetoothDevice sourceDevice = - in.readTypedObject(BluetoothDevice.CREATOR); - final byte sourceAdvSid = in.readByte(); - final int broadcastId = in.readInt(); - final int paSyncState = in.readInt(); - final int audioSyncState = in.readInt(); - final int encryptionStatus = in.readInt(); - final int badBroadcastLen = in.readInt(); - byte[] badBroadcastCode = null; - - if (badBroadcastLen > 0) { - badBroadcastCode = new byte[badBroadcastLen]; - in.readByteArray(badBroadcastCode); - } - final byte numSubGroups = in.readByte(); - final String broadcastCode = in.readString(); - Map<Integer, Integer> subgroupBisSyncState = new HashMap<Integer, Integer>(); - readSubgroupBisSyncStateFromParcel(in, subgroupBisSyncState); - Map<Integer, byte[]> subgroupMetadata = new HashMap<Integer, byte[]>(); - readSubgroupMetadataFromParcel(in, subgroupMetadata); - - BluetoothLeBroadcastSourceInfo srcInfo = - new BluetoothLeBroadcastSourceInfo( - sourceId, - sourceAddressType, - sourceDevice, - sourceAdvSid, - broadcastId, - paSyncState, - encryptionStatus, - audioSyncState, - badBroadcastCode, - numSubGroups, - subgroupBisSyncState, - subgroupMetadata, - broadcastCode); - return srcInfo; - } - - public @NonNull BluetoothLeBroadcastSourceInfo[] newArray(int size) { - return new BluetoothLeBroadcastSourceInfo[size]; - } - }; - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeByte(mSourceId); - out.writeInt(mSourceAddressType); - out.writeTypedObject(mSourceDevice, 0); - out.writeByte(mSourceAdvSid); - out.writeInt(mBroadcastId); - out.writeInt(mPaSyncState); - out.writeInt(mAudioSyncState); - out.writeInt(mEncryptionStatus); - - if (mBadBroadcastCode != null) { - out.writeInt(mBadBroadcastCode.length); - out.writeByteArray(mBadBroadcastCode); - } else { - // zero indicates that there is no "bad broadcast code" - out.writeInt(0); - } - out.writeByte(mNumSubGroups); - out.writeString(mBroadcastCode); - writeSubgroupBisSyncStateToParcel(out, mSubgroupBisSyncState); - writeSubgroupMetadataToParcel(out, mSubgroupMetadata); - } - - private static void log(@NonNull String msg) { - if (DBG) { - Log.d(TAG, msg); - } - } -} -; diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java deleted file mode 100644 index fef6f225dd3b..000000000000 --- a/core/java/android/bluetooth/BluetoothManager.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.RequiresFeature; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SystemService; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.RemoteException; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * High level manager used to obtain an instance of an {@link BluetoothAdapter} - * and to conduct overall Bluetooth Management. - * <p> - * Use {@link android.content.Context#getSystemService(java.lang.String)} - * with {@link Context#BLUETOOTH_SERVICE} to create an {@link BluetoothManager}, - * then call {@link #getAdapter} to obtain the {@link BluetoothAdapter}. - * </p> - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p> - * For more information about using BLUETOOTH, read the <a href= - * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer - * guide. - * </p> - * </div> - * - * @see Context#getSystemService - * @see BluetoothAdapter#getDefaultAdapter() - */ -@SystemService(Context.BLUETOOTH_SERVICE) -@RequiresFeature(PackageManager.FEATURE_BLUETOOTH) -public final class BluetoothManager { - private static final String TAG = "BluetoothManager"; - private static final boolean DBG = false; - - private final AttributionSource mAttributionSource; - private final BluetoothAdapter mAdapter; - - /** - * @hide - */ - public BluetoothManager(Context context) { - mAttributionSource = (context != null) ? context.getAttributionSource() : - AttributionSource.myAttributionSource(); - mAdapter = BluetoothAdapter.createAdapter(mAttributionSource); - } - - /** - * Get the BLUETOOTH Adapter for this device. - * - * @return the BLUETOOTH Adapter - */ - @RequiresNoPermission - public BluetoothAdapter getAdapter() { - return mAdapter; - } - - /** - * Get the current connection state of the profile to the remote device. - * - * <p>This is not specific to any application configuration but represents - * the connection state of the local Bluetooth adapter for certain profile. - * This can be used by applications like status bar which would just like - * to know the state of Bluetooth. - * - * @param device Remote bluetooth device. - * @param profile GATT or GATT_SERVER - * @return State of the profile connection. One of {@link BluetoothProfile#STATE_CONNECTED}, - * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED}, - * {@link BluetoothProfile#STATE_DISCONNECTING} - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device, int profile) { - if (DBG) Log.d(TAG, "getConnectionState()"); - - List<BluetoothDevice> connectedDevices = getConnectedDevices(profile); - for (BluetoothDevice connectedDevice : connectedDevices) { - if (device.equals(connectedDevice)) { - return BluetoothProfile.STATE_CONNECTED; - } - } - - return BluetoothProfile.STATE_DISCONNECTED; - } - - /** - * Get connected devices for the specified profile. - * - * <p> Return the set of devices which are in state {@link BluetoothProfile#STATE_CONNECTED} - * - * <p>This is not specific to any application configuration but represents - * the connection state of Bluetooth for this profile. - * This can be used by applications like status bar which would just like - * to know the state of Bluetooth. - * - * @param profile GATT or GATT_SERVER - * @return List of devices. The list will be empty on error. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices(int profile) { - if (DBG) Log.d(TAG, "getConnectedDevices"); - return getDevicesMatchingConnectionStates(profile, new int[] { - BluetoothProfile.STATE_CONNECTED - }); - } - - /** - * Get a list of devices that match any of the given connection - * states. - * - * <p> If none of the devices match any of the given states, - * an empty list will be returned. - * - * <p>This is not specific to any application configuration but represents - * the connection state of the local Bluetooth adapter for this profile. - * This can be used by applications like status bar which would just like - * to know the state of the local adapter. - * - * @param profile GATT or GATT_SERVER - * @param states Array of states. States can be one of {@link BluetoothProfile#STATE_CONNECTED}, - * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED}, - * {@link BluetoothProfile#STATE_DISCONNECTING}, - * @return List of devices. The list will be empty on error. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int profile, int[] states) { - if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates"); - - if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) { - throw new IllegalArgumentException("Profile not supported: " + profile); - } - - List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); - - try { - IBluetoothManager managerService = mAdapter.getBluetoothManager(); - IBluetoothGatt iGatt = managerService.getBluetoothGatt(); - if (iGatt == null) return devices; - devices = Attributable.setAttributionSource( - iGatt.getDevicesMatchingConnectionStates(states, mAttributionSource), - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - - return devices; - } - - /** - * Open a GATT Server - * The callback is used to deliver results to Caller, such as connection status as well - * as the results of any other GATT server operations. - * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer - * to conduct GATT server operations. - * - * @param context App context - * @param callback GATT server callback handler that will receive asynchronous callbacks. - * @return BluetoothGattServer instance - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGattServer openGattServer(Context context, - BluetoothGattServerCallback callback) { - - return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO)); - } - - /** - * Open a GATT Server - * The callback is used to deliver results to Caller, such as connection status as well - * as the results of any other GATT server operations. - * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer - * to conduct GATT server operations. - * - * @param context App context - * @param callback GATT server callback handler that will receive asynchronous callbacks. - * @param eatt_support idicates if server should use eatt channel for notifications. - * @return BluetoothGattServer instance - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGattServer openGattServer(Context context, - BluetoothGattServerCallback callback, boolean eatt_support) { - return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support)); - } - - /** - * Open a GATT Server - * The callback is used to deliver results to Caller, such as connection status as well - * as the results of any other GATT server operations. - * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer - * to conduct GATT server operations. - * - * @param context App context - * @param callback GATT server callback handler that will receive asynchronous callbacks. - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @return BluetoothGattServer instance - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGattServer openGattServer(Context context, - BluetoothGattServerCallback callback, int transport) { - return (openGattServer(context, callback, transport, false)); - } - - /** - * Open a GATT Server - * The callback is used to deliver results to Caller, such as connection status as well - * as the results of any other GATT server operations. - * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer - * to conduct GATT server operations. - * - * @param context App context - * @param callback GATT server callback handler that will receive asynchronous callbacks. - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @param eatt_support idicates if server should use eatt channel for notifications. - * @return BluetoothGattServer instance - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGattServer openGattServer(Context context, - BluetoothGattServerCallback callback, int transport, boolean eatt_support) { - if (context == null || callback == null) { - throw new IllegalArgumentException("null parameter: " + context + " " + callback); - } - - // TODO(Bluetooth) check whether platform support BLE - // Do the check here or in GattServer? - - try { - IBluetoothManager managerService = mAdapter.getBluetoothManager(); - IBluetoothGatt iGatt = managerService.getBluetoothGatt(); - if (iGatt == null) { - Log.e(TAG, "Fail to get GATT Server connection"); - return null; - } - BluetoothGattServer mGattServer = - new BluetoothGattServer(iGatt, transport, mAdapter); - Boolean regStatus = mGattServer.registerCallback(callback, eatt_support); - return regStatus ? mGattServer : null; - } catch (RemoteException e) { - Log.e(TAG, "", e); - return null; - } - } -} diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java deleted file mode 100644 index 56e497262421..000000000000 --- a/core/java/android/bluetooth/BluetoothMap.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth MAP - * Profile. - * - * @hide - */ -@SystemApi -public final class BluetoothMap implements BluetoothProfile, AutoCloseable { - - private static final String TAG = "BluetoothMap"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - private CloseGuard mCloseGuard; - - /** @hide */ - @SuppressLint("ActionValue") - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * There was an error trying to obtain the state - * - * @hide - */ - public static final int STATE_ERROR = -1; - - /** @hide */ - public static final int RESULT_FAILURE = 0; - /** @hide */ - public static final int RESULT_SUCCESS = 1; - /** - * Connection canceled before completion. - * - * @hide - */ - public static final int RESULT_CANCELED = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothMap> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.MAP, - "BluetoothMap", IBluetoothMap.class.getName()) { - @Override - public IBluetoothMap getServiceInterface(IBinder service) { - return IBluetoothMap.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothMap proxy object. - */ - /* package */ BluetoothMap(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - if (DBG) Log.d(TAG, "Create BluetoothMap proxy object"); - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - mCloseGuard = new CloseGuard(); - mCloseGuard.open("close"); - } - - protected void finalize() { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - close(); - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothMap will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - * - * @hide - */ - @SystemApi - public void close() { - if (VDBG) log("close()"); - mProfileConnector.disconnect(); - } - - private IBluetoothMap getService() { - return mProfileConnector.getService(); - } - - /** - * Get the current state of the BluetoothMap service. - * - * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not - * connected to the Map service. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getState() { - if (VDBG) log("getState()"); - final IBluetoothMap service = getService(); - final int defaultValue = BluetoothMap.STATE_ERROR; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getState(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the currently connected remote Bluetooth device (PCE). - * - * @return The remote Bluetooth device, or null if not in connected or connecting state, or if - * this proxy object is not connected to the Map service. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothDevice getClient() { - if (VDBG) log("getClient()"); - final IBluetoothMap service = getService(); - final BluetoothDevice defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothDevice> recv = - new SynchronousResultReceiver(); - service.getClient(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns true if the specified Bluetooth device is connected. - * Returns false if not connected, or if this proxy object is not - * currently connected to the Map service. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isConnected(BluetoothDevice device) { - if (VDBG) log("isConnected(" + device + ")"); - final IBluetoothMap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate connection. Initiation of outgoing connections is not - * supported for MAP server. - * - * @hide - */ - @RequiresNoPermission - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")" + "not supported for MAPS"); - return false; - } - - /** - * Initiate disconnect. - * - * @param device Remote Bluetooth Device - * @return false on error, true otherwise - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothMap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check class bits for possible Map support. - * This is a simple heuristic that tries to guess if a device with the - * given class bits might support Map. It is not accurate for all - * devices. It tries to err on the side of false positives. - * - * @return True if this device might support Map. - * - * @hide - */ - public static boolean doesClassMatchSink(BluetoothClass btClass) { - // TODO optimize the rule - switch (btClass.getDeviceClass()) { - case BluetoothClass.Device.COMPUTER_DESKTOP: - case BluetoothClass.Device.COMPUTER_LAPTOP: - case BluetoothClass.Device.COMPUTER_SERVER: - case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: - return true; - default: - return false; - } - } - - /** - * Get the list of connected devices. Currently at most one. - * - * @return list of connected devices - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (DBG) log("getConnectedDevices()"); - final IBluetoothMap service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) log("getDevicesMatchingStates()"); - final IBluetoothMap service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) log("getConnectionState(" + device + ")"); - final IBluetoothMap service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = - new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothMap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothMap service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private boolean isEnabled() { - return mAdapter.isEnabled(); - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } -} diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java deleted file mode 100644 index 03536f9aad39..000000000000 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.app.PendingIntent; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.net.Uri; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth MAP MCE Profile. - * - * @hide - */ -@SystemApi -public final class BluetoothMapClient implements BluetoothProfile { - - private static final String TAG = "BluetoothMapClient"; - private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED"; - /** @hide */ - @RequiresPermission(android.Manifest.permission.RECEIVE_SMS) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_RECEIVED = - "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED"; - /* Actions to be used for pending intents */ - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY = - "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY"; - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY = - "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY"; - - /** - * Action to notify read status changed - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_READ_STATUS_CHANGED = - "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED"; - - /** - * Action to notify deleted status changed - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED = - "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED"; - - /** - * Extras used in ACTION_MESSAGE_RECEIVED intent. - * NOTE: HANDLE is only valid for a single session with the device. - */ - /** @hide */ - public static final String EXTRA_MESSAGE_HANDLE = - "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE"; - /** @hide */ - public static final String EXTRA_MESSAGE_TIMESTAMP = - "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP"; - /** @hide */ - public static final String EXTRA_MESSAGE_READ_STATUS = - "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS"; - /** @hide */ - public static final String EXTRA_SENDER_CONTACT_URI = - "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI"; - /** @hide */ - public static final String EXTRA_SENDER_CONTACT_NAME = - "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME"; - - /** - * Used as a boolean extra in ACTION_MESSAGE_DELETED_STATUS_CHANGED - * Contains the MAP message deleted status - * Possible values are: - * true: deleted - * false: undeleted - * - * @hide - */ - public static final String EXTRA_MESSAGE_DELETED_STATUS = - "android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS"; - - /** - * Extra used in ACTION_MESSAGE_READ_STATUS_CHANGED or ACTION_MESSAGE_DELETED_STATUS_CHANGED - * Possible values are: - * 0: failure - * 1: success - * - * @hide - */ - public static final String EXTRA_RESULT_CODE = - "android.bluetooth.device.extra.RESULT_CODE"; - - /** - * There was an error trying to obtain the state - * @hide - */ - public static final int STATE_ERROR = -1; - - /** @hide */ - public static final int RESULT_FAILURE = 0; - /** @hide */ - public static final int RESULT_SUCCESS = 1; - /** - * Connection canceled before completion. - * @hide - */ - public static final int RESULT_CANCELED = 2; - /** @hide */ - private static final int UPLOADING_FEATURE_BITMASK = 0x08; - - /* - * UNREAD, READ, UNDELETED, DELETED are passed as parameters - * to setMessageStatus to indicate the messages new state. - */ - - /** @hide */ - public static final int UNREAD = 0; - /** @hide */ - public static final int READ = 1; - /** @hide */ - public static final int UNDELETED = 2; - /** @hide */ - public static final int DELETED = 3; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT, - "BluetoothMapClient", IBluetoothMapClient.class.getName()) { - @Override - public IBluetoothMapClient getServiceInterface(IBinder service) { - return IBluetoothMapClient.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothMapClient proxy object. - */ - /* package */ BluetoothMapClient(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object"); - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothMap will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - * @hide - */ - public void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothMapClient getService() { - return mProfileConnector.getService(); - } - - /** - * Returns true if the specified Bluetooth device is connected. - * Returns false if not connected, or if this proxy object is not - * currently connected to the Map service. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isConnected(BluetoothDevice device) { - if (VDBG) Log.d(TAG, "isConnected(" + device + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate connection. Initiation of outgoing connections is not - * supported for MAP server. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnect. - * - * @param device Remote Bluetooth Device - * @return false on error, true otherwise - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disconnect(BluetoothDevice device) { - if (DBG) Log.d(TAG, "disconnect(" + device + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of connected devices. Currently at most one. - * - * @return list of connected devices - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (DBG) Log.d(TAG, "getConnectedDevices()"); - final IBluetoothMapClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) Log.d(TAG, "getDevicesMatchingStates()"); - final IBluetoothMapClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) Log.d(TAG, "getConnectionState(" + device + ")"); - final IBluetoothMapClient service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver<>(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) Log.d(TAG, "getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")"); - final IBluetoothMapClient service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send a message. - * - * Send an SMS message to either the contacts primary number or the telephone number specified. - * - * @param device Bluetooth device - * @param contacts Uri Collection of the contacts - * @param message Message to be sent - * @param sentIntent intent issued when message is sent - * @param deliveredIntent intent issued when message is delivered - * @return true if the message is enqueued, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.SEND_SMS, - }) - public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts, - @NonNull String message, @Nullable PendingIntent sentIntent, - @Nullable PendingIntent deliveredIntent) { - return sendMessage(device, contacts.toArray(new Uri[contacts.size()]), message, sentIntent, - deliveredIntent); - } - - /** - * Send a message. - * - * Send an SMS message to either the contacts primary number or the telephone number specified. - * - * @param device Bluetooth device - * @param contacts Uri[] of the contacts - * @param message Message to be sent - * @param sentIntent intent issued when message is sent - * @param deliveredIntent intent issued when message is delivered - * @return true if the message is enqueued, false on error - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.SEND_SMS, - }) - public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, - PendingIntent sentIntent, PendingIntent deliveredIntent) { - if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendMessage(device, contacts, message, sentIntent, deliveredIntent, - mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get unread messages. Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}. - * - * @param device Bluetooth device - * @return true if the message is enqueued, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.READ_SMS, - }) - public boolean getUnreadMessages(BluetoothDevice device) { - if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getUnreadMessages(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns the "Uploading" feature bit value from the SDP record's - * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114). - * @param device The Bluetooth device to get this value for. - * @return Returns true if the Uploading bit value in SDP record's - * MapSupportedFeatures field is set. False is returned otherwise. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isUploadingSupported(BluetoothDevice device) { - if (DBG) Log.d(TAG, "isUploadingSupported(" + device + ")"); - final IBluetoothMapClient service = getService(); - final int defaultValue = 0; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getSupportedFeatures(device, mAttributionSource, recv); - return (recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue) - & UPLOADING_FEATURE_BITMASK) > 0; - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return false; - } - - /** - * Set message status of message on MSE - * <p> - * When read status changed, the result will be published via - * {@link #ACTION_MESSAGE_READ_STATUS_CHANGED} - * When deleted status changed, the result will be published via - * {@link #ACTION_MESSAGE_DELETED_STATUS_CHANGED} - * - * @param device Bluetooth device - * @param handle message handle - * @param status <code>UNREAD</code> for "unread", <code>READ</code> for - * "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for - * "deleted", otherwise return error - * @return <code>true</code> if request has been sent, <code>false</code> on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.READ_SMS, - }) - public boolean setMessageStatus(BluetoothDevice device, String handle, int status) { - if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) && handle != null && (status == READ - || status == UNREAD || status == UNDELETED || status == DELETED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setMessageStatus(device, handle, status, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.isEnabled(); - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } -} diff --git a/core/java/android/bluetooth/BluetoothMasInstance.java b/core/java/android/bluetooth/BluetoothMasInstance.java deleted file mode 100644 index eeaf08545146..000000000000 --- a/core/java/android/bluetooth/BluetoothMasInstance.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public final class BluetoothMasInstance implements Parcelable { - private final int mId; - private final String mName; - private final int mChannel; - private final int mMsgTypes; - - public BluetoothMasInstance(int id, String name, int channel, int msgTypes) { - mId = id; - mName = name; - mChannel = channel; - mMsgTypes = msgTypes; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothMasInstance) { - return mId == ((BluetoothMasInstance) o).mId; - } - return false; - } - - @Override - public int hashCode() { - return mId + (mChannel << 8) + (mMsgTypes << 16); - } - - @Override - public String toString() { - return Integer.toString(mId) + ":" + mName + ":" + mChannel + ":" - + Integer.toHexString(mMsgTypes); - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothMasInstance> CREATOR = - new Parcelable.Creator<BluetoothMasInstance>() { - public BluetoothMasInstance createFromParcel(Parcel in) { - return new BluetoothMasInstance(in.readInt(), in.readString(), - in.readInt(), in.readInt()); - } - - public BluetoothMasInstance[] newArray(int size) { - return new BluetoothMasInstance[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mId); - out.writeString(mName); - out.writeInt(mChannel); - out.writeInt(mMsgTypes); - } - - public static final class MessageType { - public static final int EMAIL = 0x01; - public static final int SMS_GSM = 0x02; - public static final int SMS_CDMA = 0x04; - public static final int MMS = 0x08; - } - - public int getId() { - return mId; - } - - public String getName() { - return mName; - } - - public int getChannel() { - return mChannel; - } - - public int getMsgTypes() { - return mMsgTypes; - } - - public boolean msgSupported(int msg) { - return (mMsgTypes & msg) != 0; - } -} diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java deleted file mode 100644 index ac2b3edb0eb8..000000000000 --- a/core/java/android/bluetooth/BluetoothOutputStream.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.SuppressLint; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * BluetoothOutputStream. - * - * Used to read from a Bluetooth socket. - * - * @hide - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -/*package*/ final class BluetoothOutputStream extends OutputStream { - private BluetoothSocket mSocket; - - /*package*/ BluetoothOutputStream(BluetoothSocket s) { - mSocket = s; - } - - /** - * Close this output stream and the socket associated with it. - */ - public void close() throws IOException { - mSocket.close(); - } - - /** - * Writes a single byte to this stream. Only the least significant byte of - * the integer {@code oneByte} is written to the stream. - * - * @param oneByte the byte to be written. - * @throws IOException if an error occurs while writing to this stream. - * @since Android 1.0 - */ - public void write(int oneByte) throws IOException { - byte[] b = new byte[1]; - b[0] = (byte) oneByte; - mSocket.write(b, 0, 1); - } - - /** - * Writes {@code count} bytes from the byte array {@code buffer} starting - * at position {@code offset} to this stream. - * - * @param b the buffer to be written. - * @param offset the start position in {@code buffer} from where to get bytes. - * @param count the number of bytes from {@code buffer} to write to this stream. - * @throws IOException if an error occurs while writing to this stream. - * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code count < 0}, or if {@code - * offset + count} is bigger than the length of {@code buffer}. - * @since Android 1.0 - */ - public void write(byte[] b, int offset, int count) throws IOException { - if (b == null) { - throw new NullPointerException("buffer is null"); - } - if ((offset | count) < 0 || count > b.length - offset) { - throw new IndexOutOfBoundsException("invalid offset or length"); - } - mSocket.write(b, offset, count); - } -} diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java deleted file mode 100644 index d4ad4ef47acd..000000000000 --- a/core/java/android/bluetooth/BluetoothPan.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth Pan - * Profile. - * - * <p>BluetoothPan is a proxy object for controlling the Bluetooth - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothPan proxy object. - * - * <p>Each method is protected with its appropriate permission. - * - * @hide - */ -@SystemApi -public final class BluetoothPan implements BluetoothProfile { - private static final String TAG = "BluetoothPan"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the Pan - * profile. - * - * <p>This intent will have 4 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * <li> {@link #EXTRA_LOCAL_ROLE} - Which local role the remote device is - * bound to. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or - * {@link #LOCAL_PANU_ROLE} - */ - @SuppressLint("ActionValue") - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Extra for {@link #ACTION_CONNECTION_STATE_CHANGED} intent - * The local role of the PAN profile that the remote device is bound to. - * It can be one of {@link #LOCAL_NAP_ROLE} or {@link #LOCAL_PANU_ROLE}. - */ - @SuppressLint("ActionValue") - public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; - - /** - * Intent used to broadcast the change in tethering state of the Pan - * Profile - * - * <p>This intent will have 1 extra: - * <ul> - * <li> {@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth - * tethering. </li> - * </ul> - * - * <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or - * {@link #TETHERING_STATE_ON} - */ - @RequiresLegacyBluetoothPermission - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_TETHERING_STATE_CHANGED = - "android.bluetooth.action.TETHERING_STATE_CHANGED"; - - /** - * Extra for {@link #ACTION_TETHERING_STATE_CHANGED} intent - * The tethering state of the PAN profile. - * It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}. - */ - public static final String EXTRA_TETHERING_STATE = - "android.bluetooth.extra.TETHERING_STATE"; - - /** @hide */ - @IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE}) - @Retention(RetentionPolicy.SOURCE) - public @interface LocalPanRole {} - - public static final int PAN_ROLE_NONE = 0; - /** - * The local device is acting as a Network Access Point. - */ - public static final int LOCAL_NAP_ROLE = 1; - - /** - * The local device is acting as a PAN User. - */ - public static final int LOCAL_PANU_ROLE = 2; - - /** @hide */ - @IntDef({PAN_ROLE_NONE, REMOTE_NAP_ROLE, REMOTE_PANU_ROLE}) - @Retention(RetentionPolicy.SOURCE) - public @interface RemotePanRole {} - - public static final int REMOTE_NAP_ROLE = 1; - - public static final int REMOTE_PANU_ROLE = 2; - - /** @hide **/ - @IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON}) - @Retention(RetentionPolicy.SOURCE) - public @interface TetheringState{} - - public static final int TETHERING_STATE_OFF = 1; - - public static final int TETHERING_STATE_ON = 2; - /** - * Return codes for the connect and disconnect Bluez / Dbus calls. - * - * @hide - */ - public static final int PAN_DISCONNECT_FAILED_NOT_CONNECTED = 1000; - - /** - * @hide - */ - public static final int PAN_CONNECT_FAILED_ALREADY_CONNECTED = 1001; - - /** - * @hide - */ - public static final int PAN_CONNECT_FAILED_ATTEMPT_FAILED = 1002; - - /** - * @hide - */ - public static final int PAN_OPERATION_GENERIC_FAILURE = 1003; - - /** - * @hide - */ - public static final int PAN_OPERATION_SUCCESS = 1004; - - private final Context mContext; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.PAN, - "BluetoothPan", IBluetoothPan.class.getName()) { - @Override - public IBluetoothPan getServiceInterface(IBinder service) { - return IBluetoothPan.Stub.asInterface(service); - } - }; - - - /** - * Create a BluetoothPan proxy object for interacting with the local - * Bluetooth Service which handles the Pan profile - * - * @hide - */ - @UnsupportedAppUsage - /* package */ BluetoothPan(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mContext = context; - mProfileConnector.connect(context, listener); - } - - /** - * Closes the connection to the service and unregisters callbacks - */ - @UnsupportedAppUsage - void close() { - if (VDBG) log("close()"); - mProfileConnector.disconnect(); - } - - private IBluetoothPan getService() { - return mProfileConnector.getService(); - } - - /** @hide */ - protected void finalize() { - close(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothPan service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @UnsupportedAppUsage - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothPan service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothPan service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothPan service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * @hide - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothPan service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getConnectionState(@NonNull BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothPan service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Turns on/off bluetooth tethering - * - * @param value is whether to enable or disable bluetooth tethering - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.TETHER_PRIVILEGED, - }) - public void setBluetoothTethering(boolean value) { - String pkgName = mContext.getOpPackageName(); - if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); - final IBluetoothPan service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setBluetoothTethering(value, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Determines whether tethering is enabled - * - * @return true if tethering is on, false if not or some error occurred - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isTetheringOn() { - if (VDBG) log("isTetheringOn()"); - final IBluetoothPan service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isTetheringOn(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - @UnsupportedAppUsage - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - @UnsupportedAppUsage - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - @UnsupportedAppUsage - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java deleted file mode 100644 index de2db9c2ca86..000000000000 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Public API for controlling the Bluetooth Pbap Service. This includes - * Bluetooth Phone book Access profile. - * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap - * Service via IPC. - * - * Creating a BluetoothPbap object will create a binding with the - * BluetoothPbap service. Users of this object should call close() when they - * are finished with the BluetoothPbap, so that this proxy object can unbind - * from the service. - * - * This BluetoothPbap object is not immediately bound to the - * BluetoothPbap service. Use the ServiceListener interface to obtain a - * notification when it is bound, this is especially important if you wish to - * immediately call methods on BluetoothPbap after construction. - * - * To get an instance of the BluetoothPbap class, you can call - * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param - * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of - * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}. - * - * Android only supports one connected Bluetooth Pce at a time. - * - * @hide - */ -@SystemApi -public class BluetoothPbap implements BluetoothProfile { - - private static final String TAG = "BluetoothPbap"; - private static final boolean DBG = false; - - /** - * Intent used to broadcast the change in connection state of the PBAP - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE} - * can be any of {@link BluetoothProfile#STATE_DISCONNECTED}, - * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, - * {@link BluetoothProfile#STATE_DISCONNECTING}. - * - * @hide - */ - @SuppressLint("ActionValue") - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; - - private final AttributionSource mAttributionSource; - - /** @hide */ - public static final int RESULT_FAILURE = 0; - /** @hide */ - public static final int RESULT_SUCCESS = 1; - /** - * Connection canceled before completion. - * - * @hide - */ - public static final int RESULT_CANCELED = 2; - - private BluetoothAdapter mAdapter; - private final BluetoothProfileConnector<IBluetoothPbap> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.PBAP, "BluetoothPbap", - IBluetoothPbap.class.getName()) { - @Override - public IBluetoothPbap getServiceInterface(IBinder service) { - return IBluetoothPbap.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothPbap proxy object. - * - * @hide - */ - public BluetoothPbap(Context context, ServiceListener listener, BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /** @hide */ - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothPbap will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - * - * @hide - */ - public synchronized void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothPbap getService() { - return (IBluetoothPbap) mProfileConnector.getService(); - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - log("getConnectedDevices()"); - final IBluetoothPbap service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - return new ArrayList<BluetoothDevice>(); - } - try { - return Attributable.setAttributionSource( - service.getConnectedDevices(mAttributionSource), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return new ArrayList<BluetoothDevice>(); - } - - /** - * {@inheritDoc} - * - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { - log("getConnectionState: device=" + device); - try { - final IBluetoothPbap service = getService(); - if (service != null && isEnabled() && isValidDevice(device)) { - return service.getConnectionState(device, mAttributionSource); - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - return BluetoothProfile.STATE_DISCONNECTED; - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return BluetoothProfile.STATE_DISCONNECTED; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states)); - final IBluetoothPbap service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - return new ArrayList<BluetoothDevice>(); - } - try { - return Attributable.setAttributionSource( - service.getDevicesMatchingConnectionStates(states, mAttributionSource), - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return new ArrayList<BluetoothDevice>(); - } - - /** - * Set connection policy of the profile and tries to disconnect it if connectionPolicy is - * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} - * - * <p> The device should already be paired. - * Connection policy can be one of: - * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, - * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, - * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - try { - final IBluetoothPbap service = getService(); - if (service != null && isEnabled() - && isValidDevice(device)) { - if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - return false; - } - return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); - } - if (service == null) Log.w(TAG, "Proxy not attached to service"); - return false; - } catch (RemoteException e) { - Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); - return false; - } - } - - /** - * Disconnects the current Pbap client (PCE). Currently this call blocks, - * it may soon be made asynchronous. Returns false if this proxy object is - * not currently connected to the Pbap service. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - log("disconnect()"); - final IBluetoothPbap service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - return false; - } - try { - service.disconnect(device, mAttributionSource); - return true; - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return false; - } - - private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; - } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - if (DBG) { - Log.d(TAG, msg); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java deleted file mode 100644 index e096de8cb829..000000000000 --- a/core/java/android/bluetooth/BluetoothPbapClient.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth PBAP Client Profile. - * - * @hide - */ -public final class BluetoothPbapClient implements BluetoothProfile { - - private static final String TAG = "BluetoothPbapClient"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED"; - - /** There was an error trying to obtain the state */ - public static final int STATE_ERROR = -1; - - public static final int RESULT_FAILURE = 0; - public static final int RESULT_SUCCESS = 1; - /** Connection canceled before completion. */ - public static final int RESULT_CANCELED = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothPbapClient> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.PBAP_CLIENT, - "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) { - @Override - public IBluetoothPbapClient getServiceInterface(IBinder service) { - return IBluetoothPbapClient.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothPbapClient proxy object. - */ - BluetoothPbapClient(Context context, ServiceListener listener, BluetoothAdapter adapter) { - if (DBG) { - Log.d(TAG, "Create BluetoothPbapClient proxy object"); - } - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothPbapClient will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - */ - public synchronized void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothPbapClient getService() { - return mProfileConnector.getService(); - } - - /** - * Initiate connection. - * Upon successful connection to remote PBAP server the Client will - * attempt to automatically download the users phonebook and call log. - * - * @param device a remote device we want connect to - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) { - log("connect(" + device + ") for PBAP Client."); - } - final IBluetoothPbapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnect. - * - * @param device Remote Bluetooth Device - * @return false on error, true otherwise - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disconnect(BluetoothDevice device) { - if (DBG) { - log("disconnect(" + device + ")" + new Exception()); - } - final IBluetoothPbapClient service = getService(); - final boolean defaultValue = true; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - return true; - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of connected devices. - * Currently at most one. - * - * @return list of connected devices - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (DBG) { - log("getConnectedDevices()"); - } - final IBluetoothPbapClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) { - log("getDevicesMatchingStates()"); - } - final IBluetoothPbapClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) { - log("getConnectionState(" + device + ")"); - } - final IBluetoothPbapClient service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private boolean isEnabled() { - return mAdapter.isEnabled(); - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) { - log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - } - final IBluetoothPbapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) { - log("getConnectionPolicy(" + device + ")"); - } - final IBluetoothPbapClient service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } -} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java deleted file mode 100644 index e047e5d81a9d..000000000000 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2010-2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.RequiresNoPermission; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -/** - * Public APIs for the Bluetooth Profiles. - * - * <p> Clients should call {@link BluetoothAdapter#getProfileProxy}, - * to get the Profile Proxy. Each public profile implements this - * interface. - */ -public interface BluetoothProfile { - - /** - * Extra for the connection state intents of the individual profiles. - * - * This extra represents the current connection state of the profile of the - * Bluetooth device. - */ - @SuppressLint("ActionValue") - String EXTRA_STATE = "android.bluetooth.profile.extra.STATE"; - - /** - * Extra for the connection state intents of the individual profiles. - * - * This extra represents the previous connection state of the profile of the - * Bluetooth device. - */ - @SuppressLint("ActionValue") - String EXTRA_PREVIOUS_STATE = - "android.bluetooth.profile.extra.PREVIOUS_STATE"; - - /** The profile is in disconnected state */ - int STATE_DISCONNECTED = 0; - /** The profile is in connecting state */ - int STATE_CONNECTING = 1; - /** The profile is in connected state */ - int STATE_CONNECTED = 2; - /** The profile is in disconnecting state */ - int STATE_DISCONNECTING = 3; - - /** @hide */ - @IntDef({ - STATE_DISCONNECTED, - STATE_CONNECTING, - STATE_CONNECTED, - STATE_DISCONNECTING, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BtProfileState {} - - /** - * Headset and Handsfree profile - */ - int HEADSET = 1; - - /** - * A2DP profile. - */ - int A2DP = 2; - - /** - * Health Profile - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - int HEALTH = 3; - - /** - * HID Host - * - * @hide - */ - int HID_HOST = 4; - - /** - * PAN Profile - * - * @hide - */ - @SystemApi - int PAN = 5; - - /** - * PBAP - * - * @hide - */ - int PBAP = 6; - - /** - * GATT - */ - int GATT = 7; - - /** - * GATT_SERVER - */ - int GATT_SERVER = 8; - - /** - * MAP Profile - * - * @hide - */ - int MAP = 9; - - /* - * SAP Profile - * @hide - */ - int SAP = 10; - - /** - * A2DP Sink Profile - * - * @hide - */ - @SystemApi - int A2DP_SINK = 11; - - /** - * AVRCP Controller Profile - * - * @hide - */ - @SystemApi - int AVRCP_CONTROLLER = 12; - - /** - * AVRCP Target Profile - * - * @hide - */ - int AVRCP = 13; - - /** - * Headset Client - HFP HF Role - * - * @hide - */ - @SystemApi - int HEADSET_CLIENT = 16; - - /** - * PBAP Client - * - * @hide - */ - @SystemApi - int PBAP_CLIENT = 17; - - /** - * MAP Messaging Client Equipment (MCE) - * - * @hide - */ - @SystemApi - int MAP_CLIENT = 18; - - /** - * HID Device - */ - int HID_DEVICE = 19; - - /** - * Object Push Profile (OPP) - * - * @hide - */ - int OPP = 20; - - /** - * Hearing Aid Device - * - */ - int HEARING_AID = 21; - - /** - * LE Audio Device - * - */ - int LE_AUDIO = 22; - - /** - * Volume Control profile - * - * @hide - */ - @SystemApi - int VOLUME_CONTROL = 23; - - /** - * @hide - * Media Control Profile server - * - */ - int MCP_SERVER = 24; - - /** - * Coordinated Set Identification Profile set coordinator - * - */ - int CSIP_SET_COORDINATOR = 25; - - /** - * LE Audio Broadcast Source - * - * @hide - */ - int LE_AUDIO_BROADCAST = 26; - - /** - * Max profile ID. This value should be updated whenever a new profile is added to match - * the largest value assigned to a profile. - * - * @hide - */ - int MAX_PROFILE_ID = 26; - - /** - * Default priority for devices that we try to auto-connect to and - * and allow incoming connections for the profile - * - * @hide - **/ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - int PRIORITY_AUTO_CONNECT = 1000; - - /** - * Default priority for devices that allow incoming - * and outgoing connections for the profile - * - * @hide - * @deprecated Replaced with {@link #CONNECTION_POLICY_ALLOWED} - **/ - @Deprecated - @SystemApi - int PRIORITY_ON = 100; - - /** - * Default priority for devices that does not allow incoming - * connections and outgoing connections for the profile. - * - * @hide - * @deprecated Replaced with {@link #CONNECTION_POLICY_FORBIDDEN} - **/ - @Deprecated - @SystemApi - int PRIORITY_OFF = 0; - - /** - * Default priority when not set or when the device is unpaired - * - * @hide - */ - @UnsupportedAppUsage - int PRIORITY_UNDEFINED = -1; - - /** @hide */ - @IntDef(prefix = "CONNECTION_POLICY_", value = {CONNECTION_POLICY_ALLOWED, - CONNECTION_POLICY_FORBIDDEN, CONNECTION_POLICY_UNKNOWN}) - @Retention(RetentionPolicy.SOURCE) - public @interface ConnectionPolicy{} - - /** - * Default connection policy for devices that allow incoming and outgoing connections - * for the profile - * - * @hide - **/ - @SystemApi - int CONNECTION_POLICY_ALLOWED = 100; - - /** - * Default connection policy for devices that do not allow incoming or outgoing connections - * for the profile. - * - * @hide - **/ - @SystemApi - int CONNECTION_POLICY_FORBIDDEN = 0; - - /** - * Default connection policy when not set or when the device is unpaired - * - * @hide - */ - @SystemApi - int CONNECTION_POLICY_UNKNOWN = -1; - - /** - * Get connected devices for this specific profile. - * - * <p> Return the set of devices which are in state {@link #STATE_CONNECTED} - * - * @return List of devices. The list will be empty on error. - */ - public List<BluetoothDevice> getConnectedDevices(); - - /** - * Get a list of devices that match any of the given connection - * states. - * - * <p> If none of the devices match any of the given states, - * an empty list will be returned. - * - * @param states Array of states. States can be one of {@link #STATE_CONNECTED}, {@link - * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}, - * @return List of devices. The list will be empty on error. - */ - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states); - - /** - * Get the current connection state of the profile - * - * @param device Remote bluetooth device. - * @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link - * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} - */ - @BtProfileState int getConnectionState(BluetoothDevice device); - - /** - * An interface for notifying BluetoothProfile IPC clients when they have - * been connected or disconnected to the service. - */ - public interface ServiceListener { - /** - * Called to notify the client when the proxy object has been - * connected to the service. - * - * @param profile - One of {@link #HEADSET} or {@link #A2DP} - * @param proxy - One of {@link BluetoothHeadset} or {@link BluetoothA2dp} - */ - @RequiresNoPermission - public void onServiceConnected(int profile, BluetoothProfile proxy); - - /** - * Called to notify the client that this proxy object has been - * disconnected from the service. - * - * @param profile - One of {@link #HEADSET} or {@link #A2DP} - */ - @RequiresNoPermission - public void onServiceDisconnected(int profile); - } - - /** - * Convert an integer value of connection state into human readable string - * - * @param connectionState - One of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, or {@link #STATE_DISCONNECTED} - * @return a string representation of the connection state, STATE_UNKNOWN if the state - * is not defined - * @hide - */ - static String getConnectionStateName(int connectionState) { - switch (connectionState) { - case STATE_DISCONNECTED: - return "STATE_DISCONNECTED"; - case STATE_CONNECTING: - return "STATE_CONNECTING"; - case STATE_CONNECTED: - return "STATE_CONNECTED"; - case STATE_DISCONNECTING: - return "STATE_DISCONNECTING"; - default: - return "STATE_UNKNOWN"; - } - } - - /** - * Convert an integer value of profile ID into human readable string - * - * @param profile profile ID - * @return profile name as String, UNKOWN_PROFILE if the profile ID is not defined. - * @hide - */ - static String getProfileName(int profile) { - switch(profile) { - case HEADSET: - return "HEADSET"; - case A2DP: - return "A2DP"; - case HID_HOST: - return "HID_HOST"; - case PAN: - return "PAN"; - case PBAP: - return "PBAP"; - case GATT: - return "GATT"; - case GATT_SERVER: - return "GATT_SERVER"; - case MAP: - return "MAP"; - case SAP: - return "SAP"; - case A2DP_SINK: - return "A2DP_SINK"; - case AVRCP_CONTROLLER: - return "AVRCP_CONTROLLER"; - case AVRCP: - return "AVRCP"; - case HEADSET_CLIENT: - return "HEADSET_CLIENT"; - case PBAP_CLIENT: - return "PBAP_CLIENT"; - case MAP_CLIENT: - return "MAP_CLIENT"; - case HID_DEVICE: - return "HID_DEVICE"; - case OPP: - return "OPP"; - case HEARING_AID: - return "HEARING_AID"; - case LE_AUDIO: - return "LE_AUDIO"; - default: - return "UNKNOWN_PROFILE"; - } - } -} diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java deleted file mode 100644 index a457679716d3..000000000000 --- a/core/java/android/bluetooth/BluetoothProfileConnector.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 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 android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.CloseGuard; -import android.util.Log; - -import java.util.List; -/** - * Connector for Bluetooth profile proxies to bind manager service and - * profile services - * @param <T> The Bluetooth profile interface for this connection. - * @hide - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -public abstract class BluetoothProfileConnector<T> { - private final CloseGuard mCloseGuard = new CloseGuard(); - private final int mProfileId; - private BluetoothProfile.ServiceListener mServiceListener; - private final BluetoothProfile mProfileProxy; - private Context mContext; - private final String mProfileName; - private final String mServiceName; - private volatile T mService; - - private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = - new IBluetoothStateChangeCallback.Stub() { - public void onBluetoothStateChange(boolean up) { - if (up) { - doBind(); - } else { - doUnbind(); - } - } - }; - - private @Nullable ComponentName resolveSystemService(@NonNull Intent intent, - @NonNull PackageManager pm) { - List<ResolveInfo> results = pm.queryIntentServices(intent, - PackageManager.ResolveInfoFlags.of(0)); - if (results == null) { - return null; - } - ComponentName comp = null; - for (int i = 0; i < results.size(); i++) { - ResolveInfo ri = results.get(i); - if ((ri.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - continue; - } - ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName, - ri.serviceInfo.name); - if (comp != null) { - throw new IllegalStateException("Multiple system services handle " + intent - + ": " + comp + ", " + foundComp); - } - comp = foundComp; - } - return comp; - } - - private final ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - logDebug("Proxy object connected"); - mService = getServiceInterface(service); - - if (mServiceListener != null) { - mServiceListener.onServiceConnected(mProfileId, mProfileProxy); - } - } - - public void onServiceDisconnected(ComponentName className) { - logDebug("Proxy object disconnected"); - doUnbind(); - if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(mProfileId); - } - } - }; - - BluetoothProfileConnector(BluetoothProfile profile, int profileId, String profileName, - String serviceName) { - mProfileId = profileId; - mProfileProxy = profile; - mProfileName = profileName; - mServiceName = serviceName; - } - - /** {@hide} */ - @Override - public void finalize() { - mCloseGuard.warnIfOpen(); - doUnbind(); - } - - @SuppressLint("AndroidFrameworkRequiresPermission") - private boolean doBind() { - synchronized (mConnection) { - if (mService == null) { - logDebug("Binding service..."); - mCloseGuard.open("doUnbind"); - try { - Intent intent = new Intent(mServiceName); - ComponentName comp = resolveSystemService(intent, mContext.getPackageManager()); - intent.setComponent(comp); - if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - UserHandle.CURRENT)) { - logError("Could not bind to Bluetooth Service with " + intent); - return false; - } - } catch (SecurityException se) { - logError("Failed to bind service. " + se); - return false; - } - } - } - return true; - } - - private void doUnbind() { - synchronized (mConnection) { - if (mService != null) { - logDebug("Unbinding service..."); - mCloseGuard.close(); - try { - mContext.unbindService(mConnection); - } catch (IllegalArgumentException ie) { - logError("Unable to unbind service: " + ie); - } finally { - mService = null; - } - } - } - } - - void connect(Context context, BluetoothProfile.ServiceListener listener) { - mContext = context; - mServiceListener = listener; - IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); - - // Preserve legacy compatibility where apps were depending on - // registerStateChangeCallback() performing a permissions check which - // has been relaxed in modern platform versions - if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R - && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Need BLUETOOTH permission"); - } - - if (mgr != null) { - try { - mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException re) { - logError("Failed to register state change callback. " + re); - } - } - doBind(); - } - - void disconnect() { - mServiceListener = null; - IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); - if (mgr != null) { - try { - mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException re) { - logError("Failed to unregister state change callback" + re); - } - } - doUnbind(); - } - - T getService() { - return mService; - } - - /** - * This abstract function is used to implement method to get the - * connected Bluetooth service interface. - * @param service the connected binder service. - * @return T the binder interface of {@code service}. - * @hide - */ - public abstract T getServiceInterface(IBinder service); - - private void logDebug(String log) { - Log.d(mProfileName, log); - } - - private void logError(String log) { - Log.e(mProfileName, log); - } -} diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java deleted file mode 100644 index 808fa3913316..000000000000 --- a/core/java/android/bluetooth/BluetoothSap.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth SIM - * Access Profile (SAP). - * - * <p>BluetoothSap is a proxy object for controlling the Bluetooth - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothSap proxy object. - * - * <p>Each method is protected with its appropriate permission. - * - * @hide - */ -public final class BluetoothSap implements BluetoothProfile { - - private static final String TAG = "BluetoothSap"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the profile. - * - * <p>This intent will have 4 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * There was an error trying to obtain the state. - * - * @hide - */ - public static final int STATE_ERROR = -1; - - /** - * Connection state change succceeded. - * - * @hide - */ - public static final int RESULT_SUCCESS = 1; - - /** - * Connection canceled before completion. - * - * @hide - */ - public static final int RESULT_CANCELED = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothSap> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.SAP, - "BluetoothSap", IBluetoothSap.class.getName()) { - @Override - public IBluetoothSap getServiceInterface(IBinder service) { - return IBluetoothSap.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothSap proxy object. - */ - /* package */ BluetoothSap(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - if (DBG) Log.d(TAG, "Create BluetoothSap proxy object"); - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothSap will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - * - * @hide - */ - public synchronized void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothSap getService() { - return mProfileConnector.getService(); - } - - /** - * Get the current state of the BluetoothSap service. - * - * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not - * connected to the Sap service. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getState() { - if (VDBG) log("getState()"); - final IBluetoothSap service = getService(); - final int defaultValue = BluetoothSap.STATE_ERROR; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getState(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the currently connected remote Bluetooth device (PCE). - * - * @return The remote Bluetooth device, or null if not in connected or connecting state, or if - * this proxy object is not connected to the Sap service. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothDevice getClient() { - if (VDBG) log("getClient()"); - final IBluetoothSap service = getService(); - final BluetoothDevice defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothDevice> recv = - new SynchronousResultReceiver(); - service.getClient(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns true if the specified Bluetooth device is connected. - * Returns false if not connected, or if this proxy object is not - * currently connected to the Sap service. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isConnected(BluetoothDevice device) { - if (VDBG) log("isConnected(" + device + ")"); - final IBluetoothSap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate connection. Initiation of outgoing connections is not - * supported for SAP server. - * - * @hide - */ - @RequiresNoPermission - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")" + "not supported for SAPS"); - return false; - } - - /** - * Initiate disconnect. - * - * @param device Remote Bluetooth Device - * @return false on error, true otherwise - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothSap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of connected devices. Currently at most one. - * - * @return list of connected devices - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (DBG) log("getConnectedDevices()"); - final IBluetoothSap service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) log("getDevicesMatchingStates()"); - final IBluetoothSap service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) log("getConnectionState(" + device + ")"); - final IBluetoothSap service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothSap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothSap service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private boolean isEnabled() { - return mAdapter.isEnabled(); - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } -} diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java deleted file mode 100644 index bb4e35483fea..000000000000 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.SuppressLint; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Handler; -import android.os.ParcelUuid; -import android.util.Log; - -import java.io.Closeable; -import java.io.IOException; - -/** - * A listening Bluetooth socket. - * - * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: - * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server - * side, use a {@link BluetoothServerSocket} to create a listening server - * socket. When a connection is accepted by the {@link BluetoothServerSocket}, - * it will return a new {@link BluetoothSocket} to manage the connection. - * On the client side, use a single {@link BluetoothSocket} to both initiate - * an outgoing connection and to manage the connection. - * - * <p>For Bluetooth BR/EDR, the most common type of socket is RFCOMM, which is the type supported by - * the Android APIs. RFCOMM is a connection-oriented, streaming transport over Bluetooth BR/EDR. It - * is also known as the Serial Port Profile (SPP). To create a listening - * {@link BluetoothServerSocket} that's ready for incoming Bluetooth BR/EDR connections, use {@link - * BluetoothAdapter#listenUsingRfcommWithServiceRecord - * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}. - * - * <p>For Bluetooth LE, the socket uses LE Connection-oriented Channel (CoC). LE CoC is a - * connection-oriented, streaming transport over Bluetooth LE and has a credit-based flow control. - * Correspondingly, use {@link BluetoothAdapter#listenUsingL2capChannel - * BluetoothAdapter.listenUsingL2capChannel()} to create a listening {@link BluetoothServerSocket} - * that's ready for incoming Bluetooth LE CoC connections. For LE CoC, you can use {@link #getPsm()} - * to get the protocol/service multiplexer (PSM) value that the peer needs to use to connect to your - * socket. - * - * <p> After the listening {@link BluetoothServerSocket} is created, call {@link #accept()} to - * listen for incoming connection requests. This call will block until a connection is established, - * at which point, it will return a {@link BluetoothSocket} to manage the connection. Once the - * {@link BluetoothSocket} is acquired, it's a good idea to call {@link #close()} on the {@link - * BluetoothServerSocket} when it's no longer needed for accepting - * connections. Closing the {@link BluetoothServerSocket} will <em>not</em> close the returned - * {@link BluetoothSocket}. - * - * <p>{@link BluetoothServerSocket} is thread - * safe. In particular, {@link #close} will always immediately abort ongoing - * operations and close the server socket. - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p>For more information about using Bluetooth, read the - * <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer guide.</p> - * </div> - * - * {@see BluetoothSocket} - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -public final class BluetoothServerSocket implements Closeable { - - private static final String TAG = "BluetoothServerSocket"; - private static final boolean DBG = false; - @UnsupportedAppUsage(publicAlternatives = "Use public {@link BluetoothServerSocket} API " - + "instead.") - /*package*/ final BluetoothSocket mSocket; - private Handler mHandler; - private int mMessage; - private int mChannel; - - /** - * Construct a socket for incoming connections. - * - * @param type type of socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param port remote port - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port) - throws IOException { - mChannel = port; - mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null); - if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - mSocket.setExcludeSdp(true); - } - } - - /** - * Construct a socket for incoming connections. - * - * @param type type of socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param port remote port - * @param mitm enforce person-in-the-middle protection for authentication. - * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port, - boolean mitm, boolean min16DigitPin) - throws IOException { - mChannel = port; - mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null, mitm, - min16DigitPin); - if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - mSocket.setExcludeSdp(true); - } - } - - /** - * Construct a socket for incoming connections. - * - * @param type type of socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param uuid uuid - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid) - throws IOException { - mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid); - // TODO: This is the same as mChannel = -1 - is this intentional? - mChannel = mSocket.getPort(); - } - - - /** - * Block until a connection is established. - * <p>Returns a connected {@link BluetoothSocket} on successful connection. - * <p>Once this call returns, it can be called again to accept subsequent - * incoming connections. - * <p>{@link #close} can be used to abort this call from another thread. - * - * @return a connected {@link BluetoothSocket} - * @throws IOException on error, for example this call was aborted, or timeout - */ - public BluetoothSocket accept() throws IOException { - return accept(-1); - } - - /** - * Block until a connection is established, with timeout. - * <p>Returns a connected {@link BluetoothSocket} on successful connection. - * <p>Once this call returns, it can be called again to accept subsequent - * incoming connections. - * <p>{@link #close} can be used to abort this call from another thread. - * - * @return a connected {@link BluetoothSocket} - * @throws IOException on error, for example this call was aborted, or timeout - */ - public BluetoothSocket accept(int timeout) throws IOException { - return mSocket.accept(timeout); - } - - /** - * Immediately close this socket, and release all associated resources. - * <p>Causes blocked calls on this socket in other threads to immediately - * throw an IOException. - * <p>Closing the {@link BluetoothServerSocket} will <em>not</em> - * close any {@link BluetoothSocket} received from {@link #accept()}. - */ - public void close() throws IOException { - if (DBG) Log.d(TAG, "BluetoothServerSocket:close() called. mChannel=" + mChannel); - synchronized (this) { - if (mHandler != null) { - mHandler.obtainMessage(mMessage).sendToTarget(); - } - } - mSocket.close(); - } - - /*package*/ - synchronized void setCloseHandler(Handler handler, int message) { - mHandler = handler; - mMessage = message; - } - - /*package*/ void setServiceName(String serviceName) { - mSocket.setServiceName(serviceName); - } - - /** - * Returns the channel on which this socket is bound. - * - * @hide - */ - public int getChannel() { - return mChannel; - } - - /** - * Returns the assigned dynamic protocol/service multiplexer (PSM) value for the listening L2CAP - * Connection-oriented Channel (CoC) server socket. This server socket must be returned by the - * {@link BluetoothAdapter#listenUsingL2capChannel()} or {@link - * BluetoothAdapter#listenUsingInsecureL2capChannel()}. The returned value is undefined if this - * method is called on non-L2CAP server sockets. - * - * @return the assigned PSM or LE_PSM value depending on transport - */ - public int getPsm() { - return mChannel; - } - - /** - * Sets the channel on which future sockets are bound. - * Currently used only when a channel is auto generated. - */ - /*package*/ void setChannel(int newChannel) { - /* TODO: From a design/architecture perspective this is wrong. - * The bind operation should be conducted through this class - * and the resulting port should be kept in mChannel, and - * not set from BluetoothAdapter. */ - if (mSocket != null) { - if (mSocket.getPort() != newChannel) { - Log.w(TAG, "The port set is different that the underlying port. mSocket.getPort(): " - + mSocket.getPort() + " requested newChannel: " + newChannel); - } - } - mChannel = newChannel; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("ServerSocket: Type: "); - switch (mSocket.getConnectionType()) { - case BluetoothSocket.TYPE_RFCOMM: { - sb.append("TYPE_RFCOMM"); - break; - } - case BluetoothSocket.TYPE_L2CAP: { - sb.append("TYPE_L2CAP"); - break; - } - case BluetoothSocket.TYPE_L2CAP_LE: { - sb.append("TYPE_L2CAP_LE"); - break; - } - case BluetoothSocket.TYPE_SCO: { - sb.append("TYPE_SCO"); - break; - } - } - sb.append(" Channel: ").append(mChannel); - return sb.toString(); - } -} diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java deleted file mode 100644 index db5b75148e88..000000000000 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ /dev/null @@ -1,809 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.net.LocalSocket; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import java.io.Closeable; -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Locale; -import java.util.UUID; - -/** - * A connected or connecting Bluetooth socket. - * - * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: - * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server - * side, use a {@link BluetoothServerSocket} to create a listening server - * socket. When a connection is accepted by the {@link BluetoothServerSocket}, - * it will return a new {@link BluetoothSocket} to manage the connection. - * On the client side, use a single {@link BluetoothSocket} to both initiate - * an outgoing connection and to manage the connection. - * - * <p>The most common type of Bluetooth socket is RFCOMM, which is the type - * supported by the Android APIs. RFCOMM is a connection-oriented, streaming - * transport over Bluetooth. It is also known as the Serial Port Profile (SPP). - * - * <p>To create a {@link BluetoothSocket} for connecting to a known device, use - * {@link BluetoothDevice#createRfcommSocketToServiceRecord - * BluetoothDevice.createRfcommSocketToServiceRecord()}. - * Then call {@link #connect()} to attempt a connection to the remote device. - * This call will block until a connection is established or the connection - * fails. - * - * <p>To create a {@link BluetoothSocket} as a server (or "host"), see the - * {@link BluetoothServerSocket} documentation. - * - * <p>Once the socket is connected, whether initiated as a client or accepted - * as a server, open the IO streams by calling {@link #getInputStream} and - * {@link #getOutputStream} in order to retrieve {@link java.io.InputStream} - * and {@link java.io.OutputStream} objects, respectively, which are - * automatically connected to the socket. - * - * <p>{@link BluetoothSocket} is thread - * safe. In particular, {@link #close} will always immediately abort ongoing - * operations and close the socket. - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p>For more information about using Bluetooth, read the - * <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer guide.</p> - * </div> - * - * {@see BluetoothServerSocket} - * {@see java.io.InputStream} - * {@see java.io.OutputStream} - */ -public final class BluetoothSocket implements Closeable { - private static final String TAG = "BluetoothSocket"; - private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); - - /** @hide */ - public static final int MAX_RFCOMM_CHANNEL = 30; - /*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF; - - /** RFCOMM socket */ - public static final int TYPE_RFCOMM = 1; - - /** SCO socket */ - public static final int TYPE_SCO = 2; - - /** L2CAP socket */ - public static final int TYPE_L2CAP = 3; - - /** L2CAP socket on BR/EDR transport - * @hide - */ - public static final int TYPE_L2CAP_BREDR = TYPE_L2CAP; - - /** L2CAP socket on LE transport - * @hide - */ - public static final int TYPE_L2CAP_LE = 4; - - /*package*/ static final int EBADFD = 77; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - /*package*/ static final int EADDRINUSE = 98; - - /*package*/ static final int SEC_FLAG_ENCRYPT = 1; - /*package*/ static final int SEC_FLAG_AUTH = 1 << 1; - /*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2; - /*package*/ static final int SEC_FLAG_AUTH_MITM = 1 << 3; - /*package*/ static final int SEC_FLAG_AUTH_16_DIGIT = 1 << 4; - - private final int mType; /* one of TYPE_RFCOMM etc */ - private BluetoothDevice mDevice; /* remote device */ - private String mAddress; /* remote address */ - private final boolean mAuth; - private final boolean mEncrypt; - private final BluetoothInputStream mInputStream; - private final BluetoothOutputStream mOutputStream; - private final ParcelUuid mUuid; - /** when true no SPP SDP record will be created */ - private boolean mExcludeSdp = false; - /** when true Person-in-the-middle protection will be enabled */ - private boolean mAuthMitm = false; - /** Minimum 16 digit pin for sec mode 2 connections */ - private boolean mMin16DigitPin = false; - @UnsupportedAppUsage(publicAlternatives = "Use {@link BluetoothSocket} public API instead.") - private ParcelFileDescriptor mPfd; - @UnsupportedAppUsage - private LocalSocket mSocket; - private InputStream mSocketIS; - private OutputStream mSocketOS; - @UnsupportedAppUsage - private int mPort; /* RFCOMM channel or L2CAP psm */ - private int mFd; - private String mServiceName; - private static final int PROXY_CONNECTION_TIMEOUT = 5000; - - private static final int SOCK_SIGNAL_SIZE = 20; - - private ByteBuffer mL2capBuffer = null; - private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer. - private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received. - - private enum SocketState { - INIT, - CONNECTED, - LISTENING, - CLOSED, - } - - /** prevents all native calls after destroyNative() */ - private volatile SocketState mSocketState; - - /** protects mSocketState */ - //private final ReentrantReadWriteLock mLock; - - /** - * Construct a BluetoothSocket. - * - * @param type type of socket - * @param fd fd to use for connected socket, or -1 for a new socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param device remote device that this socket can connect to - * @param port remote port - * @param uuid SDP uuid - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, - BluetoothDevice device, int port, ParcelUuid uuid) throws IOException { - this(type, fd, auth, encrypt, device, port, uuid, false, false); - } - - /** - * Construct a BluetoothSocket. - * - * @param type type of socket - * @param fd fd to use for connected socket, or -1 for a new socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param device remote device that this socket can connect to - * @param port remote port - * @param uuid SDP uuid - * @param mitm enforce person-in-the-middle protection. - * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, - BluetoothDevice device, int port, ParcelUuid uuid, boolean mitm, boolean min16DigitPin) - throws IOException { - if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type); - if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1 - && port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - if (port < 1 || port > MAX_RFCOMM_CHANNEL) { - throw new IOException("Invalid RFCOMM channel: " + port); - } - } - if (uuid != null) { - mUuid = uuid; - } else { - mUuid = new ParcelUuid(new UUID(0, 0)); - } - mType = type; - mAuth = auth; - mAuthMitm = mitm; - mMin16DigitPin = min16DigitPin; - mEncrypt = encrypt; - mDevice = device; - mPort = port; - mFd = fd; - - mSocketState = SocketState.INIT; - - if (device == null) { - // Server socket - mAddress = BluetoothAdapter.getDefaultAdapter().getAddress(); - } else { - // Remote socket - mAddress = device.getAddress(); - } - mInputStream = new BluetoothInputStream(this); - mOutputStream = new BluetoothOutputStream(this); - } - - private BluetoothSocket(BluetoothSocket s) { - if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType); - mUuid = s.mUuid; - mType = s.mType; - mAuth = s.mAuth; - mEncrypt = s.mEncrypt; - mPort = s.mPort; - mInputStream = new BluetoothInputStream(this); - mOutputStream = new BluetoothOutputStream(this); - mMaxRxPacketSize = s.mMaxRxPacketSize; - mMaxTxPacketSize = s.mMaxTxPacketSize; - - mServiceName = s.mServiceName; - mExcludeSdp = s.mExcludeSdp; - mAuthMitm = s.mAuthMitm; - mMin16DigitPin = s.mMin16DigitPin; - } - - private BluetoothSocket acceptSocket(String remoteAddr) throws IOException { - BluetoothSocket as = new BluetoothSocket(this); - as.mSocketState = SocketState.CONNECTED; - FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors(); - if (DBG) Log.d(TAG, "socket fd passed by stack fds: " + Arrays.toString(fds)); - if (fds == null || fds.length != 1) { - Log.e(TAG, "socket fd passed from stack failed, fds: " + Arrays.toString(fds)); - as.close(); - throw new IOException("bt socket acept failed"); - } - - as.mPfd = ParcelFileDescriptor.dup(fds[0]); - as.mSocket = LocalSocket.createConnectedLocalSocket(fds[0]); - as.mSocketIS = as.mSocket.getInputStream(); - as.mSocketOS = as.mSocket.getOutputStream(); - as.mAddress = remoteAddr; - as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddr); - return as; - } - - /** - * Construct a BluetoothSocket from address. Used by native code. - * - * @param type type of socket - * @param fd fd to use for connected socket, or -1 for a new socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param address remote device that this socket can connect to - * @param port remote port - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, - int port) throws IOException { - this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null, false, false); - } - - /** @hide */ - @Override - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - private int getSecurityFlags() { - int flags = 0; - if (mAuth) { - flags |= SEC_FLAG_AUTH; - } - if (mEncrypt) { - flags |= SEC_FLAG_ENCRYPT; - } - if (mExcludeSdp) { - flags |= BTSOCK_FLAG_NO_SDP; - } - if (mAuthMitm) { - flags |= SEC_FLAG_AUTH_MITM; - } - if (mMin16DigitPin) { - flags |= SEC_FLAG_AUTH_16_DIGIT; - } - return flags; - } - - /** - * Get the remote device this socket is connecting, or connected, to. - * - * @return remote device - */ - @RequiresNoPermission - public BluetoothDevice getRemoteDevice() { - return mDevice; - } - - /** - * Get the input stream associated with this socket. - * <p>The input stream will be returned even if the socket is not yet - * connected, but operations on that stream will throw IOException until - * the associated socket is connected. - * - * @return InputStream - */ - @RequiresNoPermission - public InputStream getInputStream() throws IOException { - return mInputStream; - } - - /** - * Get the output stream associated with this socket. - * <p>The output stream will be returned even if the socket is not yet - * connected, but operations on that stream will throw IOException until - * the associated socket is connected. - * - * @return OutputStream - */ - @RequiresNoPermission - public OutputStream getOutputStream() throws IOException { - return mOutputStream; - } - - /** - * Get the connection status of this socket, ie, whether there is an active connection with - * remote device. - * - * @return true if connected false if not connected - */ - @RequiresNoPermission - public boolean isConnected() { - return mSocketState == SocketState.CONNECTED; - } - - /*package*/ void setServiceName(String name) { - mServiceName = name; - } - - /** - * Attempt to connect to a remote device. - * <p>This method will block until a connection is made or the connection - * fails. If this method returns without an exception then this socket - * is now connected. - * <p>Creating new connections to - * remote Bluetooth devices should not be attempted while device discovery - * is in progress. Device discovery is a heavyweight procedure on the - * Bluetooth adapter and will significantly slow a device connection. - * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing - * discovery. Discovery is not managed by the Activity, - * but is run as a system service, so an application should always call - * {@link BluetoothAdapter#cancelDiscovery()} even if it - * did not directly request a discovery, just to be sure. - * <p>{@link #close} can be used to abort this call from another thread. - * - * @throws IOException on error, for example connection failure - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void connect() throws IOException { - if (mDevice == null) throw new IOException("Connect is called on null device"); - - try { - if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - IBluetooth bluetoothProxy = - BluetoothAdapter.getDefaultAdapter().getBluetoothService(); - if (bluetoothProxy == null) throw new IOException("Bluetooth is off"); - mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType, - mUuid, mPort, getSecurityFlags()); - synchronized (this) { - if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd); - if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - if (mPfd == null) throw new IOException("bt socket connect failed"); - FileDescriptor fd = mPfd.getFileDescriptor(); - mSocket = LocalSocket.createConnectedLocalSocket(fd); - mSocketIS = mSocket.getInputStream(); - mSocketOS = mSocket.getOutputStream(); - } - int channel = readInt(mSocketIS); - if (channel <= 0) { - throw new IOException("bt socket connect failed"); - } - mPort = channel; - waitSocketSignal(mSocketIS); - synchronized (this) { - if (mSocketState == SocketState.CLOSED) { - throw new IOException("bt socket closed"); - } - mSocketState = SocketState.CONNECTED; - } - } catch (RemoteException e) { - Log.e(TAG, Log.getStackTraceString(new Throwable())); - throw new IOException("unable to send RPC: " + e.getMessage()); - } - } - - /** - * Currently returns unix errno instead of throwing IOException, - * so that BluetoothAdapter can check the error code for EADDRINUSE - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - /*package*/ int bindListen() { - int ret; - if (mSocketState == SocketState.CLOSED) return EBADFD; - IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(); - if (bluetoothProxy == null) { - Log.e(TAG, "bindListen fail, reason: bluetooth is off"); - return -1; - } - try { - if (DBG) Log.d(TAG, "bindListen(): mPort=" + mPort + ", mType=" + mType); - mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName, - mUuid, mPort, getSecurityFlags()); - } catch (RemoteException e) { - Log.e(TAG, Log.getStackTraceString(new Throwable())); - return -1; - } - - // read out port number - try { - synchronized (this) { - if (DBG) { - Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " + mPfd); - } - if (mSocketState != SocketState.INIT) return EBADFD; - if (mPfd == null) return -1; - FileDescriptor fd = mPfd.getFileDescriptor(); - if (fd == null) { - Log.e(TAG, "bindListen(), null file descriptor"); - return -1; - } - - if (DBG) Log.d(TAG, "bindListen(), Create LocalSocket"); - mSocket = LocalSocket.createConnectedLocalSocket(fd); - if (DBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream()"); - mSocketIS = mSocket.getInputStream(); - mSocketOS = mSocket.getOutputStream(); - } - if (DBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS); - int channel = readInt(mSocketIS); - synchronized (this) { - if (mSocketState == SocketState.INIT) { - mSocketState = SocketState.LISTENING; - } - } - if (DBG) Log.d(TAG, "bindListen(): channel=" + channel + ", mPort=" + mPort); - if (mPort <= -1) { - mPort = channel; - } // else ASSERT(mPort == channel) - ret = 0; - } catch (IOException e) { - if (mPfd != null) { - try { - mPfd.close(); - } catch (IOException e1) { - Log.e(TAG, "bindListen, close mPfd: " + e1); - } - mPfd = null; - } - Log.e(TAG, "bindListen, fail to get port number, exception: " + e); - return -1; - } - return ret; - } - - /*package*/ BluetoothSocket accept(int timeout) throws IOException { - BluetoothSocket acceptedSocket; - if (mSocketState != SocketState.LISTENING) { - throw new IOException("bt socket is not in listen state"); - } - if (timeout > 0) { - Log.d(TAG, "accept() set timeout (ms):" + timeout); - mSocket.setSoTimeout(timeout); - } - String RemoteAddr = waitSocketSignal(mSocketIS); - if (timeout > 0) { - mSocket.setSoTimeout(0); - } - synchronized (this) { - if (mSocketState != SocketState.LISTENING) { - throw new IOException("bt socket is not in listen state"); - } - acceptedSocket = acceptSocket(RemoteAddr); - //quick drop the reference of the file handle - } - return acceptedSocket; - } - - /*package*/ int available() throws IOException { - if (VDBG) Log.d(TAG, "available: " + mSocketIS); - return mSocketIS.available(); - } - - /*package*/ int read(byte[] b, int offset, int length) throws IOException { - int ret = 0; - if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length); - if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { - int bytesToRead = length; - if (VDBG) { - Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length - + "mL2capBuffer= " + mL2capBuffer); - } - if (mL2capBuffer == null) { - createL2capRxBuffer(); - } - if (mL2capBuffer.remaining() == 0) { - if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling..."); - if (fillL2capRxBuffer() == -1) { - return -1; - } - } - if (bytesToRead > mL2capBuffer.remaining()) { - bytesToRead = mL2capBuffer.remaining(); - } - if (VDBG) { - Log.v(TAG, "get(): offset: " + offset - + " bytesToRead: " + bytesToRead); - } - mL2capBuffer.get(b, offset, bytesToRead); - ret = bytesToRead; - } else { - if (VDBG) Log.v(TAG, "default: read(): offset: " + offset + " length:" + length); - ret = mSocketIS.read(b, offset, length); - } - if (ret < 0) { - throw new IOException("bt socket closed, read return: " + ret); - } - if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret); - return ret; - } - - /*package*/ int write(byte[] b, int offset, int length) throws IOException { - - //TODO: Since bindings can exist between the SDU size and the - // protocol, we might need to throw an exception instead of just - // splitting the write into multiple smaller writes. - // Rfcomm uses dynamic allocation, and should not have any bindings - // to the actual message length. - if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length); - if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { - if (length <= mMaxTxPacketSize) { - mSocketOS.write(b, offset, length); - } else { - if (DBG) { - Log.w(TAG, "WARNING: Write buffer larger than L2CAP packet size!\n" - + "Packet will be divided into SDU packets of size " - + mMaxTxPacketSize); - } - int tmpOffset = offset; - int bytesToWrite = length; - while (bytesToWrite > 0) { - int tmpLength = (bytesToWrite > mMaxTxPacketSize) - ? mMaxTxPacketSize - : bytesToWrite; - mSocketOS.write(b, tmpOffset, tmpLength); - tmpOffset += tmpLength; - bytesToWrite -= tmpLength; - } - } - } else { - mSocketOS.write(b, offset, length); - } - // There is no good way to confirm since the entire process is asynchronous anyway - if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length); - return length; - } - - @Override - public void close() throws IOException { - Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS - + ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket + ", mSocketState: " - + mSocketState); - if (mSocketState == SocketState.CLOSED) { - return; - } else { - synchronized (this) { - if (mSocketState == SocketState.CLOSED) { - return; - } - mSocketState = SocketState.CLOSED; - if (mSocket != null) { - if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket); - mSocket.shutdownInput(); - mSocket.shutdownOutput(); - mSocket.close(); - mSocket = null; - } - if (mPfd != null) { - mPfd.close(); - mPfd = null; - } - } - } - } - - /*package */ void removeChannel() { - } - - /*package */ int getPort() { - return mPort; - } - - /** - * Get the maximum supported Transmit packet size for the underlying transport. - * Use this to optimize the writes done to the output socket, to avoid sending - * half full packets. - * - * @return the maximum supported Transmit packet size for the underlying transport. - */ - @RequiresNoPermission - public int getMaxTransmitPacketSize() { - return mMaxTxPacketSize; - } - - /** - * Get the maximum supported Receive packet size for the underlying transport. - * Use this to optimize the reads done on the input stream, as any call to read - * will return a maximum of this amount of bytes - or for some transports a - * multiple of this value. - * - * @return the maximum supported Receive packet size for the underlying transport. - */ - @RequiresNoPermission - public int getMaxReceivePacketSize() { - return mMaxRxPacketSize; - } - - /** - * Get the type of the underlying connection. - * - * @return one of {@link #TYPE_RFCOMM}, {@link #TYPE_SCO} or {@link #TYPE_L2CAP} - */ - @RequiresNoPermission - public int getConnectionType() { - if (mType == TYPE_L2CAP_LE) { - // Treat the LE CoC to be the same type as L2CAP. - return TYPE_L2CAP; - } - return mType; - } - - /** - * Change if a SDP entry should be automatically created. - * Must be called before calling .bind, for the call to have any effect. - * - * @param excludeSdp <li>TRUE - do not auto generate SDP record. <li>FALSE - default - auto - * generate SPP SDP record. - * @hide - */ - @RequiresNoPermission - public void setExcludeSdp(boolean excludeSdp) { - mExcludeSdp = excludeSdp; - } - - /** - * Set the LE Transmit Data Length to be the maximum that the BT Controller is capable of. This - * parameter is used by the BT Controller to set the maximum transmission packet size on this - * connection. This function is currently used for testing only. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void requestMaximumTxDataLength() throws IOException { - if (mDevice == null) { - throw new IOException("requestMaximumTxDataLength is called on null device"); - } - - try { - if (mSocketState == SocketState.CLOSED) { - throw new IOException("socket closed"); - } - IBluetooth bluetoothProxy = - BluetoothAdapter.getDefaultAdapter().getBluetoothService(); - if (bluetoothProxy == null) { - throw new IOException("Bluetooth is off"); - } - - if (DBG) Log.d(TAG, "requestMaximumTxDataLength"); - bluetoothProxy.getSocketManager().requestMaximumTxDataLength(mDevice); - } catch (RemoteException e) { - Log.e(TAG, Log.getStackTraceString(new Throwable())); - throw new IOException("unable to send RPC: " + e.getMessage()); - } - } - - private String convertAddr(final byte[] addr) { - return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", - addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); - } - - private String waitSocketSignal(InputStream is) throws IOException { - byte[] sig = new byte[SOCK_SIGNAL_SIZE]; - int ret = readAll(is, sig); - if (VDBG) { - Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE + " bytes signal ret: " + ret); - } - ByteBuffer bb = ByteBuffer.wrap(sig); - /* the struct in native is decorated with __attribute__((packed)), hence this is possible */ - bb.order(ByteOrder.nativeOrder()); - int size = bb.getShort(); - if (size != SOCK_SIGNAL_SIZE) { - throw new IOException("Connection failure, wrong signal size: " + size); - } - byte[] addr = new byte[6]; - bb.get(addr); - int channel = bb.getInt(); - int status = bb.getInt(); - mMaxTxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value - mMaxRxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value - String RemoteAddr = convertAddr(addr); - if (VDBG) { - Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: " - + RemoteAddr + ", channel: " + channel + ", status: " + status - + " MaxRxPktSize: " + mMaxRxPacketSize + " MaxTxPktSize: " + mMaxTxPacketSize); - } - if (status != 0) { - throw new IOException("Connection failure, status: " + status); - } - return RemoteAddr; - } - - private void createL2capRxBuffer() { - if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { - // Allocate the buffer to use for reads. - if (VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize); - mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]); - if (VDBG) Log.v(TAG, "mL2capBuffer.remaining()" + mL2capBuffer.remaining()); - mL2capBuffer.limit(0); // Ensure we do a real read at the first read-request - if (VDBG) { - Log.v(TAG, "mL2capBuffer.remaining() after limit(0):" + mL2capBuffer.remaining()); - } - } - } - - private int readAll(InputStream is, byte[] b) throws IOException { - int left = b.length; - while (left > 0) { - int ret = is.read(b, b.length - left, left); - if (ret <= 0) { - throw new IOException("read failed, socket might closed or timeout, read ret: " - + ret); - } - left -= ret; - if (left != 0) { - Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) - + ", expect size: " + b.length); - } - } - return b.length; - } - - private int readInt(InputStream is) throws IOException { - byte[] ibytes = new byte[4]; - int ret = readAll(is, ibytes); - if (VDBG) Log.d(TAG, "inputStream.read ret: " + ret); - ByteBuffer bb = ByteBuffer.wrap(ibytes); - bb.order(ByteOrder.nativeOrder()); - return bb.getInt(); - } - - private int fillL2capRxBuffer() throws IOException { - mL2capBuffer.rewind(); - int ret = mSocketIS.read(mL2capBuffer.array()); - if (ret == -1) { - // reached end of stream - return -1 - mL2capBuffer.limit(0); - return -1; - } - mL2capBuffer.limit(ret); - return ret; - } - - -} diff --git a/core/java/android/bluetooth/BluetoothStatusCodes.java b/core/java/android/bluetooth/BluetoothStatusCodes.java deleted file mode 100644 index fff32fffd8a7..000000000000 --- a/core/java/android/bluetooth/BluetoothStatusCodes.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.SystemApi; - -/** - * A class with constants representing possible return values for Bluetooth APIs. General return - * values occupy the range 0 to 99. Profile-specific return values occupy the range 100-999. - * API-specific return values start at 1000. The exception to this is the "UNKNOWN" error code which - * occupies the max integer value. - */ -public final class BluetoothStatusCodes { - - private BluetoothStatusCodes() {} - - /** - * Indicates that the API call was successful - */ - public static final int SUCCESS = 0; - - /** - * Error code indicating that Bluetooth is not enabled - */ - public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; - - /** - * Error code indicating that the API call was initiated by neither the system nor the active - * user - */ - public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; - - /** - * Error code indicating that the Bluetooth Device specified is not bonded - */ - public static final int ERROR_DEVICE_NOT_BONDED = 3; - - /** - * Error code indicating that the Bluetooth Device specified is not connected, but is bonded - * - * @hide - */ - public static final int ERROR_DEVICE_NOT_CONNECTED = 4; - - /** - * Error code indicating that the caller does not have the - * {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission - * - * @hide - */ - public static final int ERROR_MISSING_BLUETOOTH_ADVERTISE_PERMISSION = 5; - - /** - * Error code indicating that the caller does not have the - * {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission - */ - public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; - - /** - * Error code indicating that the caller does not have the - * {@link android.Manifest.permission#BLUETOOTH_SCAN} permission - * - * @hide - */ - public static final int ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION = 7; - - /** - * Error code indicating that the caller does not have the - * {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission - */ - public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8; - - /** - * Error code indicating that the profile service is not bound. You can bind a profile service - * by calling {@link BluetoothAdapter#getProfileProxy} - */ - public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9; - - /** - * Error code indicating that the feature is not supported. - */ - public static final int ERROR_FEATURE_NOT_SUPPORTED = 10; - - /** - * A GATT writeCharacteristic request is not permitted on the remote device. - */ - public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 101; - - /** - * A GATT writeCharacteristic request is issued to a busy remote device. - */ - public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 102; - - /** - * If another application has already requested {@link OobData} then another fetch will be - * disallowed until the callback is removed. - * - * @hide - */ - @SystemApi - public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000; - - /** - * Indicates that the ACL disconnected due to an explicit request from the local device. - * <p> - * Example cause: This is a normal disconnect reason, e.g., user/app initiates - * disconnection. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_LOCAL_REQUEST = 1100; - - /** - * Indicates that the ACL disconnected due to an explicit request from the remote device. - * <p> - * Example cause: This is a normal disconnect reason, e.g., user/app initiates - * disconnection. - * <p> - * Example solution: The app can also prompt the user to check their remote device. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_REMOTE_REQUEST = 1101; - - /** - * Generic disconnect reason indicating the ACL disconnected due to an error on the local - * device. - * <p> - * Example solution: Prompt the user to check their local device (e.g., phone, car - * headunit). - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_LOCAL = 1102; - - /** - * Generic disconnect reason indicating the ACL disconnected due to an error on the remote - * device. - * <p> - * Example solution: Prompt the user to check their remote device (e.g., headset, car - * headunit, watch). - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_REMOTE = 1103; - - /** - * Indicates that the ACL disconnected due to a timeout. - * <p> - * Example cause: remote device might be out of range. - * <p> - * Example solution: Prompt user to verify their remote device is on or in - * connection/pairing mode. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_TIMEOUT = 1104; - - /** - * Indicates that the ACL disconnected due to link key issues. - * <p> - * Example cause: Devices are either unpaired or remote device is refusing our pairing - * request. - * <p> - * Example solution: Prompt user to unpair and pair again. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_SECURITY = 1105; - - /** - * Indicates that the ACL disconnected due to the local device's system policy. - * <p> - * Example cause: privacy policy, power management policy, permissions, etc. - * <p> - * Example solution: Prompt the user to check settings, or check with their system - * administrator (e.g. some corp-managed devices do not allow OPP connection). - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_SYSTEM_POLICY = 1106; - - /** - * Indicates that the ACL disconnected due to resource constraints, either on the local - * device or the remote device. - * <p> - * Example cause: controller is busy, memory limit reached, maximum number of connections - * reached. - * <p> - * Example solution: The app should wait and try again. If still failing, prompt the user - * to disconnect some devices, or toggle Bluetooth on the local and/or the remote device. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED = 1107; - - /** - * Indicates that the ACL disconnected because another ACL connection already exists. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS = 1108; - - /** - * Indicates that the ACL disconnected due to incorrect parameters passed in from the app. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_BAD_PARAMETERS = 1109; - - /** - * Indicates that setting the LE Audio Broadcast mode failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED = 1110; - - /** - * Indicates that setting a new encryption key for Bluetooth LE Audio Broadcast Source failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED = 1111; - - /** - * Indicates that connecting to a remote Broadcast Audio Scan Service failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_CONNECT_FAILED = 1112; - - /** - * Indicates that disconnecting from a remote Broadcast Audio Scan Service failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_DISCONNECT_FAILED = 1113; - - /** - * Indicates that enabling LE Audio Broadcast encryption failed - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED = 1114; - - /** - * Indicates that disabling LE Audio Broadcast encryption failed - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED = 1115; - - /** - * Indicates that an unknown error has occurred has occurred. - */ - public static final int ERROR_UNKNOWN = Integer.MAX_VALUE; -} diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java deleted file mode 100644 index 2a8ff5185085..000000000000 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.ParcelUuid; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.HashSet; -import java.util.UUID; - -/** - * Static helper methods and constants to decode the ParcelUuid of remote devices. - * - * @hide - */ -@SystemApi -@SuppressLint("AndroidFrameworkBluetoothPermission") -public final class BluetoothUuid { - - /* See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs - * for the various services. - * - * The following 128 bit values are calculated as: - * uuid * 2^96 + BASE_UUID - */ - - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid A2DP_SINK = - ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid A2DP_SOURCE = - ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid ADV_AUDIO_DIST = - ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HSP = - ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HSP_AG = - ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HFP = - ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HFP_AG = - ParcelUuid.fromString("0000111F-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid AVRCP_CONTROLLER = - ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid AVRCP_TARGET = - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid OBEX_OBJECT_PUSH = - ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HID = - ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HOGP = - ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid PANU = - ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid NAP = - ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid BNEP = - ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid PBAP_PCE = - ParcelUuid.fromString("0000112e-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid PBAP_PSE = - ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid MAP = - ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid MNS = - ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid MAS = - ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid SAP = - ParcelUuid.fromString("0000112D-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HEARING_AID = - ParcelUuid.fromString("0000FDF0-0000-1000-8000-00805f9b34fb"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid LE_AUDIO = - ParcelUuid.fromString("0000184E-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid DIP = - ParcelUuid.fromString("00001200-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid VOLUME_CONTROL = - ParcelUuid.fromString("00001844-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid GENERIC_MEDIA_CONTROL = - ParcelUuid.fromString("00001849-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid MEDIA_CONTROL = - ParcelUuid.fromString("00001848-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid COORDINATED_SET = - ParcelUuid.fromString("00001846-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid CAP = - ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid BASE_UUID = - ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); - - /** - * Length of bytes for 16 bit UUID - * - * @hide - */ - @SystemApi - public static final int UUID_BYTES_16_BIT = 2; - /** - * Length of bytes for 32 bit UUID - * - * @hide - */ - @SystemApi - public static final int UUID_BYTES_32_BIT = 4; - /** - * Length of bytes for 128 bit UUID - * - * @hide - */ - @SystemApi - public static final int UUID_BYTES_128_BIT = 16; - - /** - * Returns true if there any common ParcelUuids in uuidA and uuidB. - * - * @param uuidA - List of ParcelUuids - * @param uuidB - List of ParcelUuids - * - * @hide - */ - @SystemApi - public static boolean containsAnyUuid(@Nullable ParcelUuid[] uuidA, - @Nullable ParcelUuid[] uuidB) { - if (uuidA == null && uuidB == null) return true; - - if (uuidA == null) { - return uuidB.length == 0; - } - - if (uuidB == null) { - return uuidA.length == 0; - } - - HashSet<ParcelUuid> uuidSet = new HashSet<ParcelUuid>(Arrays.asList(uuidA)); - for (ParcelUuid uuid : uuidB) { - if (uuidSet.contains(uuid)) return true; - } - return false; - } - - /** - * Extract the Service Identifier or the actual uuid from the Parcel Uuid. - * For example, if 0000110B-0000-1000-8000-00805F9B34FB is the parcel Uuid, - * this function will return 110B - * - * @param parcelUuid - * @return the service identifier. - */ - private static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) { - UUID uuid = parcelUuid.getUuid(); - long value = (uuid.getMostSignificantBits() & 0xFFFFFFFF00000000L) >>> 32; - return (int) value; - } - - /** - * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID, - * but the returned UUID is always in 128-bit format. - * Note UUID is little endian in Bluetooth. - * - * @param uuidBytes Byte representation of uuid. - * @return {@link ParcelUuid} parsed from bytes. - * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed. - * - * @hide - */ - @NonNull - @SystemApi - public static ParcelUuid parseUuidFrom(@Nullable byte[] uuidBytes) { - if (uuidBytes == null) { - throw new IllegalArgumentException("uuidBytes cannot be null"); - } - int length = uuidBytes.length; - if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT - && length != UUID_BYTES_128_BIT) { - throw new IllegalArgumentException("uuidBytes length invalid - " + length); - } - - // Construct a 128 bit UUID. - if (length == UUID_BYTES_128_BIT) { - ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); - long msb = buf.getLong(8); - long lsb = buf.getLong(0); - return new ParcelUuid(new UUID(msb, lsb)); - } - - // For 16 bit and 32 bit UUID we need to convert them to 128 bit value. - // 128_bit_value = uuid * 2^96 + BASE_UUID - long shortUuid; - if (length == UUID_BYTES_16_BIT) { - shortUuid = uuidBytes[0] & 0xFF; - shortUuid += (uuidBytes[1] & 0xFF) << 8; - } else { - shortUuid = uuidBytes[0] & 0xFF; - shortUuid += (uuidBytes[1] & 0xFF) << 8; - shortUuid += (uuidBytes[2] & 0xFF) << 16; - shortUuid += (uuidBytes[3] & 0xFF) << 24; - } - long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32); - long lsb = BASE_UUID.getUuid().getLeastSignificantBits(); - return new ParcelUuid(new UUID(msb, lsb)); - } - - /** - * Parse UUID to bytes. The returned value is shortest representation, a 16-bit, 32-bit or - * 128-bit UUID, Note returned value is little endian (Bluetooth). - * - * @param uuid uuid to parse. - * @return shortest representation of {@code uuid} as bytes. - * @throws IllegalArgumentException If the {@code uuid} is null. - * - * @hide - */ - public static byte[] uuidToBytes(ParcelUuid uuid) { - if (uuid == null) { - throw new IllegalArgumentException("uuid cannot be null"); - } - - if (is16BitUuid(uuid)) { - byte[] uuidBytes = new byte[UUID_BYTES_16_BIT]; - int uuidVal = getServiceIdentifierFromParcelUuid(uuid); - uuidBytes[0] = (byte) (uuidVal & 0xFF); - uuidBytes[1] = (byte) ((uuidVal & 0xFF00) >> 8); - return uuidBytes; - } - - if (is32BitUuid(uuid)) { - byte[] uuidBytes = new byte[UUID_BYTES_32_BIT]; - int uuidVal = getServiceIdentifierFromParcelUuid(uuid); - uuidBytes[0] = (byte) (uuidVal & 0xFF); - uuidBytes[1] = (byte) ((uuidVal & 0xFF00) >> 8); - uuidBytes[2] = (byte) ((uuidVal & 0xFF0000) >> 16); - uuidBytes[3] = (byte) ((uuidVal & 0xFF000000) >> 24); - return uuidBytes; - } - - // Construct a 128 bit UUID. - long msb = uuid.getUuid().getMostSignificantBits(); - long lsb = uuid.getUuid().getLeastSignificantBits(); - - byte[] uuidBytes = new byte[UUID_BYTES_128_BIT]; - ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); - buf.putLong(8, msb); - buf.putLong(0, lsb); - return uuidBytes; - } - - /** - * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid. - * - * @param parcelUuid - * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise. - * - * @hide - */ - @UnsupportedAppUsage - public static boolean is16BitUuid(ParcelUuid parcelUuid) { - UUID uuid = parcelUuid.getUuid(); - if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { - return false; - } - return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L); - } - - - /** - * Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid. - * - * @param parcelUuid - * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise. - * - * @hide - */ - @UnsupportedAppUsage - public static boolean is32BitUuid(ParcelUuid parcelUuid) { - UUID uuid = parcelUuid.getUuid(); - if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { - return false; - } - if (is16BitUuid(parcelUuid)) { - return false; - } - return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L); - } - - private BluetoothUuid() {} -} diff --git a/core/java/android/bluetooth/BluetoothVolumeControl.java b/core/java/android/bluetooth/BluetoothVolumeControl.java deleted file mode 100644 index 27532aabc3fc..000000000000 --- a/core/java/android/bluetooth/BluetoothVolumeControl.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Bluetooth Volume Control service. - * - * <p>BluetoothVolumeControl is a proxy object for controlling the Bluetooth VC - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothVolumeControl proxy object. - * @hide - */ -@SystemApi -public final class BluetoothVolumeControl implements BluetoothProfile, AutoCloseable { - private static final String TAG = "BluetoothVolumeControl"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - private CloseGuard mCloseGuard; - - /** - * Intent used to broadcast the change in connection state of the Volume Control - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * @hide - */ - @SystemApi - @SuppressLint("ActionValue") - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED"; - - private BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothVolumeControl> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.VOLUME_CONTROL, TAG, - IBluetoothVolumeControl.class.getName()) { - @Override - public IBluetoothVolumeControl getServiceInterface(IBinder service) { - return IBluetoothVolumeControl.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothVolumeControl proxy object for interacting with the local - * Bluetooth Volume Control service. - */ - /*package*/ BluetoothVolumeControl(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - mCloseGuard = new CloseGuard(); - mCloseGuard.open("close"); - } - - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - protected void finalize() { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - close(); - } - - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothVolumeControl getService() { return mProfileConnector.getService(); } - - /** - * Get the list of connected devices. Currently at most one. - * - * @return list of connected devices - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (DBG) log("getConnectedDevices()"); - final IBluetoothVolumeControl service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) log("getDevicesMatchingStates()"); - final IBluetoothVolumeControl service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) log("getConnectionState(" + device + ")"); - final IBluetoothVolumeControl service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Tells remote device to set an absolute volume. - * - * @param volume Absolute volume to be set on remote device. - * Minimum value is 0 and maximum value is 255 - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public void setVolume(@Nullable BluetoothDevice device, - @IntRange(from = 0, to = 255) int volume) { - if (DBG) log("setVolume(" + volume + ")"); - final IBluetoothVolumeControl service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setVolume(device, volume, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothVolumeControl service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothVolumeControl service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(@Nullable BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BufferConstraint.java b/core/java/android/bluetooth/BufferConstraint.java deleted file mode 100644 index cbffc788c35d..000000000000 --- a/core/java/android/bluetooth/BufferConstraint.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Stores a codec's constraints on buffering length in milliseconds. - * - * {@hide} - */ -@SystemApi -public final class BufferConstraint implements Parcelable { - - private static final String TAG = "BufferConstraint"; - private int mDefaultMillis; - private int mMaxMillis; - private int mMinMillis; - - public BufferConstraint(int defaultMillis, int maxMillis, - int minMillis) { - mDefaultMillis = defaultMillis; - mMaxMillis = maxMillis; - mMinMillis = minMillis; - } - - BufferConstraint(Parcel in) { - mDefaultMillis = in.readInt(); - mMaxMillis = in.readInt(); - mMinMillis = in.readInt(); - } - - public static final @NonNull Parcelable.Creator<BufferConstraint> CREATOR = - new Parcelable.Creator<BufferConstraint>() { - public BufferConstraint createFromParcel(Parcel in) { - return new BufferConstraint(in); - } - - public BufferConstraint[] newArray(int size) { - return new BufferConstraint[size]; - } - }; - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeInt(mDefaultMillis); - out.writeInt(mMaxMillis); - out.writeInt(mMinMillis); - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Get the default buffer millis - * - * @return default buffer millis - * @hide - */ - @SystemApi - public int getDefaultMillis() { - return mDefaultMillis; - } - - /** - * Get the maximum buffer millis - * - * @return maximum buffer millis - * @hide - */ - @SystemApi - public int getMaxMillis() { - return mMaxMillis; - } - - /** - * Get the minimum buffer millis - * - * @return minimum buffer millis - * @hide - */ - @SystemApi - public int getMinMillis() { - return mMinMillis; - } -} diff --git a/core/java/android/bluetooth/BufferConstraints.java b/core/java/android/bluetooth/BufferConstraints.java deleted file mode 100644 index 06b45ee5c8b5..000000000000 --- a/core/java/android/bluetooth/BufferConstraints.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - -/** - * A parcelable collection of buffer constraints by codec type. - * - * {@hide} - */ -@SystemApi -public final class BufferConstraints implements Parcelable { - public static final int BUFFER_CODEC_MAX_NUM = 32; - - private static final String TAG = "BufferConstraints"; - - private Map<Integer, BufferConstraint> mBufferConstraints; - private List<BufferConstraint> mBufferConstraintList; - - public BufferConstraints(@NonNull List<BufferConstraint> - bufferConstraintList) { - - mBufferConstraintList = new ArrayList<BufferConstraint>(bufferConstraintList); - mBufferConstraints = new HashMap<Integer, BufferConstraint>(); - for (int i = 0; i < BUFFER_CODEC_MAX_NUM; i++) { - mBufferConstraints.put(i, bufferConstraintList.get(i)); - } - } - - BufferConstraints(Parcel in) { - mBufferConstraintList = new ArrayList<BufferConstraint>(); - mBufferConstraints = new HashMap<Integer, BufferConstraint>(); - in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader(), android.bluetooth.BufferConstraint.class); - for (int i = 0; i < mBufferConstraintList.size(); i++) { - mBufferConstraints.put(i, mBufferConstraintList.get(i)); - } - } - - public static final @NonNull Parcelable.Creator<BufferConstraints> CREATOR = - new Parcelable.Creator<BufferConstraints>() { - public BufferConstraints createFromParcel(Parcel in) { - return new BufferConstraints(in); - } - - public BufferConstraints[] newArray(int size) { - return new BufferConstraints[size]; - } - }; - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeList(mBufferConstraintList); - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Get the buffer constraints by codec type. - * - * @param codec Audio codec - * @return buffer constraints by codec type. - * @hide - */ - @SystemApi - public @Nullable BufferConstraint forCodec(@BluetoothCodecConfig.SourceCodecType int codec) { - return mBufferConstraints.get(codec); - } -} diff --git a/core/java/android/bluetooth/OWNERS b/core/java/android/bluetooth/OWNERS deleted file mode 100644 index fbee57773173..000000000000 --- a/core/java/android/bluetooth/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# Bug component: 27441 - -rahulsabnis@google.com -sattiraju@google.com -siyuanh@google.com -zachoverflow@google.com diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java deleted file mode 100644 index bb0b95649b17..000000000000 --- a/core/java/android/bluetooth/OobData.java +++ /dev/null @@ -1,958 +0,0 @@ -/** - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import static java.util.Objects.requireNonNull; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Out Of Band Data for Bluetooth device pairing. - * - * <p>This object represents optional data obtained from a remote device through - * an out-of-band channel (eg. NFC, QR). - * - * <p>References: - * NFC AD Forum SSP 1.1 (AD) - * {@link https://members.nfc-forum.org//apps/group_public/download.php/24620/NFCForum-AD-BTSSP_1_1.pdf} - * Core Specification Supplement (CSS) V9 - * - * <p>There are several BR/EDR Examples - * - * <p>Negotiated Handover: - * Bluetooth Carrier Configuration Record: - * - OOB Data Length - * - Device Address - * - Class of Device - * - Simple Pairing Hash C - * - Simple Pairing Randomizer R - * - Service Class UUID - * - Bluetooth Local Name - * - * <p>Static Handover: - * Bluetooth Carrier Configuration Record: - * - OOB Data Length - * - Device Address - * - Class of Device - * - Service Class UUID - * - Bluetooth Local Name - * - * <p>Simplified Tag Format for Single BT Carrier: - * Bluetooth OOB Data Record: - * - OOB Data Length - * - Device Address - * - Class of Device - * - Service Class UUID - * - Bluetooth Local Name - * - * @hide - */ -@SystemApi -public final class OobData implements Parcelable { - - private static final String TAG = "OobData"; - /** The {@link OobData#mClassicLength} may be. (AD 3.1.1) (CSS 1.6.2) @hide */ - @SystemApi - public static final int OOB_LENGTH_OCTETS = 2; - /** - * The length for the {@link OobData#mDeviceAddressWithType}(6) and Address Type(1). - * (AD 3.1.2) (CSS 1.6.2) - * @hide - */ - @SystemApi - public static final int DEVICE_ADDRESS_OCTETS = 7; - /** The Class of Device is 3 octets. (AD 3.1.3) (CSS 1.6.2) @hide */ - @SystemApi - public static final int CLASS_OF_DEVICE_OCTETS = 3; - /** The Confirmation data must be 16 octets. (AD 3.2.2) (CSS 1.6.2) @hide */ - @SystemApi - public static final int CONFIRMATION_OCTETS = 16; - /** The Randomizer data must be 16 octets. (AD 3.2.3) (CSS 1.6.2) @hide */ - @SystemApi - public static final int RANDOMIZER_OCTETS = 16; - /** The LE Device Role length is 1 octet. (AD 3.3.2) (CSS 1.17) @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_OCTETS = 1; - /** The {@link OobData#mLeTemporaryKey} length. (3.4.1) @hide */ - @SystemApi - public static final int LE_TK_OCTETS = 16; - /** The {@link OobData#mLeAppearance} length. (3.4.1) @hide */ - @SystemApi - public static final int LE_APPEARANCE_OCTETS = 2; - /** The {@link OobData#mLeFlags} length. (3.4.1) @hide */ - @SystemApi - public static final int LE_DEVICE_FLAG_OCTETS = 1; // 1 octet to hold the 0-4 value. - - // Le Roles - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "LE_DEVICE_ROLE_" }, - value = { - LE_DEVICE_ROLE_PERIPHERAL_ONLY, - LE_DEVICE_ROLE_CENTRAL_ONLY, - LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL, - LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL - } - ) - public @interface LeRole {} - - /** @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0x00; - /** @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 0x01; - /** @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 0x02; - /** @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 0x03; - - // Le Flags - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "LE_FLAG_" }, - value = { - LE_FLAG_LIMITED_DISCOVERY_MODE, - LE_FLAG_GENERAL_DISCOVERY_MODE, - LE_FLAG_BREDR_NOT_SUPPORTED, - LE_FLAG_SIMULTANEOUS_CONTROLLER, - LE_FLAG_SIMULTANEOUS_HOST - } - ) - public @interface LeFlag {} - - /** @hide */ - @SystemApi - public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0x00; - /** @hide */ - @SystemApi - public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 0x01; - /** @hide */ - @SystemApi - public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 0x02; - /** @hide */ - @SystemApi - public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 0x03; - /** @hide */ - @SystemApi - public static final int LE_FLAG_SIMULTANEOUS_HOST = 0x04; - - /** - * Builds an {@link OobData} object and validates that the required combination - * of values are present to create the LE specific OobData type. - * - * @hide - */ - @SystemApi - public static final class LeBuilder { - - /** - * It is recommended that this Hash C is generated anew for each - * pairing. - * - * <p>It should be noted that on passive NFC this isn't possible as the data is static - * and immutable. - */ - private byte[] mConfirmationHash = null; - - /** - * Optional, but adds more validity to the pairing. - * - * <p>If not present a value of 0 is assumed. - */ - private byte[] mRandomizerHash = new byte[] { - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - }; - - /** - * The Bluetooth Device user-friendly name presented over Bluetooth Technology. - * - * <p>This is the name that may be displayed to the device user as part of the UI. - */ - private byte[] mDeviceName = null; - - /** - * Sets the Bluetooth Device name to be used for UI purposes. - * - * <p>Optional attribute. - * - * @param deviceName byte array representing the name, may be 0 in length, not null. - * - * @return {@link OobData#ClassicBuilder} - * - * @throws NullPointerException if deviceName is null. - * - * @hide - */ - @NonNull - @SystemApi - public LeBuilder setDeviceName(@NonNull byte[] deviceName) { - requireNonNull(deviceName); - this.mDeviceName = deviceName; - return this; - } - - /** - * The Bluetooth Device Address is the address to which the OOB data belongs. - * - * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets. - * - * <p> Address is encoded in Little Endian order. - * - * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00 - */ - private final byte[] mDeviceAddressWithType; - - /** - * During an LE connection establishment, one must be in the Peripheral mode and the other - * in the Central role. - * - * <p>Possible Values: - * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported - * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported - * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported; - * Peripheral Preferred - * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred - * 0x04 - 0xFF Reserved - */ - private final @LeRole int mLeDeviceRole; - - /** - * Temporary key value from the Security Manager. - * - * <p> Must be {@link LE_TK_OCTETS} in size - */ - private byte[] mLeTemporaryKey = null; - - /** - * Defines the representation of the external appearance of the device. - * - * <p>For example, a mouse, remote control, or keyboard. - * - * <p>Used for visual on discovering device to represent icon/string/etc... - */ - private byte[] mLeAppearance = null; - - /** - * Contains which discoverable mode to use, BR/EDR support and capability. - * - * <p>Possible LE Flags: - * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode. - * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode. - * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of - * LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to - * Same Device Capable (Controller). - * Bit 49 of LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to - * Same Device Capable (Host). - * Bit 55 of LMP Feature Mask Definitions. - * <b>0x05- 0x07 Reserved</b> - */ - private @LeFlag int mLeFlags = LE_FLAG_GENERAL_DISCOVERY_MODE; // Invalid default - - /** - * Main creation method for creating a LE version of {@link OobData}. - * - * <p>This object will allow the caller to call {@link LeBuilder#build()} - * to build the data object or add any option information to the builder. - * - * @param deviceAddressWithType the LE device address plus the address type (7 octets); - * not null. - * @param leDeviceRole whether the device supports Peripheral, Central, - * Both including preference; not null. (1 octet) - * @param confirmationHash Array consisting of {@link OobData#CONFIRMATION_OCTETS} octets - * of data. Data is derived from controller/host stack and is - * required for pairing OOB. - * - * <p>Possible Values: - * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported - * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported - * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported; - * Peripheral Preferred - * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred - * 0x04 - 0xFF Reserved - * - * @throws IllegalArgumentException if any of the values fail to be set. - * @throws NullPointerException if any argument is null. - * - * @hide - */ - @SystemApi - public LeBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] deviceAddressWithType, - @LeRole int leDeviceRole) { - requireNonNull(confirmationHash); - requireNonNull(deviceAddressWithType); - if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) { - throw new IllegalArgumentException("confirmationHash must be " - + OobData.CONFIRMATION_OCTETS + " octets in length."); - } - this.mConfirmationHash = confirmationHash; - if (deviceAddressWithType.length != OobData.DEVICE_ADDRESS_OCTETS) { - throw new IllegalArgumentException("confirmationHash must be " - + OobData.DEVICE_ADDRESS_OCTETS+ " octets in length."); - } - this.mDeviceAddressWithType = deviceAddressWithType; - if (leDeviceRole < LE_DEVICE_ROLE_PERIPHERAL_ONLY - || leDeviceRole > LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL) { - throw new IllegalArgumentException("leDeviceRole must be a valid value."); - } - this.mLeDeviceRole = leDeviceRole; - } - - /** - * Sets the Temporary Key value to be used by the LE Security Manager during - * LE pairing. - * - * @param leTemporaryKey byte array that shall be 16 bytes. Please see Bluetooth CSSv6, - * Part A 1.8 for a detailed description. - * - * @return {@link OobData#Builder} - * - * @throws IllegalArgumentException if the leTemporaryKey is an invalid format. - * @throws NullinterException if leTemporaryKey is null. - * - * @hide - */ - @NonNull - @SystemApi - public LeBuilder setLeTemporaryKey(@NonNull byte[] leTemporaryKey) { - requireNonNull(leTemporaryKey); - if (leTemporaryKey.length != LE_TK_OCTETS) { - throw new IllegalArgumentException("leTemporaryKey must be " - + LE_TK_OCTETS + " octets in length."); - } - this.mLeTemporaryKey = leTemporaryKey; - return this; - } - - /** - * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets - * of data. Data is derived from controller/host stack and is required for pairing OOB. - * Also, randomizerHash may be all 0s or null in which case it becomes all 0s. - * - * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed. - * @throws NullPointerException if randomizerHash is null. - * - * @hide - */ - @NonNull - @SystemApi - public LeBuilder setRandomizerHash(@NonNull byte[] randomizerHash) { - requireNonNull(randomizerHash); - if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) { - throw new IllegalArgumentException("randomizerHash must be " - + OobData.RANDOMIZER_OCTETS + " octets in length."); - } - this.mRandomizerHash = randomizerHash; - return this; - } - - /** - * Sets the LE Flags necessary for the pairing scenario or discovery mode. - * - * @param leFlags enum value representing the 1 octet of data about discovery modes. - * - * <p>Possible LE Flags: - * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode. - * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode. - * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of - * LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to - * Same Device Capable (Controller) Bit 49 of LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to - * Same Device Capable (Host). - * Bit 55 of LMP Feature Mask Definitions. - * 0x05- 0x07 Reserved - * - * @throws IllegalArgumentException for invalid flag - * @hide - */ - @NonNull - @SystemApi - public LeBuilder setLeFlags(@LeFlag int leFlags) { - if (leFlags < LE_FLAG_LIMITED_DISCOVERY_MODE || leFlags > LE_FLAG_SIMULTANEOUS_HOST) { - throw new IllegalArgumentException("leFlags must be a valid value."); - } - this.mLeFlags = leFlags; - return this; - } - - /** - * Validates and builds the {@link OobData} object for LE Security. - * - * @return {@link OobData} with given builder values - * - * @throws IllegalStateException if either of the 2 required fields were not set. - * - * @hide - */ - @NonNull - @SystemApi - public OobData build() { - final OobData oob = - new OobData(this.mDeviceAddressWithType, this.mLeDeviceRole, - this.mConfirmationHash); - - // If we have values, set them, otherwise use default - oob.mLeTemporaryKey = - (this.mLeTemporaryKey != null) ? this.mLeTemporaryKey : oob.mLeTemporaryKey; - oob.mLeAppearance = (this.mLeAppearance != null) - ? this.mLeAppearance : oob.mLeAppearance; - oob.mLeFlags = (this.mLeFlags != 0xF) ? this.mLeFlags : oob.mLeFlags; - oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName; - oob.mRandomizerHash = this.mRandomizerHash; - return oob; - } - } - - /** - * Builds an {@link OobData} object and validates that the required combination - * of values are present to create the Classic specific OobData type. - * - * @hide - */ - @SystemApi - public static final class ClassicBuilder { - // Used by both Classic and LE - /** - * It is recommended that this Hash C is generated anew for each - * pairing. - * - * <p>It should be noted that on passive NFC this isn't possible as the data is static - * and immutable. - * - * @hide - */ - private byte[] mConfirmationHash = null; - - /** - * Optional, but adds more validity to the pairing. - * - * <p>If not present a value of 0 is assumed. - * - * @hide - */ - private byte[] mRandomizerHash = new byte[] { - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - }; - - /** - * The Bluetooth Device user-friendly name presented over Bluetooth Technology. - * - * <p>This is the name that may be displayed to the device user as part of the UI. - * - * @hide - */ - private byte[] mDeviceName = null; - - /** - * This length value provides the absolute length of total OOB data block used for - * Bluetooth BR/EDR - * - * <p>OOB communication, which includes the length field itself and the Bluetooth - * Device Address. - * - * <p>The minimum length that may be represented in this field is 8. - * - * @hide - */ - private final byte[] mClassicLength; - - /** - * The Bluetooth Device Address is the address to which the OOB data belongs. - * - * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets. - * - * <p> Address is encoded in Little Endian order. - * - * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00 - * - * @hide - */ - private final byte[] mDeviceAddressWithType; - - /** - * Class of Device information is to be used to provide a graphical representation - * to the user as part of UI involving operations. - * - * <p>This is not to be used to determine a particular service can be used. - * - * <p>The length MUST be {@link OobData#CLASS_OF_DEVICE_OCTETS} octets. - * - * @hide - */ - private byte[] mClassOfDevice = null; - - /** - * Main creation method for creating a Classic version of {@link OobData}. - * - * <p>This object will allow the caller to call {@link ClassicBuilder#build()} - * to build the data object or add any option information to the builder. - * - * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS} - * octets of data. Data is derived from controller/host stack and is required for pairing - * OOB. - * @param classicLength byte array representing the length of data from 8-65535 across 2 - * octets (0xXXXX). - * @param deviceAddressWithType byte array representing the Bluetooth Address of the device - * that owns the OOB data. (i.e. the originator) [6 octets] - * - * @throws IllegalArgumentException if any of the values fail to be set. - * @throws NullPointerException if any argument is null. - * - * @hide - */ - @SystemApi - public ClassicBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] classicLength, - @NonNull byte[] deviceAddressWithType) { - requireNonNull(confirmationHash); - requireNonNull(classicLength); - requireNonNull(deviceAddressWithType); - if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) { - throw new IllegalArgumentException("confirmationHash must be " - + OobData.CONFIRMATION_OCTETS + " octets in length."); - } - this.mConfirmationHash = confirmationHash; - if (classicLength.length != OOB_LENGTH_OCTETS) { - throw new IllegalArgumentException("classicLength must be " - + OOB_LENGTH_OCTETS + " octets in length."); - } - this.mClassicLength = classicLength; - if (deviceAddressWithType.length != DEVICE_ADDRESS_OCTETS) { - throw new IllegalArgumentException("deviceAddressWithType must be " - + DEVICE_ADDRESS_OCTETS + " octets in length."); - } - this.mDeviceAddressWithType = deviceAddressWithType; - } - - /** - * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets - * of data. Data is derived from controller/host stack and is required for pairing OOB. - * Also, randomizerHash may be all 0s or null in which case it becomes all 0s. - * - * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed. - * @throws NullPointerException if randomizerHash is null. - * - * @hide - */ - @NonNull - @SystemApi - public ClassicBuilder setRandomizerHash(@NonNull byte[] randomizerHash) { - requireNonNull(randomizerHash); - if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) { - throw new IllegalArgumentException("randomizerHash must be " - + OobData.RANDOMIZER_OCTETS + " octets in length."); - } - this.mRandomizerHash = randomizerHash; - return this; - } - - /** - * Sets the Bluetooth Device name to be used for UI purposes. - * - * <p>Optional attribute. - * - * @param deviceName byte array representing the name, may be 0 in length, not null. - * - * @return {@link OobData#ClassicBuilder} - * - * @throws NullPointerException if deviceName is null - * - * @hide - */ - @NonNull - @SystemApi - public ClassicBuilder setDeviceName(@NonNull byte[] deviceName) { - requireNonNull(deviceName); - this.mDeviceName = deviceName; - return this; - } - - /** - * Sets the Bluetooth Class of Device; used for UI purposes only. - * - * <p>Not an indicator of available services! - * - * <p>Optional attribute. - * - * @param classOfDevice byte array of {@link OobData#CLASS_OF_DEVICE_OCTETS} octets. - * - * @return {@link OobData#ClassicBuilder} - * - * @throws IllegalArgumentException if length is not equal to - * {@link OobData#CLASS_OF_DEVICE_OCTETS} octets. - * @throws NullPointerException if classOfDevice is null. - * - * @hide - */ - @NonNull - @SystemApi - public ClassicBuilder setClassOfDevice(@NonNull byte[] classOfDevice) { - requireNonNull(classOfDevice); - if (classOfDevice.length != OobData.CLASS_OF_DEVICE_OCTETS) { - throw new IllegalArgumentException("classOfDevice must be " - + OobData.CLASS_OF_DEVICE_OCTETS + " octets in length."); - } - this.mClassOfDevice = classOfDevice; - return this; - } - - /** - * Validates and builds the {@link OobDat object for Classic Security. - * - * @return {@link OobData} with previously given builder values. - * - * @hide - */ - @NonNull - @SystemApi - public OobData build() { - final OobData oob = - new OobData(this.mClassicLength, this.mDeviceAddressWithType, - this.mConfirmationHash); - // If we have values, set them, otherwise use default - oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName; - oob.mClassOfDevice = (this.mClassOfDevice != null) - ? this.mClassOfDevice : oob.mClassOfDevice; - oob.mRandomizerHash = this.mRandomizerHash; - return oob; - } - } - - // Members (Defaults for Optionals must be set or Parceling fails on NPE) - // Both - private final byte[] mDeviceAddressWithType; - private final byte[] mConfirmationHash; - private byte[] mRandomizerHash = new byte[] { - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - }; - // Default the name to "Bluetooth Device" - private byte[] mDeviceName = new byte[] { - // Bluetooth - 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68, - // <space>Device - 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65 - }; - - // Classic - private final byte[] mClassicLength; - private byte[] mClassOfDevice = new byte[CLASS_OF_DEVICE_OCTETS]; - - // LE - private final @LeRole int mLeDeviceRole; - private byte[] mLeTemporaryKey = new byte[LE_TK_OCTETS]; - private byte[] mLeAppearance = new byte[LE_APPEARANCE_OCTETS]; - private @LeFlag int mLeFlags = LE_FLAG_LIMITED_DISCOVERY_MODE; - - /** - * @return byte array representing the MAC address of a bluetooth device. - * The Address is 6 octets long with a 1 octet address type associated with the address. - * - * <p>For classic this will be 6 byte address plus the default of PUBLIC_ADDRESS Address Type. - * For LE there are more choices for Address Type. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getDeviceAddressWithType() { - return mDeviceAddressWithType; - } - - /** - * @return byte array representing the confirmationHash value - * which is used to confirm the identity to the controller. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getConfirmationHash() { - return mConfirmationHash; - } - - /** - * @return byte array representing the randomizerHash value - * which is used to verify the identity of the controller. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getRandomizerHash() { - return mRandomizerHash; - } - - /** - * @return Device Name used for displaying name in UI. - * - * <p>Also, this will be populated with the LE Local Name if the data is for LE. - * - * @hide - */ - @Nullable - @SystemApi - public byte[] getDeviceName() { - return mDeviceName; - } - - /** - * @return byte array representing the oob data length which is the length - * of all of the data including these octets. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getClassicLength() { - return mClassicLength; - } - - /** - * @return byte array representing the class of device for UI display. - * - * <p>Does not indicate services available; for display only. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getClassOfDevice() { - return mClassOfDevice; - } - - /** - * @return Temporary Key used for LE pairing. - * - * @hide - */ - @Nullable - @SystemApi - public byte[] getLeTemporaryKey() { - return mLeTemporaryKey; - } - - /** - * @return Appearance used for LE pairing. For use in UI situations - * when determining what sort of icons or text to display regarding - * the device. - * - * @hide - */ - @Nullable - @SystemApi - public byte[] getLeAppearance() { - return mLeAppearance; - } - - /** - * @return Flags used to determing discoverable mode to use, BR/EDR Support, and Capability. - * - * <p>Possible LE Flags: - * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode. - * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode. - * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of - * LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to - * Same Device Capable (Controller). - * Bit 49 of LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to - * Same Device Capable (Host). - * Bit 55 of LMP Feature Mask Definitions. - * <b>0x05- 0x07 Reserved</b> - * - * @hide - */ - @NonNull - @SystemApi - @LeFlag - public int getLeFlags() { - return mLeFlags; - } - - /** - * @return the supported and preferred roles of the LE device. - * - * <p>Possible Values: - * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported - * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported - * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported; - * Peripheral Preferred - * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred - * 0x04 - 0xFF Reserved - * - * @hide - */ - @NonNull - @SystemApi - @LeRole - public int getLeDeviceRole() { - return mLeDeviceRole; - } - - /** - * Classic Security Constructor - */ - private OobData(@NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType, - @NonNull byte[] confirmationHash) { - mClassicLength = classicLength; - mDeviceAddressWithType = deviceAddressWithType; - mConfirmationHash = confirmationHash; - mLeDeviceRole = -1; // Satisfy final - } - - /** - * LE Security Constructor - */ - private OobData(@NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole, - @NonNull byte[] confirmationHash) { - mDeviceAddressWithType = deviceAddressWithType; - mLeDeviceRole = leDeviceRole; - mConfirmationHash = confirmationHash; - mClassicLength = new byte[OOB_LENGTH_OCTETS]; // Satisfy final - } - - private OobData(Parcel in) { - // Both - mDeviceAddressWithType = in.createByteArray(); - mConfirmationHash = in.createByteArray(); - mRandomizerHash = in.createByteArray(); - mDeviceName = in.createByteArray(); - - // Classic - mClassicLength = in.createByteArray(); - mClassOfDevice = in.createByteArray(); - - // LE - mLeDeviceRole = in.readInt(); - mLeTemporaryKey = in.createByteArray(); - mLeAppearance = in.createByteArray(); - mLeFlags = in.readInt(); - } - - /** - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - /** - * @hide - */ - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - // Both - // Required - out.writeByteArray(mDeviceAddressWithType); - // Required - out.writeByteArray(mConfirmationHash); - // Optional - out.writeByteArray(mRandomizerHash); - // Optional - out.writeByteArray(mDeviceName); - - // Classic - // Required - out.writeByteArray(mClassicLength); - // Optional - out.writeByteArray(mClassOfDevice); - - // LE - // Required - out.writeInt(mLeDeviceRole); - // Required - out.writeByteArray(mLeTemporaryKey); - // Optional - out.writeByteArray(mLeAppearance); - // Optional - out.writeInt(mLeFlags); - } - - // For Parcelable - public static final @android.annotation.NonNull Parcelable.Creator<OobData> CREATOR = - new Parcelable.Creator<OobData>() { - public OobData createFromParcel(Parcel in) { - return new OobData(in); - } - - public OobData[] newArray(int size) { - return new OobData[size]; - } - }; - - /** - * @return a {@link String} representation of the OobData object. - * - * @hide - */ - @Override - @NonNull - public String toString() { - return "OobData: \n\t" - // Both - + "Device Address With Type: " + toHexString(mDeviceAddressWithType) + "\n\t" - + "Confirmation: " + toHexString(mConfirmationHash) + "\n\t" - + "Randomizer: " + toHexString(mRandomizerHash) + "\n\t" - + "Device Name: " + toHexString(mDeviceName) + "\n\t" - // Classic - + "OobData Length: " + toHexString(mClassicLength) + "\n\t" - + "Class of Device: " + toHexString(mClassOfDevice) + "\n\t" - // LE - + "LE Device Role: " + toHexString(mLeDeviceRole) + "\n\t" - + "LE Temporary Key: " + toHexString(mLeTemporaryKey) + "\n\t" - + "LE Appearance: " + toHexString(mLeAppearance) + "\n\t" - + "LE Flags: " + toHexString(mLeFlags) + "\n\t"; - } - - @NonNull - private String toHexString(int b) { - return toHexString(new byte[] {(byte) b}); - } - - @NonNull - private String toHexString(byte b) { - return toHexString(new byte[] {b}); - } - - @NonNull - private String toHexString(byte[] array) { - if (array == null) return "null"; - StringBuilder builder = new StringBuilder(array.length * 2); - for (byte b: array) { - builder.append(String.format("%02x", b)); - } - return builder.toString(); - } -} diff --git a/core/java/android/bluetooth/SdpDipRecord.java b/core/java/android/bluetooth/SdpDipRecord.java deleted file mode 100644 index 84b0eef0593e..000000000000 --- a/core/java/android/bluetooth/SdpDipRecord.java +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package android.bluetooth; - -import java.util.Arrays; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Data representation of a Object Push Profile Server side SDP record. - */ -/** @hide */ -public class SdpDipRecord implements Parcelable { - private final int mSpecificationId; - private final int mVendorId; - private final int mVendorIdSource; - private final int mProductId; - private final int mVersion; - private final boolean mPrimaryRecord; - - public SdpDipRecord(int specificationId, - int vendorId, int vendorIdSource, - int productId, int version, - boolean primaryRecord) { - super(); - this.mSpecificationId = specificationId; - this.mVendorId = vendorId; - this.mVendorIdSource = vendorIdSource; - this.mProductId = productId; - this.mVersion = version; - this.mPrimaryRecord = primaryRecord; - } - - public SdpDipRecord(Parcel in) { - this.mSpecificationId = in.readInt(); - this.mVendorId = in.readInt(); - this.mVendorIdSource = in.readInt(); - this.mProductId = in.readInt(); - this.mVersion = in.readInt(); - this.mPrimaryRecord = in.readBoolean(); - } - - public int getSpecificationId() { - return mSpecificationId; - } - - public int getVendorId() { - return mVendorId; - } - - public int getVendorIdSource() { - return mVendorIdSource; - } - - public int getProductId() { - return mProductId; - } - - public int getVersion() { - return mVersion; - } - - public boolean getPrimaryRecord() { - return mPrimaryRecord; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mSpecificationId); - dest.writeInt(mVendorId); - dest.writeInt(mVendorIdSource); - dest.writeInt(mProductId); - dest.writeInt(mVersion); - dest.writeBoolean(mPrimaryRecord); - } - - @Override - public int describeContents() { - /* No special objects */ - return 0; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpDipRecord createFromParcel(Parcel in) { - return new SdpDipRecord(in); - } - public SdpDipRecord[] newArray(int size) { - return new SdpDipRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/SdpMasRecord.java b/core/java/android/bluetooth/SdpMasRecord.java deleted file mode 100644 index 72d49380b713..000000000000 --- a/core/java/android/bluetooth/SdpMasRecord.java +++ /dev/null @@ -1,150 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public class SdpMasRecord implements Parcelable { - private final int mMasInstanceId; - private final int mL2capPsm; - private final int mRfcommChannelNumber; - private final int mProfileVersion; - private final int mSupportedFeatures; - private final int mSupportedMessageTypes; - private final String mServiceName; - - /** Message type */ - public static final class MessageType { - public static final int EMAIL = 0x01; - public static final int SMS_GSM = 0x02; - public static final int SMS_CDMA = 0x04; - public static final int MMS = 0x08; - } - - public SdpMasRecord(int masInstanceId, - int l2capPsm, - int rfcommChannelNumber, - int profileVersion, - int supportedFeatures, - int supportedMessageTypes, - String serviceName) { - mMasInstanceId = masInstanceId; - mL2capPsm = l2capPsm; - mRfcommChannelNumber = rfcommChannelNumber; - mProfileVersion = profileVersion; - mSupportedFeatures = supportedFeatures; - mSupportedMessageTypes = supportedMessageTypes; - mServiceName = serviceName; - } - - public SdpMasRecord(Parcel in) { - mMasInstanceId = in.readInt(); - mL2capPsm = in.readInt(); - mRfcommChannelNumber = in.readInt(); - mProfileVersion = in.readInt(); - mSupportedFeatures = in.readInt(); - mSupportedMessageTypes = in.readInt(); - mServiceName = in.readString(); - } - - @Override - public int describeContents() { - // TODO Auto-generated method stub - return 0; - } - - public int getMasInstanceId() { - return mMasInstanceId; - } - - public int getL2capPsm() { - return mL2capPsm; - } - - public int getRfcommCannelNumber() { - return mRfcommChannelNumber; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - public int getSupportedFeatures() { - return mSupportedFeatures; - } - - public int getSupportedMessageTypes() { - return mSupportedMessageTypes; - } - - public boolean msgSupported(int msg) { - return (mSupportedMessageTypes & msg) != 0; - } - - public String getServiceName() { - return mServiceName; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mMasInstanceId); - dest.writeInt(mL2capPsm); - dest.writeInt(mRfcommChannelNumber); - dest.writeInt(mProfileVersion); - dest.writeInt(mSupportedFeatures); - dest.writeInt(mSupportedMessageTypes); - dest.writeString(mServiceName); - } - - @Override - public String toString() { - String ret = "Bluetooth MAS SDP Record:\n"; - - if (mMasInstanceId != -1) { - ret += "Mas Instance Id: " + mMasInstanceId + "\n"; - } - if (mRfcommChannelNumber != -1) { - ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; - } - if (mL2capPsm != -1) { - ret += "L2CAP PSM: " + mL2capPsm + "\n"; - } - if (mServiceName != null) { - ret += "Service Name: " + mServiceName + "\n"; - } - if (mProfileVersion != -1) { - ret += "Profile version: " + mProfileVersion + "\n"; - } - if (mSupportedMessageTypes != -1) { - ret += "Supported msg types: " + mSupportedMessageTypes + "\n"; - } - if (mSupportedFeatures != -1) { - ret += "Supported features: " + mSupportedFeatures + "\n"; - } - return ret; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpMasRecord createFromParcel(Parcel in) { - return new SdpMasRecord(in); - } - - public SdpRecord[] newArray(int size) { - return new SdpRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/SdpMnsRecord.java b/core/java/android/bluetooth/SdpMnsRecord.java deleted file mode 100644 index a781d5df7dd0..000000000000 --- a/core/java/android/bluetooth/SdpMnsRecord.java +++ /dev/null @@ -1,114 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public class SdpMnsRecord implements Parcelable { - private final int mL2capPsm; - private final int mRfcommChannelNumber; - private final int mSupportedFeatures; - private final int mProfileVersion; - private final String mServiceName; - - public SdpMnsRecord(int l2capPsm, - int rfcommChannelNumber, - int profileVersion, - int supportedFeatures, - String serviceName) { - mL2capPsm = l2capPsm; - mRfcommChannelNumber = rfcommChannelNumber; - mSupportedFeatures = supportedFeatures; - mServiceName = serviceName; - mProfileVersion = profileVersion; - } - - public SdpMnsRecord(Parcel in) { - mRfcommChannelNumber = in.readInt(); - mL2capPsm = in.readInt(); - mServiceName = in.readString(); - mSupportedFeatures = in.readInt(); - mProfileVersion = in.readInt(); - } - - @Override - public int describeContents() { - // TODO Auto-generated method stub - return 0; - } - - - public int getL2capPsm() { - return mL2capPsm; - } - - public int getRfcommChannelNumber() { - return mRfcommChannelNumber; - } - - public int getSupportedFeatures() { - return mSupportedFeatures; - } - - public String getServiceName() { - return mServiceName; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRfcommChannelNumber); - dest.writeInt(mL2capPsm); - dest.writeString(mServiceName); - dest.writeInt(mSupportedFeatures); - dest.writeInt(mProfileVersion); - } - - public String toString() { - String ret = "Bluetooth MNS SDP Record:\n"; - - if (mRfcommChannelNumber != -1) { - ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; - } - if (mL2capPsm != -1) { - ret += "L2CAP PSM: " + mL2capPsm + "\n"; - } - if (mServiceName != null) { - ret += "Service Name: " + mServiceName + "\n"; - } - if (mSupportedFeatures != -1) { - ret += "Supported features: " + mSupportedFeatures + "\n"; - } - if (mProfileVersion != -1) { - ret += "Profile_version: " + mProfileVersion + "\n"; - } - return ret; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpMnsRecord createFromParcel(Parcel in) { - return new SdpMnsRecord(in); - } - - public SdpMnsRecord[] newArray(int size) { - return new SdpMnsRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/SdpOppOpsRecord.java b/core/java/android/bluetooth/SdpOppOpsRecord.java deleted file mode 100644 index e30745b89821..000000000000 --- a/core/java/android/bluetooth/SdpOppOpsRecord.java +++ /dev/null @@ -1,121 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Arrays; - -/** - * Data representation of a Object Push Profile Server side SDP record. - */ - -/** @hide */ -public class SdpOppOpsRecord implements Parcelable { - - private final String mServiceName; - private final int mRfcommChannel; - private final int mL2capPsm; - private final int mProfileVersion; - private final byte[] mFormatsList; - - public SdpOppOpsRecord(String serviceName, int rfcommChannel, - int l2capPsm, int version, byte[] formatsList) { - super(); - mServiceName = serviceName; - mRfcommChannel = rfcommChannel; - mL2capPsm = l2capPsm; - mProfileVersion = version; - mFormatsList = formatsList; - } - - public String getServiceName() { - return mServiceName; - } - - public int getRfcommChannel() { - return mRfcommChannel; - } - - public int getL2capPsm() { - return mL2capPsm; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - public byte[] getFormatsList() { - return mFormatsList; - } - - @Override - public int describeContents() { - /* No special objects */ - return 0; - } - - public SdpOppOpsRecord(Parcel in) { - mRfcommChannel = in.readInt(); - mL2capPsm = in.readInt(); - mProfileVersion = in.readInt(); - mServiceName = in.readString(); - int arrayLength = in.readInt(); - if (arrayLength > 0) { - byte[] bytes = new byte[arrayLength]; - in.readByteArray(bytes); - mFormatsList = bytes; - } else { - mFormatsList = null; - } - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRfcommChannel); - dest.writeInt(mL2capPsm); - dest.writeInt(mProfileVersion); - dest.writeString(mServiceName); - if (mFormatsList != null && mFormatsList.length > 0) { - dest.writeInt(mFormatsList.length); - dest.writeByteArray(mFormatsList); - } else { - dest.writeInt(0); - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Bluetooth OPP Server SDP Record:\n"); - sb.append(" RFCOMM Chan Number: ").append(mRfcommChannel); - sb.append("\n L2CAP PSM: ").append(mL2capPsm); - sb.append("\n Profile version: ").append(mProfileVersion); - sb.append("\n Service Name: ").append(mServiceName); - sb.append("\n Formats List: ").append(Arrays.toString(mFormatsList)); - return sb.toString(); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpOppOpsRecord createFromParcel(Parcel in) { - return new SdpOppOpsRecord(in); - } - - public SdpOppOpsRecord[] newArray(int size) { - return new SdpOppOpsRecord[size]; - } - }; - -} diff --git a/core/java/android/bluetooth/SdpPseRecord.java b/core/java/android/bluetooth/SdpPseRecord.java deleted file mode 100644 index 72249d0585c6..000000000000 --- a/core/java/android/bluetooth/SdpPseRecord.java +++ /dev/null @@ -1,129 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public class SdpPseRecord implements Parcelable { - private final int mL2capPsm; - private final int mRfcommChannelNumber; - private final int mProfileVersion; - private final int mSupportedFeatures; - private final int mSupportedRepositories; - private final String mServiceName; - - public SdpPseRecord(int l2capPsm, - int rfcommChannelNumber, - int profileVersion, - int supportedFeatures, - int supportedRepositories, - String serviceName) { - mL2capPsm = l2capPsm; - mRfcommChannelNumber = rfcommChannelNumber; - mProfileVersion = profileVersion; - mSupportedFeatures = supportedFeatures; - mSupportedRepositories = supportedRepositories; - mServiceName = serviceName; - } - - public SdpPseRecord(Parcel in) { - mRfcommChannelNumber = in.readInt(); - mL2capPsm = in.readInt(); - mProfileVersion = in.readInt(); - mSupportedFeatures = in.readInt(); - mSupportedRepositories = in.readInt(); - mServiceName = in.readString(); - } - - @Override - public int describeContents() { - // TODO Auto-generated method stub - return 0; - } - - public int getL2capPsm() { - return mL2capPsm; - } - - public int getRfcommChannelNumber() { - return mRfcommChannelNumber; - } - - public int getSupportedFeatures() { - return mSupportedFeatures; - } - - public String getServiceName() { - return mServiceName; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - public int getSupportedRepositories() { - return mSupportedRepositories; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRfcommChannelNumber); - dest.writeInt(mL2capPsm); - dest.writeInt(mProfileVersion); - dest.writeInt(mSupportedFeatures); - dest.writeInt(mSupportedRepositories); - dest.writeString(mServiceName); - - } - - @Override - public String toString() { - String ret = "Bluetooth MNS SDP Record:\n"; - - if (mRfcommChannelNumber != -1) { - ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; - } - if (mL2capPsm != -1) { - ret += "L2CAP PSM: " + mL2capPsm + "\n"; - } - if (mProfileVersion != -1) { - ret += "profile version: " + mProfileVersion + "\n"; - } - if (mServiceName != null) { - ret += "Service Name: " + mServiceName + "\n"; - } - if (mSupportedFeatures != -1) { - ret += "Supported features: " + mSupportedFeatures + "\n"; - } - if (mSupportedRepositories != -1) { - ret += "Supported repositories: " + mSupportedRepositories + "\n"; - } - - return ret; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpPseRecord createFromParcel(Parcel in) { - return new SdpPseRecord(in); - } - - public SdpPseRecord[] newArray(int size) { - return new SdpPseRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/SdpRecord.java b/core/java/android/bluetooth/SdpRecord.java deleted file mode 100644 index 730862ec6f91..000000000000 --- a/core/java/android/bluetooth/SdpRecord.java +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Arrays; - -/** @hide */ -public class SdpRecord implements Parcelable { - - private final byte[] mRawData; - private final int mRawSize; - - @Override - public String toString() { - return "BluetoothSdpRecord [rawData=" + Arrays.toString(mRawData) - + ", rawSize=" + mRawSize + "]"; - } - - public SdpRecord(int sizeRecord, byte[] record) { - mRawData = record; - mRawSize = sizeRecord; - } - - public SdpRecord(Parcel in) { - mRawSize = in.readInt(); - mRawData = new byte[mRawSize]; - in.readByteArray(mRawData); - - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRawSize); - dest.writeByteArray(mRawData); - - - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpRecord createFromParcel(Parcel in) { - return new SdpRecord(in); - } - - public SdpRecord[] newArray(int size) { - return new SdpRecord[size]; - } - }; - - public byte[] getRawData() { - return mRawData; - } - - public int getRawSize() { - return mRawSize; - } -} diff --git a/core/java/android/bluetooth/SdpSapsRecord.java b/core/java/android/bluetooth/SdpSapsRecord.java deleted file mode 100644 index a1e2f7b51f35..000000000000 --- a/core/java/android/bluetooth/SdpSapsRecord.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public class SdpSapsRecord implements Parcelable { - private final int mRfcommChannelNumber; - private final int mProfileVersion; - private final String mServiceName; - - public SdpSapsRecord(int rfcommChannelNumber, int profileVersion, String serviceName) { - mRfcommChannelNumber = rfcommChannelNumber; - mProfileVersion = profileVersion; - mServiceName = serviceName; - } - - public SdpSapsRecord(Parcel in) { - mRfcommChannelNumber = in.readInt(); - mProfileVersion = in.readInt(); - mServiceName = in.readString(); - } - - @Override - public int describeContents() { - return 0; - } - - public int getRfcommCannelNumber() { - return mRfcommChannelNumber; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - public String getServiceName() { - return mServiceName; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRfcommChannelNumber); - dest.writeInt(mProfileVersion); - dest.writeString(mServiceName); - - } - - @Override - public String toString() { - String ret = "Bluetooth MAS SDP Record:\n"; - - if (mRfcommChannelNumber != -1) { - ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; - } - if (mServiceName != null) { - ret += "Service Name: " + mServiceName + "\n"; - } - if (mProfileVersion != -1) { - ret += "Profile version: " + mProfileVersion + "\n"; - } - return ret; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpSapsRecord createFromParcel(Parcel in) { - return new SdpSapsRecord(in); - } - - public SdpRecord[] newArray(int size) { - return new SdpRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/UidTraffic.java b/core/java/android/bluetooth/UidTraffic.java deleted file mode 100644 index 9982fa6121e4..000000000000 --- a/core/java/android/bluetooth/UidTraffic.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.bluetooth; - -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Record of data traffic (in bytes) by an application identified by its UID. - * - * @hide - */ -@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) -public final class UidTraffic implements Cloneable, Parcelable { - private final int mAppUid; - private long mRxBytes; - private long mTxBytes; - - /** @hide */ - public UidTraffic(int appUid, long rx, long tx) { - mAppUid = appUid; - mRxBytes = rx; - mTxBytes = tx; - } - - /** @hide */ - private UidTraffic(Parcel in) { - mAppUid = in.readInt(); - mRxBytes = in.readLong(); - mTxBytes = in.readLong(); - } - - /** @hide */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAppUid); - dest.writeLong(mRxBytes); - dest.writeLong(mTxBytes); - } - - /** @hide */ - public void setRxBytes(long bytes) { - mRxBytes = bytes; - } - - /** @hide */ - public void setTxBytes(long bytes) { - mTxBytes = bytes; - } - - /** @hide */ - public void addRxBytes(long bytes) { - mRxBytes += bytes; - } - - /** @hide */ - public void addTxBytes(long bytes) { - mTxBytes += bytes; - } - - /** - * @return corresponding app Uid - */ - public int getUid() { - return mAppUid; - } - - /** - * @return rx bytes count - */ - public long getRxBytes() { - return mRxBytes; - } - - /** - * @return tx bytes count - */ - public long getTxBytes() { - return mTxBytes; - } - - /** @hide */ - @Override - public int describeContents() { - return 0; - } - - /** @hide */ - @Override - public UidTraffic clone() { - return new UidTraffic(mAppUid, mRxBytes, mTxBytes); - } - - /** @hide */ - @Override - public String toString() { - return "UidTraffic{mAppUid=" + mAppUid + ", mRxBytes=" + mRxBytes + ", mTxBytes=" - + mTxBytes + '}'; - } - - public static final @android.annotation.NonNull Creator<UidTraffic> CREATOR = new Creator<UidTraffic>() { - @Override - public UidTraffic createFromParcel(Parcel source) { - return new UidTraffic(source); - } - - @Override - public UidTraffic[] newArray(int size) { - return new UidTraffic[size]; - } - }; -} diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java deleted file mode 100644 index c508c2c9ca0b..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, - * this requires the {@link Manifest.permission#BLUETOOTH_ADVERTISE} - * permission which can be gained with - * {@link android.app.Activity#requestPermissions(String[], int)}. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresBluetoothAdvertisePermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java deleted file mode 100644 index e159eaafe2e4..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, - * this requires the {@link Manifest.permission#BLUETOOTH_CONNECT} - * permission which can be gained with - * {@link android.app.Activity#requestPermissions(String[], int)}. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresBluetoothConnectPermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java deleted file mode 100644 index 2bb320413941..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc In addition, this requires either the - * {@link Manifest.permission#ACCESS_FINE_LOCATION} - * permission or a strong assertion that you will never derive the - * physical location of the device. You can make this assertion by - * declaring {@code usesPermissionFlags="neverForLocation"} on the - * relevant {@code <uses-permission>} manifest tag, but it may - * restrict the types of Bluetooth devices you can interact with. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresBluetoothLocationPermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java deleted file mode 100644 index 800ff39933f2..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, - * this requires the {@link Manifest.permission#BLUETOOTH_SCAN} - * permission which can be gained with - * {@link android.app.Activity#requestPermissions(String[], int)}. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresBluetoothScanPermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java deleted file mode 100644 index 9adf695cde0f..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this - * requires the {@link Manifest.permission#BLUETOOTH_ADMIN} - * permission which can be gained with a simple - * {@code <uses-permission>} manifest tag. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresLegacyBluetoothAdminPermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java deleted file mode 100644 index 79621c366f59..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this - * requires the {@link Manifest.permission#BLUETOOTH} permission - * which can be gained with a simple {@code <uses-permission>} - * manifest tag. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresLegacyBluetoothPermission { -} diff --git a/core/java/android/bluetooth/le/AdvertiseCallback.java b/core/java/android/bluetooth/le/AdvertiseCallback.java deleted file mode 100644 index 4fa8c4f2f539..000000000000 --- a/core/java/android/bluetooth/le/AdvertiseCallback.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -/** - * Bluetooth LE advertising callbacks, used to deliver advertising operation status. - */ -public abstract class AdvertiseCallback { - - /** - * The requested operation was successful. - * - * @hide - */ - public static final int ADVERTISE_SUCCESS = 0; - - /** - * Failed to start advertising as the advertise data to be broadcasted is larger than 31 bytes. - */ - public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; - - /** - * Failed to start advertising because no advertising instance is available. - */ - public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; - - /** - * Failed to start advertising as the advertising is already started. - */ - public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; - - /** - * Operation failed due to an internal error. - */ - public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; - - /** - * This feature is not supported on this platform. - */ - public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; - - /** - * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertising} indicating - * that the advertising has been started successfully. - * - * @param settingsInEffect The actual settings used for advertising, which may be different from - * what has been requested. - */ - public void onStartSuccess(AdvertiseSettings settingsInEffect) { - } - - /** - * Callback when advertising could not be started. - * - * @param errorCode Error code (see ADVERTISE_FAILED_* constants) for advertising start - * failures. - */ - public void onStartFailure(int errorCode) { - } -} diff --git a/core/java/android/bluetooth/le/AdvertiseData.java b/core/java/android/bluetooth/le/AdvertiseData.java deleted file mode 100644 index fdf62ec3a647..000000000000 --- a/core/java/android/bluetooth/le/AdvertiseData.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.util.ArrayMap; -import android.util.SparseArray; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * Advertise data packet container for Bluetooth LE advertising. This represents the data to be - * advertised as well as the scan response data for active scans. - * <p> - * Use {@link AdvertiseData.Builder} to create an instance of {@link AdvertiseData} to be - * advertised. - * - * @see BluetoothLeAdvertiser - * @see ScanRecord - */ -public final class AdvertiseData implements Parcelable { - - @Nullable - private final List<ParcelUuid> mServiceUuids; - - @NonNull - private final List<ParcelUuid> mServiceSolicitationUuids; - - @Nullable - private final List<TransportDiscoveryData> mTransportDiscoveryData; - - private final SparseArray<byte[]> mManufacturerSpecificData; - private final Map<ParcelUuid, byte[]> mServiceData; - private final boolean mIncludeTxPowerLevel; - private final boolean mIncludeDeviceName; - - private AdvertiseData(List<ParcelUuid> serviceUuids, - List<ParcelUuid> serviceSolicitationUuids, - List<TransportDiscoveryData> transportDiscoveryData, - SparseArray<byte[]> manufacturerData, - Map<ParcelUuid, byte[]> serviceData, - boolean includeTxPowerLevel, - boolean includeDeviceName) { - mServiceUuids = serviceUuids; - mServiceSolicitationUuids = serviceSolicitationUuids; - mTransportDiscoveryData = transportDiscoveryData; - mManufacturerSpecificData = manufacturerData; - mServiceData = serviceData; - mIncludeTxPowerLevel = includeTxPowerLevel; - mIncludeDeviceName = includeDeviceName; - } - - /** - * Returns a list of service UUIDs within the advertisement that are used to identify the - * Bluetooth GATT services. - */ - public List<ParcelUuid> getServiceUuids() { - return mServiceUuids; - } - - /** - * Returns a list of service solicitation UUIDs within the advertisement that we invite to connect. - */ - @NonNull - public List<ParcelUuid> getServiceSolicitationUuids() { - return mServiceSolicitationUuids; - } - - /** - * Returns a list of {@link TransportDiscoveryData} within the advertisement. - */ - @NonNull - public List<TransportDiscoveryData> getTransportDiscoveryData() { - if (mTransportDiscoveryData == null) { - return Collections.emptyList(); - } - return mTransportDiscoveryData; - } - - /** - * Returns an array of manufacturer Id and the corresponding manufacturer specific data. The - * manufacturer id is a non-negative number assigned by Bluetooth SIG. - */ - public SparseArray<byte[]> getManufacturerSpecificData() { - return mManufacturerSpecificData; - } - - /** - * Returns a map of 16-bit UUID and its corresponding service data. - */ - public Map<ParcelUuid, byte[]> getServiceData() { - return mServiceData; - } - - /** - * Whether the transmission power level will be included in the advertisement packet. - */ - public boolean getIncludeTxPowerLevel() { - return mIncludeTxPowerLevel; - } - - /** - * Whether the device name will be included in the advertisement packet. - */ - public boolean getIncludeDeviceName() { - return mIncludeDeviceName; - } - - /** - * @hide - */ - @Override - public int hashCode() { - return Objects.hash(mServiceUuids, mServiceSolicitationUuids, mTransportDiscoveryData, - mManufacturerSpecificData, mServiceData, mIncludeDeviceName, mIncludeTxPowerLevel); - } - - /** - * @hide - */ - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - AdvertiseData other = (AdvertiseData) obj; - return Objects.equals(mServiceUuids, other.mServiceUuids) - && Objects.equals(mServiceSolicitationUuids, other.mServiceSolicitationUuids) - && Objects.equals(mTransportDiscoveryData, other.mTransportDiscoveryData) - && BluetoothLeUtils.equals(mManufacturerSpecificData, - other.mManufacturerSpecificData) - && BluetoothLeUtils.equals(mServiceData, other.mServiceData) - && mIncludeDeviceName == other.mIncludeDeviceName - && mIncludeTxPowerLevel == other.mIncludeTxPowerLevel; - } - - @Override - public String toString() { - return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mServiceSolicitationUuids=" - + mServiceSolicitationUuids + ", mTransportDiscoveryData=" - + mTransportDiscoveryData + ", mManufacturerSpecificData=" - + BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData=" - + BluetoothLeUtils.toString(mServiceData) - + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName=" - + mIncludeDeviceName + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeTypedArray(mServiceUuids.toArray(new ParcelUuid[mServiceUuids.size()]), flags); - dest.writeTypedArray(mServiceSolicitationUuids.toArray( - new ParcelUuid[mServiceSolicitationUuids.size()]), flags); - - dest.writeTypedList(mTransportDiscoveryData); - - // mManufacturerSpecificData could not be null. - dest.writeInt(mManufacturerSpecificData.size()); - for (int i = 0; i < mManufacturerSpecificData.size(); ++i) { - dest.writeInt(mManufacturerSpecificData.keyAt(i)); - dest.writeByteArray(mManufacturerSpecificData.valueAt(i)); - } - dest.writeInt(mServiceData.size()); - for (ParcelUuid uuid : mServiceData.keySet()) { - dest.writeTypedObject(uuid, flags); - dest.writeByteArray(mServiceData.get(uuid)); - } - dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0)); - dest.writeByte((byte) (getIncludeDeviceName() ? 1 : 0)); - } - - public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseData> CREATOR = - new Creator<AdvertiseData>() { - @Override - public AdvertiseData[] newArray(int size) { - return new AdvertiseData[size]; - } - - @Override - public AdvertiseData createFromParcel(Parcel in) { - Builder builder = new Builder(); - ArrayList<ParcelUuid> uuids = in.createTypedArrayList(ParcelUuid.CREATOR); - for (ParcelUuid uuid : uuids) { - builder.addServiceUuid(uuid); - } - - ArrayList<ParcelUuid> solicitationUuids = in.createTypedArrayList(ParcelUuid.CREATOR); - for (ParcelUuid uuid : solicitationUuids) { - builder.addServiceSolicitationUuid(uuid); - } - - List<TransportDiscoveryData> transportDiscoveryData = - in.createTypedArrayList(TransportDiscoveryData.CREATOR); - for (TransportDiscoveryData tdd : transportDiscoveryData) { - builder.addTransportDiscoveryData(tdd); - } - - int manufacturerSize = in.readInt(); - for (int i = 0; i < manufacturerSize; ++i) { - int manufacturerId = in.readInt(); - byte[] manufacturerData = in.createByteArray(); - builder.addManufacturerData(manufacturerId, manufacturerData); - } - int serviceDataSize = in.readInt(); - for (int i = 0; i < serviceDataSize; ++i) { - ParcelUuid serviceDataUuid = in.readTypedObject(ParcelUuid.CREATOR); - byte[] serviceData = in.createByteArray(); - builder.addServiceData(serviceDataUuid, serviceData); - } - builder.setIncludeTxPowerLevel(in.readByte() == 1); - builder.setIncludeDeviceName(in.readByte() == 1); - return builder.build(); - } - }; - - /** - * Builder for {@link AdvertiseData}. - */ - public static final class Builder { - @Nullable - private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>(); - @NonNull - private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>(); - @Nullable - private List<TransportDiscoveryData> mTransportDiscoveryData = - new ArrayList<TransportDiscoveryData>(); - private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>(); - private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>(); - private boolean mIncludeTxPowerLevel; - private boolean mIncludeDeviceName; - - /** - * Add a service UUID to advertise data. - * - * @param serviceUuid A service UUID to be advertised. - * @throws IllegalArgumentException If the {@code serviceUuid} is null. - */ - public Builder addServiceUuid(ParcelUuid serviceUuid) { - if (serviceUuid == null) { - throw new IllegalArgumentException("serviceUuid is null"); - } - mServiceUuids.add(serviceUuid); - return this; - } - - /** - * Add a service solicitation UUID to advertise data. - * - * @param serviceSolicitationUuid A service solicitation UUID to be advertised. - * @throws IllegalArgumentException If the {@code serviceSolicitationUuid} is null. - */ - @NonNull - public Builder addServiceSolicitationUuid(@NonNull ParcelUuid serviceSolicitationUuid) { - if (serviceSolicitationUuid == null) { - throw new IllegalArgumentException("serviceSolicitationUuid is null"); - } - mServiceSolicitationUuids.add(serviceSolicitationUuid); - return this; - } - - /** - * Add service data to advertise data. - * - * @param serviceDataUuid 16-bit UUID of the service the data is associated with - * @param serviceData Service data - * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is - * empty. - */ - public Builder addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { - if (serviceDataUuid == null || serviceData == null) { - throw new IllegalArgumentException( - "serviceDataUuid or serviceDataUuid is null"); - } - mServiceData.put(serviceDataUuid, serviceData); - return this; - } - - /** - * Add Transport Discovery Data to advertise data. - * - * @param transportDiscoveryData Transport Discovery Data, consisting of one or more - * Transport Blocks. Transport Discovery Data AD Type Code is already included. - * @throws IllegalArgumentException If the {@code transportDiscoveryData} is empty - */ - @NonNull - public Builder addTransportDiscoveryData( - @NonNull TransportDiscoveryData transportDiscoveryData) { - if (transportDiscoveryData == null) { - throw new IllegalArgumentException("transportDiscoveryData is null"); - } - mTransportDiscoveryData.add(transportDiscoveryData); - return this; - } - - /** - * Add manufacturer specific data. - * <p> - * Please refer to the Bluetooth Assigned Numbers document provided by the <a - * href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing company - * identifiers. - * - * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG. - * @param manufacturerSpecificData Manufacturer specific data - * @throws IllegalArgumentException If the {@code manufacturerId} is negative or {@code - * manufacturerSpecificData} is null. - */ - public Builder addManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) { - if (manufacturerId < 0) { - throw new IllegalArgumentException( - "invalid manufacturerId - " + manufacturerId); - } - if (manufacturerSpecificData == null) { - throw new IllegalArgumentException("manufacturerSpecificData is null"); - } - mManufacturerSpecificData.put(manufacturerId, manufacturerSpecificData); - return this; - } - - /** - * Whether the transmission power level should be included in the advertise packet. Tx power - * level field takes 3 bytes in advertise packet. - */ - public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) { - mIncludeTxPowerLevel = includeTxPowerLevel; - return this; - } - - /** - * Set whether the device name should be included in advertise packet. - */ - public Builder setIncludeDeviceName(boolean includeDeviceName) { - mIncludeDeviceName = includeDeviceName; - return this; - } - - /** - * Build the {@link AdvertiseData}. - */ - public AdvertiseData build() { - return new AdvertiseData(mServiceUuids, mServiceSolicitationUuids, - mTransportDiscoveryData, mManufacturerSpecificData, mServiceData, - mIncludeTxPowerLevel, mIncludeDeviceName); - } - } -} diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java deleted file mode 100644 index c52a6ee33989..000000000000 --- a/core/java/android/bluetooth/le/AdvertiseSettings.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.bluetooth.le.AdvertisingSetParameters.AddressTypeStatus; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each - * Bluetooth LE advertisement instance. Use {@link AdvertiseSettings.Builder} to create an - * instance of this class. - */ -public final class AdvertiseSettings implements Parcelable { - /** - * Perform Bluetooth LE advertising in low power mode. This is the default and preferred - * advertising mode as it consumes the least power. - */ - public static final int ADVERTISE_MODE_LOW_POWER = 0; - - /** - * Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising - * frequency and power consumption. - */ - public static final int ADVERTISE_MODE_BALANCED = 1; - - /** - * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power - * consumption and should not be used for continuous background advertising. - */ - public static final int ADVERTISE_MODE_LOW_LATENCY = 2; - - /** - * Advertise using the lowest transmission (TX) power level. Low transmission power can be used - * to restrict the visibility range of advertising packets. - */ - public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; - - /** - * Advertise using low TX power level. - */ - public static final int ADVERTISE_TX_POWER_LOW = 1; - - /** - * Advertise using medium TX power level. - */ - public static final int ADVERTISE_TX_POWER_MEDIUM = 2; - - /** - * Advertise using high TX power level. This corresponds to largest visibility range of the - * advertising packet. - */ - public static final int ADVERTISE_TX_POWER_HIGH = 3; - - /** - * The maximum limited advertisement duration as specified by the Bluetooth SIG - */ - private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000; - - - private final int mAdvertiseMode; - private final int mAdvertiseTxPowerLevel; - private final int mAdvertiseTimeoutMillis; - private final boolean mAdvertiseConnectable; - private final int mOwnAddressType; - - private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel, - boolean advertiseConnectable, int advertiseTimeout, - @AddressTypeStatus int ownAddressType) { - mAdvertiseMode = advertiseMode; - mAdvertiseTxPowerLevel = advertiseTxPowerLevel; - mAdvertiseConnectable = advertiseConnectable; - mAdvertiseTimeoutMillis = advertiseTimeout; - mOwnAddressType = ownAddressType; - } - - private AdvertiseSettings(Parcel in) { - mAdvertiseMode = in.readInt(); - mAdvertiseTxPowerLevel = in.readInt(); - mAdvertiseConnectable = in.readInt() != 0; - mAdvertiseTimeoutMillis = in.readInt(); - mOwnAddressType = in.readInt(); - } - - /** - * Returns the advertise mode. - */ - public int getMode() { - return mAdvertiseMode; - } - - /** - * Returns the TX power level for advertising. - */ - public int getTxPowerLevel() { - return mAdvertiseTxPowerLevel; - } - - /** - * Returns whether the advertisement will indicate connectable. - */ - public boolean isConnectable() { - return mAdvertiseConnectable; - } - - /** - * Returns the advertising time limit in milliseconds. - */ - public int getTimeout() { - return mAdvertiseTimeoutMillis; - } - - /** - * @return the own address type for advertising - * - * @hide - */ - @SystemApi - public @AddressTypeStatus int getOwnAddressType() { - return mOwnAddressType; - } - - @Override - public String toString() { - return "Settings [mAdvertiseMode=" + mAdvertiseMode - + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel - + ", mAdvertiseConnectable=" + mAdvertiseConnectable - + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis - + ", mOwnAddressType=" + mOwnAddressType + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAdvertiseMode); - dest.writeInt(mAdvertiseTxPowerLevel); - dest.writeInt(mAdvertiseConnectable ? 1 : 0); - dest.writeInt(mAdvertiseTimeoutMillis); - dest.writeInt(mOwnAddressType); - } - - public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseSettings> CREATOR = - new Creator<AdvertiseSettings>() { - @Override - public AdvertiseSettings[] newArray(int size) { - return new AdvertiseSettings[size]; - } - - @Override - public AdvertiseSettings createFromParcel(Parcel in) { - return new AdvertiseSettings(in); - } - }; - - /** - * Builder class for {@link AdvertiseSettings}. - */ - public static final class Builder { - private int mMode = ADVERTISE_MODE_LOW_POWER; - private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM; - private int mTimeoutMillis = 0; - private boolean mConnectable = true; - private int mOwnAddressType = AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT; - - /** - * Set advertise mode to control the advertising power and latency. - * - * @param advertiseMode Bluetooth LE Advertising mode, can only be one of {@link - * AdvertiseSettings#ADVERTISE_MODE_LOW_POWER}, - * {@link AdvertiseSettings#ADVERTISE_MODE_BALANCED}, - * or {@link AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY}. - * @throws IllegalArgumentException If the advertiseMode is invalid. - */ - public Builder setAdvertiseMode(int advertiseMode) { - if (advertiseMode < ADVERTISE_MODE_LOW_POWER - || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) { - throw new IllegalArgumentException("unknown mode " + advertiseMode); - } - mMode = advertiseMode; - return this; - } - - /** - * Set advertise TX power level to control the transmission power level for the advertising. - * - * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of - * {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW}, {@link - * AdvertiseSettings#ADVERTISE_TX_POWER_LOW}, - * {@link AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM} - * or {@link AdvertiseSettings#ADVERTISE_TX_POWER_HIGH}. - * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. - */ - public Builder setTxPowerLevel(int txPowerLevel) { - if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW - || txPowerLevel > ADVERTISE_TX_POWER_HIGH) { - throw new IllegalArgumentException("unknown tx power level " + txPowerLevel); - } - mTxPowerLevel = txPowerLevel; - return this; - } - - /** - * Set whether the advertisement type should be connectable or non-connectable. - * - * @param connectable Controls whether the advertisment type will be connectable (true) or - * non-connectable (false). - */ - public Builder setConnectable(boolean connectable) { - mConnectable = connectable; - return this; - } - - /** - * Limit advertising to a given amount of time. - * - * @param timeoutMillis Advertising time limit. May not exceed 180000 milliseconds. A value - * of 0 will disable the time limit. - * @throws IllegalArgumentException If the provided timeout is over 180000 ms. - */ - public Builder setTimeout(int timeoutMillis) { - if (timeoutMillis < 0 || timeoutMillis > LIMITED_ADVERTISING_MAX_MILLIS) { - throw new IllegalArgumentException("timeoutMillis invalid (must be 0-" - + LIMITED_ADVERTISING_MAX_MILLIS + " milliseconds)"); - } - mTimeoutMillis = timeoutMillis; - return this; - } - - /** - * Set own address type for advertising to control public or privacy mode. If used to set - * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT}, - * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the - * time of starting advertising. - * - * @throws IllegalArgumentException If the {@code ownAddressType} is invalid - * - * @hide - */ - @SystemApi - public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) { - if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT - || ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) { - throw new IllegalArgumentException("unknown address type " + ownAddressType); - } - mOwnAddressType = ownAddressType; - return this; - } - - /** - * Build the {@link AdvertiseSettings} object. - */ - public AdvertiseSettings build() { - return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis, - mOwnAddressType); - } - } -} diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java deleted file mode 100644 index bbdb6953afd1..000000000000 --- a/core/java/android/bluetooth/le/AdvertisingSet.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.content.AttributionSource; -import android.os.RemoteException; -import android.util.Log; - -/** - * This class provides a way to control single Bluetooth LE advertising instance. - * <p> - * To get an instance of {@link AdvertisingSet}, call the - * {@link BluetoothLeAdvertiser#startAdvertisingSet} method. - * - * @see AdvertiseData - */ -public final class AdvertisingSet { - private static final String TAG = "AdvertisingSet"; - - private final IBluetoothGatt mGatt; - private int mAdvertiserId; - private AttributionSource mAttributionSource; - - /* package */ AdvertisingSet(int advertiserId, IBluetoothManager bluetoothManager, - AttributionSource attributionSource) { - mAdvertiserId = advertiserId; - mAttributionSource = attributionSource; - try { - mGatt = bluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth gatt - ", e); - throw new IllegalStateException("Failed to get Bluetooth"); - } - } - - /* package */ void setAdvertiserId(int advertiserId) { - mAdvertiserId = advertiserId; - } - - /** - * Enables Advertising. This method returns immediately, the operation status is - * delivered through {@code callback.onAdvertisingEnabled()}. - * - * @param enable whether the advertising should be enabled (true), or disabled (false) - * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 - * (655,350 ms) - * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the - * controller shall attempt to send prior to terminating the extended advertising, even if the - * duration has not expired. Valid range is from 1 to 255. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void enableAdvertising(boolean enable, int duration, - int maxExtendedAdvertisingEvents) { - try { - mGatt.enableAdvertisingSet(mAdvertiserId, enable, duration, - maxExtendedAdvertisingEvents, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Set/update data being Advertised. Make sure that data doesn't exceed the size limit for - * specified AdvertisingSetParameters. This method returns immediately, the operation status is - * delivered through {@code callback.onAdvertisingDataSet()}. - * <p> - * Advertising data must be empty if non-legacy scannable advertising is used. - * - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. If the update takes place when the advertising set is - * enabled, the data can be maximum 251 bytes long. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setAdvertisingData(AdvertiseData advertiseData) { - try { - mGatt.setAdvertisingData(mAdvertiserId, advertiseData, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Set/update scan response data. Make sure that data doesn't exceed the size limit for - * specified AdvertisingSetParameters. This method returns immediately, the operation status - * is delivered through {@code callback.onScanResponseDataSet()}. - * - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place - * when the advertising set is enabled, the data can be maximum 251 bytes long. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setScanResponseData(AdvertiseData scanResponse) { - try { - mGatt.setScanResponseData(mAdvertiserId, scanResponse, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Update advertising parameters associated with this AdvertisingSet. Must be called when - * advertising is not active. This method returns immediately, the operation status is delivered - * through {@code callback.onAdvertisingParametersUpdated}. - * - * @param parameters advertising set parameters. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setAdvertisingParameters(AdvertisingSetParameters parameters) { - try { - mGatt.setAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Update periodic advertising parameters associated with this set. Must be called when - * periodic advertising is not enabled. This method returns immediately, the operation - * status is delivered through {@code callback.onPeriodicAdvertisingParametersUpdated()}. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) { - try { - mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Used to set periodic advertising data, must be called after setPeriodicAdvertisingParameters, - * or after advertising was started with periodic advertising data set. This method returns - * immediately, the operation status is delivered through - * {@code callback.onPeriodicAdvertisingDataSet()}. - * - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place when the - * periodic advertising is enabled for this set, the data can be maximum 251 bytes long. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setPeriodicAdvertisingData(AdvertiseData periodicData) { - try { - mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Used to enable/disable periodic advertising. This method returns immediately, the operation - * status is delivered through {@code callback.onPeriodicAdvertisingEnable()}. - * - * @param enable whether the periodic advertising should be enabled (true), or disabled - * (false). - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setPeriodicAdvertisingEnabled(boolean enable) { - try { - mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Returns address associated with this advertising set. - * This method is exposed only for Bluetooth PTS tests, no app or system service - * should ever use it. - * - * @hide - */ - @RequiresBluetoothAdvertisePermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_ADVERTISE, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public void getOwnAddress() { - try { - mGatt.getOwnAddress(mAdvertiserId, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Returns advertiserId associated with this advertising set. - * - * @hide - */ - @RequiresNoPermission - public int getAdvertiserId() { - return mAdvertiserId; - } -} diff --git a/core/java/android/bluetooth/le/AdvertisingSetCallback.java b/core/java/android/bluetooth/le/AdvertisingSetCallback.java deleted file mode 100644 index 51324fdb01ff..000000000000 --- a/core/java/android/bluetooth/le/AdvertisingSetCallback.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -/** - * Bluetooth LE advertising set callbacks, used to deliver advertising operation - * status. - */ -public abstract class AdvertisingSetCallback { - - /** - * The requested operation was successful. - */ - public static final int ADVERTISE_SUCCESS = 0; - - /** - * Failed to start advertising as the advertise data to be broadcasted is too - * large. - */ - public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; - - /** - * Failed to start advertising because no advertising instance is available. - */ - public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; - - /** - * Failed to start advertising as the advertising is already started. - */ - public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; - - /** - * Operation failed due to an internal error. - */ - public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; - - /** - * This feature is not supported on this platform. - */ - public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; - - /** - * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet} - * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertisingSet - * contains the started set and it is advertising. If error occurred, advertisingSet is - * null, and status will be set to proper error code. - * - * @param advertisingSet The advertising set that was started or null if error. - * @param txPower tx power that will be used for this set. - * @param status Status of the operation. - */ - public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) { - } - - /** - * Callback triggered in response to {@link BluetoothLeAdvertiser#stopAdvertisingSet} - * indicating advertising set is stopped. - * - * @param advertisingSet The advertising set. - */ - public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { - } - - /** - * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet} - * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertising set is - * advertising. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating - * result of the operation. If status is ADVERTISE_SUCCESS, then data was changed. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating - * result of the operation. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setAdvertisingParameters} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param txPower tx power that will be used for this set. - * @param status Status of the operation. - */ - public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet, - int txPower, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingParameters} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onPeriodicAdvertisingParametersUpdated(AdvertisingSet advertisingSet, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingData} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onPeriodicAdvertisingDataSet(AdvertisingSet advertisingSet, - int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnabled} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onPeriodicAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, - int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#getOwnAddress()} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param addressType type of address. - * @param address advertising set bluetooth address. - * @hide - */ - public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, String address) { - } -} diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java deleted file mode 100644 index 5c8fae65193d..000000000000 --- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * The {@link AdvertisingSetParameters} provide a way to adjust advertising - * preferences for each - * Bluetooth LE advertising set. Use {@link AdvertisingSetParameters.Builder} to - * create an - * instance of this class. - */ -public final class AdvertisingSetParameters implements Parcelable { - - /** - * Advertise on low frequency, around every 1000ms. This is the default and - * preferred advertising mode as it consumes the least power. - */ - public static final int INTERVAL_HIGH = 1600; - - /** - * Advertise on medium frequency, around every 250ms. This is balanced - * between advertising frequency and power consumption. - */ - public static final int INTERVAL_MEDIUM = 400; - - /** - * Perform high frequency, low latency advertising, around every 100ms. This - * has the highest power consumption and should not be used for continuous - * background advertising. - */ - public static final int INTERVAL_LOW = 160; - - /** - * Minimum value for advertising interval. - */ - public static final int INTERVAL_MIN = 160; - - /** - * Maximum value for advertising interval. - */ - public static final int INTERVAL_MAX = 16777215; - - /** - * Advertise using the lowest transmission (TX) power level. Low transmission - * power can be used to restrict the visibility range of advertising packets. - */ - public static final int TX_POWER_ULTRA_LOW = -21; - - /** - * Advertise using low TX power level. - */ - public static final int TX_POWER_LOW = -15; - - /** - * Advertise using medium TX power level. - */ - public static final int TX_POWER_MEDIUM = -7; - - /** - * Advertise using high TX power level. This corresponds to largest visibility - * range of the advertising packet. - */ - public static final int TX_POWER_HIGH = 1; - - /** - * Minimum value for TX power. - */ - public static final int TX_POWER_MIN = -127; - - /** - * Maximum value for TX power. - */ - public static final int TX_POWER_MAX = 1; - - /** - * The maximum limited advertisement duration as specified by the Bluetooth - * SIG - */ - private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000; - - /** @hide */ - @IntDef(prefix = "ADDRESS_TYPE_", value = { - ADDRESS_TYPE_DEFAULT, - ADDRESS_TYPE_PUBLIC, - ADDRESS_TYPE_RANDOM - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AddressTypeStatus {} - - /** - * Advertise own address type that corresponds privacy settings of the device. - * - * @hide - */ - @SystemApi - public static final int ADDRESS_TYPE_DEFAULT = -1; - - /** - * Advertise own public address type. - * - * @hide - */ - @SystemApi - public static final int ADDRESS_TYPE_PUBLIC = 0; - - /** - * Generate and adverise own resolvable private address. - * - * @hide - */ - @SystemApi - public static final int ADDRESS_TYPE_RANDOM = 1; - - private final boolean mIsLegacy; - private final boolean mIsAnonymous; - private final boolean mIncludeTxPower; - private final int mPrimaryPhy; - private final int mSecondaryPhy; - private final boolean mConnectable; - private final boolean mScannable; - private final int mInterval; - private final int mTxPowerLevel; - private final int mOwnAddressType; - - private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy, - boolean isAnonymous, boolean includeTxPower, - int primaryPhy, int secondaryPhy, - int interval, int txPowerLevel, @AddressTypeStatus int ownAddressType) { - mConnectable = connectable; - mScannable = scannable; - mIsLegacy = isLegacy; - mIsAnonymous = isAnonymous; - mIncludeTxPower = includeTxPower; - mPrimaryPhy = primaryPhy; - mSecondaryPhy = secondaryPhy; - mInterval = interval; - mTxPowerLevel = txPowerLevel; - mOwnAddressType = ownAddressType; - } - - private AdvertisingSetParameters(Parcel in) { - mConnectable = in.readInt() != 0; - mScannable = in.readInt() != 0; - mIsLegacy = in.readInt() != 0; - mIsAnonymous = in.readInt() != 0; - mIncludeTxPower = in.readInt() != 0; - mPrimaryPhy = in.readInt(); - mSecondaryPhy = in.readInt(); - mInterval = in.readInt(); - mTxPowerLevel = in.readInt(); - mOwnAddressType = in.readInt(); - } - - /** - * Returns whether the advertisement will be connectable. - */ - public boolean isConnectable() { - return mConnectable; - } - - /** - * Returns whether the advertisement will be scannable. - */ - public boolean isScannable() { - return mScannable; - } - - /** - * Returns whether the legacy advertisement will be used. - */ - public boolean isLegacy() { - return mIsLegacy; - } - - /** - * Returns whether the advertisement will be anonymous. - */ - public boolean isAnonymous() { - return mIsAnonymous; - } - - /** - * Returns whether the TX Power will be included. - */ - public boolean includeTxPower() { - return mIncludeTxPower; - } - - /** - * Returns the primary advertising phy. - */ - public int getPrimaryPhy() { - return mPrimaryPhy; - } - - /** - * Returns the secondary advertising phy. - */ - public int getSecondaryPhy() { - return mSecondaryPhy; - } - - /** - * Returns the advertising interval. - */ - public int getInterval() { - return mInterval; - } - - /** - * Returns the TX power level for advertising. - */ - public int getTxPowerLevel() { - return mTxPowerLevel; - } - - /** - * @return the own address type for advertising - * - * @hide - */ - @SystemApi - public @AddressTypeStatus int getOwnAddressType() { - return mOwnAddressType; - } - - @Override - public String toString() { - return "AdvertisingSetParameters [connectable=" + mConnectable - + ", isLegacy=" + mIsLegacy - + ", isAnonymous=" + mIsAnonymous - + ", includeTxPower=" + mIncludeTxPower - + ", primaryPhy=" + mPrimaryPhy - + ", secondaryPhy=" + mSecondaryPhy - + ", interval=" + mInterval - + ", txPowerLevel=" + mTxPowerLevel - + ", ownAddressType=" + mOwnAddressType + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mConnectable ? 1 : 0); - dest.writeInt(mScannable ? 1 : 0); - dest.writeInt(mIsLegacy ? 1 : 0); - dest.writeInt(mIsAnonymous ? 1 : 0); - dest.writeInt(mIncludeTxPower ? 1 : 0); - dest.writeInt(mPrimaryPhy); - dest.writeInt(mSecondaryPhy); - dest.writeInt(mInterval); - dest.writeInt(mTxPowerLevel); - dest.writeInt(mOwnAddressType); - } - - public static final @android.annotation.NonNull Parcelable.Creator<AdvertisingSetParameters> CREATOR = - new Creator<AdvertisingSetParameters>() { - @Override - public AdvertisingSetParameters[] newArray(int size) { - return new AdvertisingSetParameters[size]; - } - - @Override - public AdvertisingSetParameters createFromParcel(Parcel in) { - return new AdvertisingSetParameters(in); - } - }; - - /** - * Builder class for {@link AdvertisingSetParameters}. - */ - public static final class Builder { - private boolean mConnectable = false; - private boolean mScannable = false; - private boolean mIsLegacy = false; - private boolean mIsAnonymous = false; - private boolean mIncludeTxPower = false; - private int mPrimaryPhy = BluetoothDevice.PHY_LE_1M; - private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M; - private int mInterval = INTERVAL_LOW; - private int mTxPowerLevel = TX_POWER_MEDIUM; - private int mOwnAddressType = ADDRESS_TYPE_DEFAULT; - - /** - * Set whether the advertisement type should be connectable or - * non-connectable. - * Legacy advertisements can be both connectable and scannable. Non-legacy - * advertisements can be only scannable or only connectable. - * - * @param connectable Controls whether the advertisement type will be connectable (true) or - * non-connectable (false). - */ - public Builder setConnectable(boolean connectable) { - mConnectable = connectable; - return this; - } - - /** - * Set whether the advertisement type should be scannable. - * Legacy advertisements can be both connectable and scannable. Non-legacy - * advertisements can be only scannable or only connectable. - * - * @param scannable Controls whether the advertisement type will be scannable (true) or - * non-scannable (false). - */ - public Builder setScannable(boolean scannable) { - mScannable = scannable; - return this; - } - - /** - * When set to true, advertising set will advertise 4.x Spec compliant - * advertisements. - * - * @param isLegacy whether legacy advertising mode should be used. - */ - public Builder setLegacyMode(boolean isLegacy) { - mIsLegacy = isLegacy; - return this; - } - - /** - * Set whether advertiser address should be ommited from all packets. If this - * mode is used, periodic advertising can't be enabled for this set. - * - * This is used only if legacy mode is not used. - * - * @param isAnonymous whether anonymous advertising should be used. - */ - public Builder setAnonymous(boolean isAnonymous) { - mIsAnonymous = isAnonymous; - return this; - } - - /** - * Set whether TX power should be included in the extended header. - * - * This is used only if legacy mode is not used. - * - * @param includeTxPower whether TX power should be included in extended header - */ - public Builder setIncludeTxPower(boolean includeTxPower) { - mIncludeTxPower = includeTxPower; - return this; - } - - /** - * Set the primary physical channel used for this advertising set. - * - * This is used only if legacy mode is not used. - * - * Use {@link BluetoothAdapter#isLeCodedPhySupported} to determine if LE Coded PHY is - * supported on this device. - * - * @param primaryPhy Primary advertising physical channel, can only be {@link - * BluetoothDevice#PHY_LE_1M} or {@link BluetoothDevice#PHY_LE_CODED}. - * @throws IllegalArgumentException If the primaryPhy is invalid. - */ - public Builder setPrimaryPhy(int primaryPhy) { - if (primaryPhy != BluetoothDevice.PHY_LE_1M - && primaryPhy != BluetoothDevice.PHY_LE_CODED) { - throw new IllegalArgumentException("bad primaryPhy " + primaryPhy); - } - mPrimaryPhy = primaryPhy; - return this; - } - - /** - * Set the secondary physical channel used for this advertising set. - * - * This is used only if legacy mode is not used. - * - * Use {@link BluetoothAdapter#isLeCodedPhySupported} and - * {@link BluetoothAdapter#isLe2MPhySupported} to determine if LE Coded PHY or 2M PHY is - * supported on this device. - * - * @param secondaryPhy Secondary advertising physical channel, can only be one of {@link - * BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M} or {@link - * BluetoothDevice#PHY_LE_CODED}. - * @throws IllegalArgumentException If the secondaryPhy is invalid. - */ - public Builder setSecondaryPhy(int secondaryPhy) { - if (secondaryPhy != BluetoothDevice.PHY_LE_1M - && secondaryPhy != BluetoothDevice.PHY_LE_2M - && secondaryPhy != BluetoothDevice.PHY_LE_CODED) { - throw new IllegalArgumentException("bad secondaryPhy " + secondaryPhy); - } - mSecondaryPhy = secondaryPhy; - return this; - } - - /** - * Set advertising interval. - * - * @param interval Bluetooth LE Advertising interval, in 0.625ms unit. Valid range is from - * 160 (100ms) to 16777215 (10,485.759375 s). Recommended values are: {@link - * AdvertisingSetParameters#INTERVAL_LOW}, {@link AdvertisingSetParameters#INTERVAL_MEDIUM}, - * or {@link AdvertisingSetParameters#INTERVAL_HIGH}. - * @throws IllegalArgumentException If the interval is invalid. - */ - public Builder setInterval(int interval) { - if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) { - throw new IllegalArgumentException("unknown interval " + interval); - } - mInterval = interval; - return this; - } - - /** - * Set the transmission power level for the advertising. - * - * @param txPowerLevel Transmission power of Bluetooth LE Advertising, in dBm. The valid - * range is [-127, 1] Recommended values are: - * {@link AdvertisingSetParameters#TX_POWER_ULTRA_LOW}, - * {@link AdvertisingSetParameters#TX_POWER_LOW}, - * {@link AdvertisingSetParameters#TX_POWER_MEDIUM}, - * or {@link AdvertisingSetParameters#TX_POWER_HIGH}. - * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. - */ - public Builder setTxPowerLevel(int txPowerLevel) { - if (txPowerLevel < TX_POWER_MIN || txPowerLevel > TX_POWER_MAX) { - throw new IllegalArgumentException("unknown txPowerLevel " + txPowerLevel); - } - mTxPowerLevel = txPowerLevel; - return this; - } - - /** - * Set own address type for advertising to control public or privacy mode. If used to set - * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT}, - * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the - * time of starting advertising. - * - * @throws IllegalArgumentException If the {@code ownAddressType} is invalid - * - * @hide - */ - @SystemApi - public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) { - if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT - || ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) { - throw new IllegalArgumentException("unknown address type " + ownAddressType); - } - mOwnAddressType = ownAddressType; - return this; - } - - /** - * Build the {@link AdvertisingSetParameters} object. - * - * @throws IllegalStateException if invalid combination of parameters is used. - */ - public AdvertisingSetParameters build() { - if (mIsLegacy) { - if (mIsAnonymous) { - throw new IllegalArgumentException("Legacy advertising can't be anonymous"); - } - - if (mConnectable && !mScannable) { - throw new IllegalStateException( - "Legacy advertisement can't be connectable and non-scannable"); - } - - if (mIncludeTxPower) { - throw new IllegalStateException( - "Legacy advertising can't include TX power level in header"); - } - } else { - if (mConnectable && mScannable) { - throw new IllegalStateException( - "Advertising can't be both connectable and scannable"); - } - - if (mIsAnonymous && mConnectable) { - throw new IllegalStateException( - "Advertising can't be both connectable and anonymous"); - } - } - - return new AdvertisingSetParameters(mConnectable, mScannable, mIsLegacy, mIsAnonymous, - mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel, - mOwnAddressType); - } - } -} diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java deleted file mode 100644 index 879dceedaaec..000000000000 --- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ /dev/null @@ -1,756 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothUuid; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.content.AttributionSource; -import android.os.Handler; -import android.os.Looper; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This class provides a way to perform Bluetooth LE advertise operations, such as starting and - * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data - * represented by {@link AdvertiseData}. - * <p> - * To get an instance of {@link BluetoothLeAdvertiser}, call the - * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. - * - * @see AdvertiseData - */ -public final class BluetoothLeAdvertiser { - - private static final String TAG = "BluetoothLeAdvertiser"; - - private static final int MAX_ADVERTISING_DATA_BYTES = 1650; - private static final int MAX_LEGACY_ADVERTISING_DATA_BYTES = 31; - // Each fields need one byte for field length and another byte for field type. - private static final int OVERHEAD_BYTES_PER_FIELD = 2; - // Flags field will be set by system. - private static final int FLAGS_FIELD_BYTES = 3; - private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2; - - private final BluetoothAdapter mBluetoothAdapter; - private final IBluetoothManager mBluetoothManager; - private final AttributionSource mAttributionSource; - - private final Handler mHandler; - private final Map<AdvertiseCallback, AdvertisingSetCallback> - mLegacyAdvertisers = new HashMap<>(); - private final Map<AdvertisingSetCallback, IAdvertisingSetCallback> - mCallbackWrappers = Collections.synchronizedMap(new HashMap<>()); - private final Map<Integer, AdvertisingSet> - mAdvertisingSets = Collections.synchronizedMap(new HashMap<>()); - - /** - * Use BluetoothAdapter.getLeAdvertiser() instead. - * - * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management - * @hide - */ - public BluetoothLeAdvertiser(BluetoothAdapter bluetoothAdapter) { - mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); - mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); - mAttributionSource = mBluetoothAdapter.getAttributionSource(); - mHandler = new Handler(Looper.getMainLooper()); - } - - /** - * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted. - * Returns immediately, the operation status is delivered through {@code callback}. - * - * @param settings Settings for Bluetooth LE advertising. - * @param advertiseData Advertisement data to be broadcasted. - * @param callback Callback for advertising status. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertising(AdvertiseSettings settings, - AdvertiseData advertiseData, final AdvertiseCallback callback) { - startAdvertising(settings, advertiseData, null, callback); - } - - /** - * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the - * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an - * active scan request. This method returns immediately, the operation status is delivered - * through {@code callback}. - * - * @param settings Settings for Bluetooth LE advertising. - * @param advertiseData Advertisement data to be advertised in advertisement packet. - * @param scanResponse Scan response associated with the advertisement data. - * @param callback Callback for advertising status. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertising(AdvertiseSettings settings, - AdvertiseData advertiseData, AdvertiseData scanResponse, - final AdvertiseCallback callback) { - synchronized (mLegacyAdvertisers) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - boolean isConnectable = settings.isConnectable(); - if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES - || totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { - postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); - return; - } - if (mLegacyAdvertisers.containsKey(callback)) { - postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); - return; - } - - AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder(); - parameters.setLegacyMode(true); - parameters.setConnectable(isConnectable); - parameters.setScannable(true); // legacy advertisements we support are always scannable - parameters.setOwnAddressType(settings.getOwnAddressType()); - if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) { - parameters.setInterval(1600); // 1s - } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) { - parameters.setInterval(400); // 250ms - } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) { - parameters.setInterval(160); // 100ms - } - - if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) { - parameters.setTxPowerLevel(-21); - } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) { - parameters.setTxPowerLevel(-15); - } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) { - parameters.setTxPowerLevel(-7); - } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) { - parameters.setTxPowerLevel(1); - } - - int duration = 0; - int timeoutMillis = settings.getTimeout(); - if (timeoutMillis > 0) { - duration = (timeoutMillis < 10) ? 1 : timeoutMillis / 10; - } - - AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings); - mLegacyAdvertisers.put(callback, wrapped); - startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null, - duration, 0, wrapped); - } - } - - @SuppressLint({ - "AndroidFrameworkBluetoothPermission", - "AndroidFrameworkRequiresPermission", - }) - AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) { - return new AdvertisingSetCallback() { - @Override - public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, - int status) { - if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { - postStartFailure(callback, status); - return; - } - - postStartSuccess(callback, settings); - } - - /* Legacy advertiser is disabled on timeout */ - @Override - public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled, - int status) { - if (enabled) { - Log.e(TAG, "Legacy advertiser should be only disabled on timeout," - + " but was enabled!"); - return; - } - - stopAdvertising(callback); - } - - }; - } - - /** - * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in - * {@link BluetoothLeAdvertiser#startAdvertising}. - * - * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void stopAdvertising(final AdvertiseCallback callback) { - synchronized (mLegacyAdvertisers) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback); - if (wrapper == null) return; - - stopAdvertisingSet(wrapper); - - mLegacyAdvertisers.remove(callback); - } - } - - /** - * Creates a new advertising set. If operation succeed, device will start advertising. This - * method returns immediately, the operation status is delivered through - * {@code callback.onAdvertisingSetStarted()}. - * <p> - * - * @param parameters advertising set parameters. - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will - * not be started. - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param callback Callback for advertising set. - * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable - * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising - * feature is made when it's not supported by the controller. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertisingSet(AdvertisingSetParameters parameters, - AdvertiseData advertiseData, AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, AdvertisingSetCallback callback) { - startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, 0, 0, callback, new Handler(Looper.getMainLooper())); - } - - /** - * Creates a new advertising set. If operation succeed, device will start advertising. This - * method returns immediately, the operation status is delivered through - * {@code callback.onAdvertisingSetStarted()}. - * <p> - * - * @param parameters advertising set parameters. - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will - * not be started. - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param callback Callback for advertising set. - * @param handler thread upon which the callbacks will be invoked. - * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable - * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising - * feature is made when it's not supported by the controller. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertisingSet(AdvertisingSetParameters parameters, - AdvertiseData advertiseData, AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, AdvertisingSetCallback callback, - Handler handler) { - startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, 0, 0, callback, handler); - } - - /** - * Creates a new advertising set. If operation succeed, device will start advertising. This - * method returns immediately, the operation status is delivered through - * {@code callback.onAdvertisingSetStarted()}. - * <p> - * - * @param parameters advertising set parameters. - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will - * not be started. - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 - * (655,350 ms). 0 means advertising should continue until stopped. - * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the - * controller shall attempt to send prior to terminating the extended advertising, even if the - * duration has not expired. Valid range is from 1 to 255. 0 means no maximum. - * @param callback Callback for advertising set. - * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable - * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising - * feature is made when it's not supported by the controller. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertisingSet(AdvertisingSetParameters parameters, - AdvertiseData advertiseData, AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, int duration, - int maxExtendedAdvertisingEvents, - AdvertisingSetCallback callback) { - startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, duration, maxExtendedAdvertisingEvents, callback, - new Handler(Looper.getMainLooper())); - } - - /** - * Creates a new advertising set. If operation succeed, device will start advertising. This - * method returns immediately, the operation status is delivered through - * {@code callback.onAdvertisingSetStarted()}. - * <p> - * - * @param parameters Advertising set parameters. - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength} - * @param periodicParameters Periodic advertisng parameters. If null, periodic advertising will - * not be started. - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength} - * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 - * (655,350 ms). 0 means advertising should continue until stopped. - * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the - * controller shall attempt to send prior to terminating the extended advertising, even if the - * duration has not expired. Valid range is from 1 to 255. 0 means no maximum. - * @param callback Callback for advertising set. - * @param handler Thread upon which the callbacks will be invoked. - * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable - * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising - * feature is made when it's not supported by the controller, or when - * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended - * Advertising - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertisingSet(AdvertisingSetParameters parameters, - AdvertiseData advertiseData, AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, int duration, - int maxExtendedAdvertisingEvents, AdvertisingSetCallback callback, - Handler handler) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - - boolean isConnectable = parameters.isConnectable(); - if (parameters.isLegacy()) { - if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { - throw new IllegalArgumentException("Legacy advertising data too big"); - } - - if (totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { - throw new IllegalArgumentException("Legacy scan response data too big"); - } - } else { - boolean supportCodedPhy = mBluetoothAdapter.isLeCodedPhySupported(); - boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported(); - int pphy = parameters.getPrimaryPhy(); - int sphy = parameters.getSecondaryPhy(); - if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) { - throw new IllegalArgumentException("Unsupported primary PHY selected"); - } - - if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) - || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) { - throw new IllegalArgumentException("Unsupported secondary PHY selected"); - } - - int maxData = mBluetoothAdapter.getLeMaximumAdvertisingDataLength(); - if (totalBytes(advertiseData, isConnectable) > maxData) { - throw new IllegalArgumentException("Advertising data too big"); - } - - if (totalBytes(scanResponse, false) > maxData) { - throw new IllegalArgumentException("Scan response data too big"); - } - - if (totalBytes(periodicData, false) > maxData) { - throw new IllegalArgumentException("Periodic advertising data too big"); - } - - boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported(); - if (periodicParameters != null && !supportPeriodic) { - throw new IllegalArgumentException( - "Controller does not support LE Periodic Advertising"); - } - } - - if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) { - throw new IllegalArgumentException( - "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents); - } - - if (maxExtendedAdvertisingEvents != 0 - && !mBluetoothAdapter.isLePeriodicAdvertisingSupported()) { - throw new IllegalArgumentException( - "Can't use maxExtendedAdvertisingEvents with controller that don't support " - + "LE Extended Advertising"); - } - - if (duration < 0 || duration > 65535) { - throw new IllegalArgumentException("duration out of range: " + duration); - } - - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth GATT - ", e); - postStartSetFailure(handler, callback, - AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - return; - } - - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); - postStartSetFailure(handler, callback, - AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - return; - } - - IAdvertisingSetCallback wrapped = wrap(callback, handler); - if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) { - throw new IllegalArgumentException( - "callback instance already associated with advertising"); - } - - try { - gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, duration, maxExtendedAdvertisingEvents, wrapped, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to start advertising set - ", e); - postStartSetFailure(handler, callback, - AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - return; - } - } - - /** - * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link - * BluetoothLeAdvertiser#startAdvertisingSet}. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void stopAdvertisingSet(AdvertisingSetCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - - IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback); - if (wrapped == null) { - return; - } - - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - gatt.stopAdvertisingSet(wrapped, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to stop advertising - ", e); - } - } - - /** - * Cleans up advertisers. Should be called when bluetooth is down. - * - * @hide - */ - @RequiresNoPermission - public void cleanup() { - mLegacyAdvertisers.clear(); - mCallbackWrappers.clear(); - mAdvertisingSets.clear(); - } - - // Compute the size of advertisement data or scan resp - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) { - if (data == null) return 0; - // Flags field is omitted if the advertising is not connectable. - int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0; - if (data.getServiceUuids() != null) { - int num16BitUuids = 0; - int num32BitUuids = 0; - int num128BitUuids = 0; - for (ParcelUuid uuid : data.getServiceUuids()) { - if (BluetoothUuid.is16BitUuid(uuid)) { - ++num16BitUuids; - } else if (BluetoothUuid.is32BitUuid(uuid)) { - ++num32BitUuids; - } else { - ++num128BitUuids; - } - } - // 16 bit service uuids are grouped into one field when doing advertising. - if (num16BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; - } - // 32 bit service uuids are grouped into one field when doing advertising. - if (num32BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; - } - // 128 bit service uuids are grouped into one field when doing advertising. - if (num128BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD - + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; - } - } - if (data.getServiceSolicitationUuids() != null) { - int num16BitUuids = 0; - int num32BitUuids = 0; - int num128BitUuids = 0; - for (ParcelUuid uuid : data.getServiceSolicitationUuids()) { - if (BluetoothUuid.is16BitUuid(uuid)) { - ++num16BitUuids; - } else if (BluetoothUuid.is32BitUuid(uuid)) { - ++num32BitUuids; - } else { - ++num128BitUuids; - } - } - // 16 bit service uuids are grouped into one field when doing advertising. - if (num16BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; - } - // 32 bit service uuids are grouped into one field when doing advertising. - if (num32BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; - } - // 128 bit service uuids are grouped into one field when doing advertising. - if (num128BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD - + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; - } - } - for (TransportDiscoveryData transportDiscoveryData : data.getTransportDiscoveryData()) { - size += OVERHEAD_BYTES_PER_FIELD + transportDiscoveryData.totalBytes(); - } - for (ParcelUuid uuid : data.getServiceData().keySet()) { - int uuidLen = BluetoothUuid.uuidToBytes(uuid).length; - size += OVERHEAD_BYTES_PER_FIELD + uuidLen - + byteLength(data.getServiceData().get(uuid)); - } - for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) { - size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH - + byteLength(data.getManufacturerSpecificData().valueAt(i)); - } - if (data.getIncludeTxPowerLevel()) { - size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. - } - if (data.getIncludeDeviceName()) { - final int length = mBluetoothAdapter.getNameLengthForAdvertise(); - if (length >= 0) { - size += OVERHEAD_BYTES_PER_FIELD + length; - } - } - return size; - } - - private int byteLength(byte[] array) { - return array == null ? 0 : array.length; - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) { - return new IAdvertisingSetCallback.Stub() { - @Override - public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) { - handler.post(new Runnable() { - @Override - public void run() { - if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { - callback.onAdvertisingSetStarted(null, 0, status); - mCallbackWrappers.remove(callback); - return; - } - - AdvertisingSet advertisingSet = new AdvertisingSet( - advertiserId, mBluetoothManager, mAttributionSource); - mAdvertisingSets.put(advertiserId, advertisingSet); - callback.onAdvertisingSetStarted(advertisingSet, txPower, status); - } - }); - } - - @Override - public void onOwnAddressRead(int advertiserId, int addressType, String address) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onOwnAddressRead(advertisingSet, addressType, address); - } - }); - } - - @Override - public void onAdvertisingSetStopped(int advertiserId) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onAdvertisingSetStopped(advertisingSet); - mAdvertisingSets.remove(advertiserId); - mCallbackWrappers.remove(callback); - } - }); - } - - @Override - public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onAdvertisingEnabled(advertisingSet, enabled, status); - } - }); - } - - @Override - public void onAdvertisingDataSet(int advertiserId, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onAdvertisingDataSet(advertisingSet, status); - } - }); - } - - @Override - public void onScanResponseDataSet(int advertiserId, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onScanResponseDataSet(advertisingSet, status); - } - }); - } - - @Override - public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status); - } - }); - } - - @Override - public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status); - } - }); - } - - @Override - public void onPeriodicAdvertisingDataSet(int advertiserId, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onPeriodicAdvertisingDataSet(advertisingSet, status); - } - }); - } - - @Override - public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status); - } - }); - } - }; - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private void postStartSetFailure(Handler handler, final AdvertisingSetCallback callback, - final int error) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onAdvertisingSetStarted(null, 0, error); - } - }); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private void postStartFailure(final AdvertiseCallback callback, final int error) { - mHandler.post(new Runnable() { - @Override - public void run() { - callback.onStartFailure(error); - } - }); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private void postStartSuccess(final AdvertiseCallback callback, - final AdvertiseSettings settings) { - mHandler.post(new Runnable() { - - @Override - public void run() { - callback.onStartSuccess(settings); - } - }); - } -} diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java deleted file mode 100644 index 540e5a778c27..000000000000 --- a/core/java/android/bluetooth/le/BluetoothLeScanner.java +++ /dev/null @@ -1,658 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package android.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.app.PendingIntent; -import android.bluetooth.Attributable; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.annotations.RequiresBluetoothLocationPermission; -import android.bluetooth.annotations.RequiresBluetoothScanPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.content.AttributionSource; -import android.os.Handler; -import android.os.Looper; -import android.os.RemoteException; -import android.os.WorkSource; -import android.util.Log; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * This class provides methods to perform scan related operations for Bluetooth LE devices. An - * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It - * can also request different types of callbacks for delivering the result. - * <p> - * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of - * {@link BluetoothLeScanner}. - * - * @see ScanFilter - */ -public final class BluetoothLeScanner { - - private static final String TAG = "BluetoothLeScanner"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Extra containing a list of ScanResults. It can have one or more results if there was no - * error. In case of error, {@link #EXTRA_ERROR_CODE} will contain the error code and this - * extra will not be available. - */ - public static final String EXTRA_LIST_SCAN_RESULT = - "android.bluetooth.le.extra.LIST_SCAN_RESULT"; - - /** - * Optional extra indicating the error code, if any. The error code will be one of the - * SCAN_FAILED_* codes in {@link ScanCallback}. - */ - public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE"; - - /** - * Optional extra indicating the callback type, which will be one of - * CALLBACK_TYPE_* constants in {@link ScanSettings}. - * - * @see ScanCallback#onScanResult(int, ScanResult) - */ - public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE"; - - private final BluetoothAdapter mBluetoothAdapter; - private final IBluetoothManager mBluetoothManager; - private final AttributionSource mAttributionSource; - - private final Handler mHandler; - private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients; - - /** - * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. - * - * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. - * @param opPackageName The opPackageName of the context this object was created from - * @param featureId The featureId of the context this object was created from - * @hide - */ - public BluetoothLeScanner(BluetoothAdapter bluetoothAdapter) { - mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); - mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); - mAttributionSource = mBluetoothAdapter.getAttributionSource(); - mHandler = new Handler(Looper.getMainLooper()); - mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); - } - - /** - * Start Bluetooth LE scan with default parameters and no filters. The scan results will be - * delivered through {@code callback}. For unfiltered scans, scanning is stopped on screen - * off to save power. Scanning is resumed when screen is turned on again. To avoid this, use - * {@link #startScan(List, ScanSettings, ScanCallback)} with desired {@link ScanFilter}. - * <p> - * An app must have - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission - * in order to get results. An App targeting Android Q or later must have - * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission - * in order to get results. - * - * @param callback Callback used to deliver scan results. - * @throws IllegalArgumentException If {@code callback} is null. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void startScan(final ScanCallback callback) { - startScan(null, new ScanSettings.Builder().build(), callback); - } - - /** - * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. - * For unfiltered scans, scanning is stopped on screen off to save power. Scanning is - * resumed when screen is turned on again. To avoid this, do filetered scanning by - * using proper {@link ScanFilter}. - * <p> - * An app must have - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission - * in order to get results. An App targeting Android Q or later must have - * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission - * in order to get results. - * - * @param filters {@link ScanFilter}s for finding exact BLE devices. - * @param settings Settings for the scan. - * @param callback Callback used to deliver scan results. - * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void startScan(List<ScanFilter> filters, ScanSettings settings, - final ScanCallback callback) { - startScan(filters, settings, null, callback, /*callbackIntent=*/ null); - } - - /** - * Start Bluetooth LE scan using a {@link PendingIntent}. The scan results will be delivered via - * the PendingIntent. Use this method of scanning if your process is not always running and it - * should be started when scan results are available. - * <p> - * An app must have - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission - * in order to get results. An App targeting Android Q or later must have - * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission - * in order to get results. - * <p> - * When the PendingIntent is delivered, the Intent passed to the receiver or activity - * will contain one or more of the extras {@link #EXTRA_CALLBACK_TYPE}, - * {@link #EXTRA_ERROR_CODE} and {@link #EXTRA_LIST_SCAN_RESULT} to indicate the result of - * the scan. - * - * @param filters Optional list of ScanFilters for finding exact BLE devices. - * @param settings Optional settings for the scan. - * @param callbackIntent The PendingIntent to deliver the result to. - * @return Returns 0 for success or an error code from {@link ScanCallback} if the scan request - * could not be sent. - * @see #stopScan(PendingIntent) - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings, - @NonNull PendingIntent callbackIntent) { - return startScan(filters, - settings != null ? settings : new ScanSettings.Builder().build(), - null, null, callbackIntent); - } - - /** - * Start Bluetooth LE scan. Same as {@link #startScan(ScanCallback)} but allows the caller to - * specify on behalf of which application(s) the work is being done. - * - * @param workSource {@link WorkSource} identifying the application(s) for which to blame for - * the scan. - * @param callback Callback used to deliver scan results. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.UPDATE_DEVICE_STATS - }) - public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) { - startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback); - } - - /** - * Start Bluetooth LE scan. Same as {@link #startScan(List, ScanSettings, ScanCallback)} but - * allows the caller to specify on behalf of which application(s) the work is being done. - * - * @param filters {@link ScanFilter}s for finding exact BLE devices. - * @param settings Settings for the scan. - * @param workSource {@link WorkSource} identifying the application(s) for which to blame for - * the scan. - * @param callback Callback used to deliver scan results. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.UPDATE_DEVICE_STATS - }) - @SuppressLint("AndroidFrameworkRequiresPermission") - public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings, - final WorkSource workSource, final ScanCallback callback) { - startScan(filters, settings, workSource, callback, null); - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - private int startScan(List<ScanFilter> filters, ScanSettings settings, - final WorkSource workSource, final ScanCallback callback, - final PendingIntent callbackIntent) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - if (callback == null && callbackIntent == null) { - throw new IllegalArgumentException("callback is null"); - } - if (settings == null) { - throw new IllegalArgumentException("settings is null"); - } - synchronized (mLeScanClients) { - if (callback != null && mLeScanClients.containsKey(callback)) { - return postCallbackErrorOrReturn(callback, - ScanCallback.SCAN_FAILED_ALREADY_STARTED); - } - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - gatt = null; - } - if (gatt == null) { - return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); - } - if (!isSettingsConfigAllowedForScan(settings)) { - return postCallbackErrorOrReturn(callback, - ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); - } - if (!isHardwareResourcesAvailableForScan(settings)) { - return postCallbackErrorOrReturn(callback, - ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES); - } - if (!isSettingsAndFilterComboAllowed(settings, filters)) { - return postCallbackErrorOrReturn(callback, - ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); - } - if (callback != null) { - BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters, - settings, workSource, callback); - wrapper.startRegistration(); - } else { - try { - gatt.startScanForIntent(callbackIntent, settings, filters, - mAttributionSource); - } catch (RemoteException e) { - return ScanCallback.SCAN_FAILED_INTERNAL_ERROR; - } - } - } - return ScanCallback.NO_ERROR; - } - - /** - * Stops an ongoing Bluetooth LE scan. - * - * @param callback - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void stopScan(ScanCallback callback) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - synchronized (mLeScanClients) { - BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); - if (wrapper == null) { - if (DBG) Log.d(TAG, "could not find callback wrapper"); - return; - } - wrapper.stopLeScan(); - } - } - - /** - * Stops an ongoing Bluetooth LE scan started using a PendingIntent. When creating the - * PendingIntent parameter, please do not use the FLAG_CANCEL_CURRENT flag. Otherwise, the stop - * scan may have no effect. - * - * @param callbackIntent The PendingIntent that was used to start the scan. - * @see #startScan(List, ScanSettings, PendingIntent) - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void stopScan(PendingIntent callbackIntent) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - gatt.stopScanForIntent(callbackIntent, mAttributionSource); - } catch (RemoteException e) { - } - } - - /** - * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth - * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data - * will be delivered through the {@code callback}. - * - * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one - * used to start scan. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void flushPendingScanResults(ScanCallback callback) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null!"); - } - synchronized (mLeScanClients) { - BleScanCallbackWrapper wrapper = mLeScanClients.get(callback); - if (wrapper == null) { - return; - } - wrapper.flushPendingBatchResults(); - } - } - - /** - * Start truncated scan. - * - * @deprecated this is not used anywhere - * - * @hide - */ - @Deprecated - @SystemApi - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings, - final ScanCallback callback) { - int filterSize = truncatedFilters.size(); - List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize); - for (TruncatedFilter filter : truncatedFilters) { - scanFilters.add(filter.getFilter()); - } - startScan(scanFilters, settings, null, callback, null); - } - - /** - * Cleans up scan clients. Should be called when bluetooth is down. - * - * @hide - */ - @RequiresNoPermission - public void cleanup() { - mLeScanClients.clear(); - } - - /** - * Bluetooth GATT interface callbacks - */ - @SuppressLint("AndroidFrameworkRequiresPermission") - private class BleScanCallbackWrapper extends IScannerCallback.Stub { - private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000; - - private final ScanCallback mScanCallback; - private final List<ScanFilter> mFilters; - private final WorkSource mWorkSource; - private ScanSettings mSettings; - private IBluetoothGatt mBluetoothGatt; - - // mLeHandle 0: not registered - // -2: registration failed because app is scanning to frequently - // -1: scan stopped or registration failed - // > 0: registered and scan started - private int mScannerId; - - public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, - List<ScanFilter> filters, ScanSettings settings, - WorkSource workSource, ScanCallback scanCallback) { - mBluetoothGatt = bluetoothGatt; - mFilters = filters; - mSettings = settings; - mWorkSource = workSource; - mScanCallback = scanCallback; - mScannerId = 0; - } - - public void startRegistration() { - synchronized (this) { - // Scan stopped. - if (mScannerId == -1 || mScannerId == -2) return; - try { - mBluetoothGatt.registerScanner(this, mWorkSource, mAttributionSource); - wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS); - } catch (InterruptedException | RemoteException e) { - Log.e(TAG, "application registeration exception", e); - postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); - } - if (mScannerId > 0) { - mLeScanClients.put(mScanCallback, this); - } else { - // Registration timed out or got exception, reset RscannerId to -1 so no - // subsequent operations can proceed. - if (mScannerId == 0) mScannerId = -1; - - // If scanning too frequently, don't report anything to the app. - if (mScannerId == -2) return; - - postCallbackError(mScanCallback, - ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); - } - } - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void stopLeScan() { - synchronized (this) { - if (mScannerId <= 0) { - Log.e(TAG, "Error state, mLeHandle: " + mScannerId); - return; - } - try { - mBluetoothGatt.stopScan(mScannerId, mAttributionSource); - mBluetoothGatt.unregisterScanner(mScannerId, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to stop scan and unregister", e); - } - mScannerId = -1; - } - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - void flushPendingBatchResults() { - synchronized (this) { - if (mScannerId <= 0) { - Log.e(TAG, "Error state, mLeHandle: " + mScannerId); - return; - } - try { - mBluetoothGatt.flushPendingBatchResults(mScannerId, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get pending scan results", e); - } - } - } - - /** - * Application interface registered - app is ready to go - */ - @Override - public void onScannerRegistered(int status, int scannerId) { - Log.d(TAG, "onScannerRegistered() - status=" + status - + " scannerId=" + scannerId + " mScannerId=" + mScannerId); - synchronized (this) { - if (status == BluetoothGatt.GATT_SUCCESS) { - try { - if (mScannerId == -1) { - // Registration succeeds after timeout, unregister scanner. - mBluetoothGatt.unregisterScanner(scannerId, mAttributionSource); - } else { - mScannerId = scannerId; - mBluetoothGatt.startScan(mScannerId, mSettings, mFilters, - mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "fail to start le scan: " + e); - mScannerId = -1; - } - } else if (status == ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY) { - // applicaiton was scanning too frequently - mScannerId = -2; - } else { - // registration failed - mScannerId = -1; - } - notifyAll(); - } - } - - /** - * Callback reporting an LE scan result. - * - * @hide - */ - @Override - public void onScanResult(final ScanResult scanResult) { - Attributable.setAttributionSource(scanResult, mAttributionSource); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onScanResult() - mScannerId=" + mScannerId); - } - if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString()); - - // Check null in case the scan has been stopped - synchronized (this) { - if (mScannerId <= 0) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Ignoring result as scan stopped."); - } - return; - }; - } - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onScanResult() - handler run"); - } - mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); - } - }); - } - - @Override - public void onBatchScanResults(final List<ScanResult> results) { - Attributable.setAttributionSource(results, mAttributionSource); - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - mScanCallback.onBatchScanResults(results); - } - }); - } - - @Override - public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) { - Attributable.setAttributionSource(scanResult, mAttributionSource); - if (VDBG) { - Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + " " + scanResult.toString()); - } - - // Check null in case the scan has been stopped - synchronized (this) { - if (mScannerId <= 0) { - return; - } - } - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - if (onFound) { - mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, - scanResult); - } else { - mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, - scanResult); - } - } - }); - } - - @Override - public void onScanManagerErrorCallback(final int errorCode) { - if (VDBG) { - Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode); - } - synchronized (this) { - if (mScannerId <= 0) { - return; - } - } - postCallbackError(mScanCallback, errorCode); - } - } - - private int postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode) { - if (callback == null) { - return errorCode; - } else { - postCallbackError(callback, errorCode); - return ScanCallback.NO_ERROR; - } - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private void postCallbackError(final ScanCallback callback, final int errorCode) { - mHandler.post(new Runnable() { - @Override - public void run() { - callback.onScanFailed(errorCode); - } - }); - } - - private boolean isSettingsConfigAllowedForScan(ScanSettings settings) { - if (mBluetoothAdapter.isOffloadedFilteringSupported()) { - return true; - } - final int callbackType = settings.getCallbackType(); - // Only support regular scan if no offloaded filter support. - if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES - && settings.getReportDelayMillis() == 0) { - return true; - } - return false; - } - - private boolean isSettingsAndFilterComboAllowed(ScanSettings settings, - List<ScanFilter> filterList) { - final int callbackType = settings.getCallbackType(); - // If onlost/onfound is requested, a non-empty filter is expected - if ((callbackType & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH - | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) != 0) { - if (filterList == null) { - return false; - } - for (ScanFilter filter : filterList) { - if (filter.isAllFieldsEmpty()) { - return false; - } - } - } - return true; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) { - final int callbackType = settings.getCallbackType(); - if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 - || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { - // For onlost/onfound, we required hw support be available - return (mBluetoothAdapter.isOffloadedFilteringSupported() - && mBluetoothAdapter.isHardwareTrackingFiltersAvailable()); - } - return true; - } -} diff --git a/core/java/android/bluetooth/le/BluetoothLeUtils.java b/core/java/android/bluetooth/le/BluetoothLeUtils.java deleted file mode 100644 index ed50b09597bb..000000000000 --- a/core/java/android/bluetooth/le/BluetoothLeUtils.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.bluetooth.BluetoothAdapter; -import android.util.SparseArray; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; - -/** - * Helper class for Bluetooth LE utils. - * - * @hide - */ -public class BluetoothLeUtils { - - /** - * Returns a string composed from a {@link SparseArray}. - */ - static String toString(SparseArray<byte[]> array) { - if (array == null) { - return "null"; - } - if (array.size() == 0) { - return "{}"; - } - StringBuilder buffer = new StringBuilder(); - buffer.append('{'); - for (int i = 0; i < array.size(); ++i) { - buffer.append(array.keyAt(i)).append("=").append(Arrays.toString(array.valueAt(i))); - } - buffer.append('}'); - return buffer.toString(); - } - - /** - * Returns a string composed from a {@link Map}. - */ - static <T> String toString(Map<T, byte[]> map) { - if (map == null) { - return "null"; - } - if (map.isEmpty()) { - return "{}"; - } - StringBuilder buffer = new StringBuilder(); - buffer.append('{'); - Iterator<Map.Entry<T, byte[]>> it = map.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<T, byte[]> entry = it.next(); - Object key = entry.getKey(); - buffer.append(key).append("=").append(Arrays.toString(map.get(key))); - if (it.hasNext()) { - buffer.append(", "); - } - } - buffer.append('}'); - return buffer.toString(); - } - - /** - * Check whether two {@link SparseArray} equal. - */ - static boolean equals(SparseArray<byte[]> array, SparseArray<byte[]> otherArray) { - if (array == otherArray) { - return true; - } - if (array == null || otherArray == null) { - return false; - } - if (array.size() != otherArray.size()) { - return false; - } - - // Keys are guaranteed in ascending order when indices are in ascending order. - for (int i = 0; i < array.size(); ++i) { - if (array.keyAt(i) != otherArray.keyAt(i) - || !Arrays.equals(array.valueAt(i), otherArray.valueAt(i))) { - return false; - } - } - return true; - } - - /** - * Check whether two {@link Map} equal. - */ - static <T> boolean equals(Map<T, byte[]> map, Map<T, byte[]> otherMap) { - if (map == otherMap) { - return true; - } - if (map == null || otherMap == null) { - return false; - } - if (map.size() != otherMap.size()) { - return false; - } - Set<T> keys = map.keySet(); - if (!keys.equals(otherMap.keySet())) { - return false; - } - for (T key : keys) { - if (!Objects.deepEquals(map.get(key), otherMap.get(key))) { - return false; - } - } - return true; - } - - /** - * Ensure Bluetooth is turned on. - * - * @throws IllegalStateException If {@code adapter} is null or Bluetooth state is not {@link - * BluetoothAdapter#STATE_ON}. - */ - static void checkAdapterStateOn(BluetoothAdapter adapter) { - if (adapter == null || !adapter.isLeEnabled()) { - throw new IllegalStateException("BT Adapter is not turned ON"); - } - } - - /** - * Compares two UUIDs with a UUID mask. - * - * @param data first {@link #UUID} to compare. - * @param uuid second {@link #UUID} to compare. - * @param mask mask {@link #UUID}. - * @return true if both UUIDs are equals when masked, false otherwise. - */ - static boolean maskedEquals(UUID data, UUID uuid, UUID mask) { - if (mask == null) { - return Objects.equals(data, uuid); - } - return (data.getLeastSignificantBits() & mask.getLeastSignificantBits()) - == (uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) - && (data.getMostSignificantBits() & mask.getMostSignificantBits()) - == (uuid.getMostSignificantBits() & mask.getMostSignificantBits()); - } -} diff --git a/core/java/android/bluetooth/le/OWNERS b/core/java/android/bluetooth/le/OWNERS deleted file mode 100644 index 3523ee0640ab..000000000000 --- a/core/java/android/bluetooth/le/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# Bug component: 27441 - -zachoverflow@google.com -siyuanh@google.com diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.java b/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.java deleted file mode 100644 index 14ac911fcb7f..000000000000 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.bluetooth.BluetoothDevice; - -/** - * Bluetooth LE periodic advertising callbacks, used to deliver periodic - * advertising operation status. - * - * @hide - * @see PeriodicAdvertisingManager#createSync - */ -public abstract class PeriodicAdvertisingCallback { - - /** - * The requested operation was successful. - * - * @hide - */ - public static final int SYNC_SUCCESS = 0; - - /** - * Sync failed to be established because remote device did not respond. - */ - public static final int SYNC_NO_RESPONSE = 1; - - /** - * Sync failed to be established because controller can't support more syncs. - */ - public static final int SYNC_NO_RESOURCES = 2; - - - /** - * Callback when synchronization was established. - * - * @param syncHandle handle used to identify this synchronization. - * @param device remote device. - * @param advertisingSid synchronized advertising set id. - * @param skip The number of periodic advertising packets that can be skipped after a successful - * receive in force. @see PeriodicAdvertisingManager#createSync - * @param timeout Synchronization timeout for the periodic advertising in force. One unit is - * 10ms. @see PeriodicAdvertisingManager#createSync - * @param timeout - * @param status operation status. - */ - public void onSyncEstablished(int syncHandle, BluetoothDevice device, - int advertisingSid, int skip, int timeout, - int status) { - } - - /** - * Callback when periodic advertising report is received. - * - * @param report periodic advertising report. - */ - public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { - } - - /** - * Callback when periodic advertising synchronization was lost. - * - * @param syncHandle handle used to identify this synchronization. - */ - public void onSyncLost(int syncHandle) { - } -} diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java deleted file mode 100644 index bbd31170bb41..000000000000 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package android.bluetooth.le; - -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.Attributable; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.annotations.RequiresBluetoothLocationPermission; -import android.bluetooth.annotations.RequiresBluetoothScanPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.content.AttributionSource; -import android.os.Handler; -import android.os.Looper; -import android.os.RemoteException; -import android.util.Log; - -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This class provides methods to perform periodic advertising related - * operations. An application can register for periodic advertisements using - * {@link PeriodicAdvertisingManager#registerSync}. - * <p> - * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an - * instance of {@link PeriodicAdvertisingManager}. - * - * @hide - */ -public final class PeriodicAdvertisingManager { - - private static final String TAG = "PeriodicAdvertisingManager"; - - private static final int SKIP_MIN = 0; - private static final int SKIP_MAX = 499; - private static final int TIMEOUT_MIN = 10; - private static final int TIMEOUT_MAX = 16384; - - private static final int SYNC_STARTING = -1; - - private final BluetoothAdapter mBluetoothAdapter; - private final IBluetoothManager mBluetoothManager; - private final AttributionSource mAttributionSource; - - /* maps callback, to callback wrapper and sync handle */ - Map<PeriodicAdvertisingCallback, - IPeriodicAdvertisingCallback /* callbackWrapper */> mCallbackWrappers; - - /** - * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. - * - * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. - * @hide - */ - public PeriodicAdvertisingManager(BluetoothAdapter bluetoothAdapter) { - mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); - mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); - mAttributionSource = mBluetoothAdapter.getAttributionSource(); - mCallbackWrappers = new IdentityHashMap<>(); - } - - /** - * Synchronize with periodic advertising pointed to by the {@code scanResult}. - * The {@code scanResult} used must contain a valid advertisingSid. First - * call to registerSync will use the {@code skip} and {@code timeout} provided. - * Subsequent calls from other apps, trying to sync with same set will reuse - * existing sync, thus {@code skip} and {@code timeout} values will not take - * effect. The values in effect will be returned in - * {@link PeriodicAdvertisingCallback#onSyncEstablished}. - * - * @param scanResult Scan result containing advertisingSid. - * @param skip The number of periodic advertising packets that can be skipped after a successful - * receive. Must be between 0 and 499. - * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must - * be between 10 (100ms) and 16384 (163.84s). - * @param callback Callback used to deliver all operations status. - * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or - * {@code timeout} is invalid or {@code callback} is null. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void registerSync(ScanResult scanResult, int skip, int timeout, - PeriodicAdvertisingCallback callback) { - registerSync(scanResult, skip, timeout, callback, null); - } - - /** - * Synchronize with periodic advertising pointed to by the {@code scanResult}. - * The {@code scanResult} used must contain a valid advertisingSid. First - * call to registerSync will use the {@code skip} and {@code timeout} provided. - * Subsequent calls from other apps, trying to sync with same set will reuse - * existing sync, thus {@code skip} and {@code timeout} values will not take - * effect. The values in effect will be returned in - * {@link PeriodicAdvertisingCallback#onSyncEstablished}. - * - * @param scanResult Scan result containing advertisingSid. - * @param skip The number of periodic advertising packets that can be skipped after a successful - * receive. Must be between 0 and 499. - * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must - * be between 10 (100ms) and 16384 (163.84s). - * @param callback Callback used to deliver all operations status. - * @param handler thread upon which the callbacks will be invoked. - * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or - * {@code timeout} is invalid or {@code callback} is null. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void registerSync(ScanResult scanResult, int skip, int timeout, - PeriodicAdvertisingCallback callback, Handler handler) { - if (callback == null) { - throw new IllegalArgumentException("callback can't be null"); - } - - if (scanResult == null) { - throw new IllegalArgumentException("scanResult can't be null"); - } - - if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) { - throw new IllegalArgumentException("scanResult must contain a valid sid"); - } - - if (skip < SKIP_MIN || skip > SKIP_MAX) { - throw new IllegalArgumentException( - "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX); - } - - if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) { - throw new IllegalArgumentException( - "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX); - } - - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth gatt - ", e); - callback.onSyncEstablished(0, scanResult.getDevice(), scanResult.getAdvertisingSid(), - skip, timeout, - PeriodicAdvertisingCallback.SYNC_NO_RESOURCES); - return; - } - - if (handler == null) { - handler = new Handler(Looper.getMainLooper()); - } - - IPeriodicAdvertisingCallback wrapped = wrap(callback, handler); - mCallbackWrappers.put(callback, wrapped); - - try { - gatt.registerSync( - scanResult, skip, timeout, wrapped, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register sync - ", e); - return; - } - } - - /** - * Cancel pending attempt to create sync, or terminate existing sync. - * - * @param callback Callback used to deliver all operations status. - * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered - * callback. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void unregisterSync(PeriodicAdvertisingCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback can't be null"); - } - - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth gatt - ", e); - return; - } - - IPeriodicAdvertisingCallback wrapper = mCallbackWrappers.remove(callback); - if (wrapper == null) { - throw new IllegalArgumentException("callback was not properly registered"); - } - - try { - gatt.unregisterSync(wrapper, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to cancel sync creation - ", e); - return; - } - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, - Handler handler) { - return new IPeriodicAdvertisingCallback.Stub() { - public void onSyncEstablished(int syncHandle, BluetoothDevice device, - int advertisingSid, int skip, int timeout, int status) { - Attributable.setAttributionSource(device, mAttributionSource); - handler.post(new Runnable() { - @Override - public void run() { - callback.onSyncEstablished(syncHandle, device, advertisingSid, skip, - timeout, - status); - - if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) { - // App can still unregister the sync until notified it failed. Remove - // callback - // after app was notifed. - mCallbackWrappers.remove(callback); - } - } - }); - } - - public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onPeriodicAdvertisingReport(report); - } - }); - } - - public void onSyncLost(int syncHandle) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onSyncLost(syncHandle); - // App can still unregister the sync until notified it's lost. - // Remove callback after app was notifed. - mCallbackWrappers.remove(callback); - } - }); - } - }; - } -} diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java deleted file mode 100644 index 4e64dbed7017..000000000000 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic - * advertising preferences for each Bluetooth LE advertising set. Use {@link - * PeriodicAdvertisingParameters.Builder} to create an instance of this class. - */ -public final class PeriodicAdvertisingParameters implements Parcelable { - - private static final int INTERVAL_MIN = 80; - private static final int INTERVAL_MAX = 65519; - - private final boolean mIncludeTxPower; - private final int mInterval; - - private PeriodicAdvertisingParameters(boolean includeTxPower, int interval) { - mIncludeTxPower = includeTxPower; - mInterval = interval; - } - - private PeriodicAdvertisingParameters(Parcel in) { - mIncludeTxPower = in.readInt() != 0; - mInterval = in.readInt(); - } - - /** - * Returns whether the TX Power will be included. - */ - public boolean getIncludeTxPower() { - return mIncludeTxPower; - } - - /** - * Returns the periodic advertising interval, in 1.25ms unit. - * Valid values are from 80 (100ms) to 65519 (81.89875s). - */ - public int getInterval() { - return mInterval; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mIncludeTxPower ? 1 : 0); - dest.writeInt(mInterval); - } - - public static final Parcelable - .Creator<PeriodicAdvertisingParameters> CREATOR = - new Creator<PeriodicAdvertisingParameters>() { - @Override - public PeriodicAdvertisingParameters[] newArray(int size) { - return new PeriodicAdvertisingParameters[size]; - } - - @Override - public PeriodicAdvertisingParameters createFromParcel(Parcel in) { - return new PeriodicAdvertisingParameters(in); - } - }; - - public static final class Builder { - private boolean mIncludeTxPower = false; - private int mInterval = INTERVAL_MAX; - - /** - * Whether the transmission power level should be included in the periodic - * packet. - */ - public Builder setIncludeTxPower(boolean includeTxPower) { - mIncludeTxPower = includeTxPower; - return this; - } - - /** - * Set advertising interval for periodic advertising, in 1.25ms unit. - * Valid values are from 80 (100ms) to 65519 (81.89875s). - * Value from range [interval, interval+20ms] will be picked as the actual value. - * - * @throws IllegalArgumentException If the interval is invalid. - */ - public Builder setInterval(int interval) { - if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) { - throw new IllegalArgumentException("Invalid interval (must be " + INTERVAL_MIN - + "-" + INTERVAL_MAX + ")"); - } - mInterval = interval; - return this; - } - - /** - * Build the {@link AdvertisingSetParameters} object. - */ - public PeriodicAdvertisingParameters build() { - return new PeriodicAdvertisingParameters(mIncludeTxPower, mInterval); - } - } -} diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java b/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java deleted file mode 100644 index 54b953c25c27..000000000000 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Objects; - -/** - * PeriodicAdvertisingReport for Bluetooth LE synchronized advertising. - * - * @hide - */ -public final class PeriodicAdvertisingReport implements Parcelable { - - /** - * The data returned is complete - */ - public static final int DATA_COMPLETE = 0; - - /** - * The data returned is incomplete. The controller was unsuccessfull to - * receive all chained packets, returning only partial data. - */ - public static final int DATA_INCOMPLETE_TRUNCATED = 2; - - private int mSyncHandle; - private int mTxPower; - private int mRssi; - private int mDataStatus; - - // periodic advertising data. - @Nullable - private ScanRecord mData; - - // Device timestamp when the result was last seen. - private long mTimestampNanos; - - /** - * Constructor of periodic advertising result. - */ - public PeriodicAdvertisingReport(int syncHandle, int txPower, int rssi, - int dataStatus, ScanRecord data) { - mSyncHandle = syncHandle; - mTxPower = txPower; - mRssi = rssi; - mDataStatus = dataStatus; - mData = data; - } - - private PeriodicAdvertisingReport(Parcel in) { - readFromParcel(in); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mSyncHandle); - dest.writeInt(mTxPower); - dest.writeInt(mRssi); - dest.writeInt(mDataStatus); - if (mData != null) { - dest.writeInt(1); - dest.writeByteArray(mData.getBytes()); - } else { - dest.writeInt(0); - } - } - - private void readFromParcel(Parcel in) { - mSyncHandle = in.readInt(); - mTxPower = in.readInt(); - mRssi = in.readInt(); - mDataStatus = in.readInt(); - if (in.readInt() == 1) { - mData = ScanRecord.parseFromBytes(in.createByteArray()); - } - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Returns the synchronization handle. - */ - public int getSyncHandle() { - return mSyncHandle; - } - - /** - * Returns the transmit power in dBm. The valid range is [-127, 126]. Value - * of 127 means information was not available. - */ - public int getTxPower() { - return mTxPower; - } - - /** - * Returns the received signal strength in dBm. The valid range is [-127, 20]. - */ - public int getRssi() { - return mRssi; - } - - /** - * Returns the data status. Can be one of {@link PeriodicAdvertisingReport#DATA_COMPLETE} - * or {@link PeriodicAdvertisingReport#DATA_INCOMPLETE_TRUNCATED}. - */ - public int getDataStatus() { - return mDataStatus; - } - - /** - * Returns the data contained in this periodic advertising report. - */ - @Nullable - public ScanRecord getData() { - return mData; - } - - /** - * Returns timestamp since boot when the scan record was observed. - */ - public long getTimestampNanos() { - return mTimestampNanos; - } - - @Override - public int hashCode() { - return Objects.hash(mSyncHandle, mTxPower, mRssi, mDataStatus, mData, mTimestampNanos); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - PeriodicAdvertisingReport other = (PeriodicAdvertisingReport) obj; - return (mSyncHandle == other.mSyncHandle) - && (mTxPower == other.mTxPower) - && (mRssi == other.mRssi) - && (mDataStatus == other.mDataStatus) - && Objects.equals(mData, other.mData) - && (mTimestampNanos == other.mTimestampNanos); - } - - @Override - public String toString() { - return "PeriodicAdvertisingReport{syncHandle=" + mSyncHandle - + ", txPower=" + mTxPower + ", rssi=" + mRssi + ", dataStatus=" + mDataStatus - + ", data=" + Objects.toString(mData) + ", timestampNanos=" + mTimestampNanos + '}'; - } - - public static final @android.annotation.NonNull Parcelable.Creator<PeriodicAdvertisingReport> CREATOR = - new Creator<PeriodicAdvertisingReport>() { - @Override - public PeriodicAdvertisingReport createFromParcel(Parcel source) { - return new PeriodicAdvertisingReport(source); - } - - @Override - public PeriodicAdvertisingReport[] newArray(int size) { - return new PeriodicAdvertisingReport[size]; - } - }; -} diff --git a/core/java/android/bluetooth/le/ResultStorageDescriptor.java b/core/java/android/bluetooth/le/ResultStorageDescriptor.java deleted file mode 100644 index f65048975deb..000000000000 --- a/core/java/android/bluetooth/le/ResultStorageDescriptor.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Describes the way to store scan result. - * - * @deprecated this is not used anywhere - * - * @hide - */ -@Deprecated -@SystemApi -public final class ResultStorageDescriptor implements Parcelable { - private int mType; - private int mOffset; - private int mLength; - - public int getType() { - return mType; - } - - public int getOffset() { - return mOffset; - } - - public int getLength() { - return mLength; - } - - /** - * Constructor of {@link ResultStorageDescriptor} - * - * @param type Type of the data. - * @param offset Offset from start of the advertise packet payload. - * @param length Byte length of the data - */ - public ResultStorageDescriptor(int type, int offset, int length) { - mType = type; - mOffset = offset; - mLength = length; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mType); - dest.writeInt(mOffset); - dest.writeInt(mLength); - } - - private ResultStorageDescriptor(Parcel in) { - ReadFromParcel(in); - } - - private void ReadFromParcel(Parcel in) { - mType = in.readInt(); - mOffset = in.readInt(); - mLength = in.readInt(); - } - - public static final @android.annotation.NonNull Parcelable.Creator<ResultStorageDescriptor> CREATOR = - new Creator<ResultStorageDescriptor>() { - @Override - public ResultStorageDescriptor createFromParcel(Parcel source) { - return new ResultStorageDescriptor(source); - } - - @Override - public ResultStorageDescriptor[] newArray(int size) { - return new ResultStorageDescriptor[size]; - } - }; -} diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java deleted file mode 100644 index 53d9310a1236..000000000000 --- a/core/java/android/bluetooth/le/ScanCallback.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import java.util.List; - -/** - * Bluetooth LE scan callbacks. Scan results are reported using these callbacks. - * - * @see BluetoothLeScanner#startScan - */ -public abstract class ScanCallback { - /** - * Fails to start scan as BLE scan with the same settings is already started by the app. - */ - public static final int SCAN_FAILED_ALREADY_STARTED = 1; - - /** - * Fails to start scan as app cannot be registered. - */ - public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; - - /** - * Fails to start scan due an internal error - */ - public static final int SCAN_FAILED_INTERNAL_ERROR = 3; - - /** - * Fails to start power optimized scan as this feature is not supported. - */ - public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4; - - /** - * Fails to start scan as it is out of hardware resources. - * - * @hide - */ - public static final int SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES = 5; - - /** - * Fails to start scan as application tries to scan too frequently. - * @hide - */ - public static final int SCAN_FAILED_SCANNING_TOO_FREQUENTLY = 6; - - static final int NO_ERROR = 0; - - /** - * Callback when a BLE advertisement has been found. - * - * @param callbackType Determines how this callback was triggered. Could be one of {@link - * ScanSettings#CALLBACK_TYPE_ALL_MATCHES}, {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH} or - * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST} - * @param result A Bluetooth LE scan result. - */ - public void onScanResult(int callbackType, ScanResult result) { - } - - /** - * Callback when batch results are delivered. - * - * @param results List of scan results that are previously scanned. - */ - public void onBatchScanResults(List<ScanResult> results) { - } - - /** - * Callback when scan could not be started. - * - * @param errorCode Error code (one of SCAN_FAILED_*) for scan failure. - */ - public void onScanFailed(int errorCode) { - } -} diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java deleted file mode 100644 index 675fe05a7dec..000000000000 --- a/core/java/android/bluetooth/le/ScanFilter.java +++ /dev/null @@ -1,910 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import static java.util.Objects.requireNonNull; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothDevice.AddressType; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -/** - * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to - * restrict scan results to only those that are of interest to them. - * <p> - * Current filtering on the following fields are supported: - * <li>Service UUIDs which identify the bluetooth gatt services running on the device. - * <li>Name of remote Bluetooth LE device. - * <li>Mac address of the remote device. - * <li>Service data which is the data associated with a service. - * <li>Manufacturer specific data which is the data associated with a particular manufacturer. - * - * @see ScanResult - * @see BluetoothLeScanner - */ -public final class ScanFilter implements Parcelable { - - @Nullable - private final String mDeviceName; - - @Nullable - private final String mDeviceAddress; - - private final @AddressType int mAddressType; - - @Nullable - private final byte[] mIrk; - - @Nullable - private final ParcelUuid mServiceUuid; - @Nullable - private final ParcelUuid mServiceUuidMask; - - @Nullable - private final ParcelUuid mServiceSolicitationUuid; - @Nullable - private final ParcelUuid mServiceSolicitationUuidMask; - - @Nullable - private final ParcelUuid mServiceDataUuid; - @Nullable - private final byte[] mServiceData; - @Nullable - private final byte[] mServiceDataMask; - - private final int mManufacturerId; - @Nullable - private final byte[] mManufacturerData; - @Nullable - private final byte[] mManufacturerDataMask; - - /** @hide */ - public static final ScanFilter EMPTY = new ScanFilter.Builder().build(); - - private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, - ParcelUuid uuidMask, ParcelUuid solicitationUuid, - ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid, - byte[] serviceData, byte[] serviceDataMask, - int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, - @AddressType int addressType, @Nullable byte[] irk) { - mDeviceName = name; - mServiceUuid = uuid; - mServiceUuidMask = uuidMask; - mServiceSolicitationUuid = solicitationUuid; - mServiceSolicitationUuidMask = solicitationUuidMask; - mDeviceAddress = deviceAddress; - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - mServiceDataMask = serviceDataMask; - mManufacturerId = manufacturerId; - mManufacturerData = manufacturerData; - mManufacturerDataMask = manufacturerDataMask; - mAddressType = addressType; - mIrk = irk; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mDeviceName == null ? 0 : 1); - if (mDeviceName != null) { - dest.writeString(mDeviceName); - } - dest.writeInt(mDeviceAddress == null ? 0 : 1); - if (mDeviceAddress != null) { - dest.writeString(mDeviceAddress); - } - dest.writeInt(mServiceUuid == null ? 0 : 1); - if (mServiceUuid != null) { - dest.writeParcelable(mServiceUuid, flags); - dest.writeInt(mServiceUuidMask == null ? 0 : 1); - if (mServiceUuidMask != null) { - dest.writeParcelable(mServiceUuidMask, flags); - } - } - dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1); - if (mServiceSolicitationUuid != null) { - dest.writeParcelable(mServiceSolicitationUuid, flags); - dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1); - if (mServiceSolicitationUuidMask != null) { - dest.writeParcelable(mServiceSolicitationUuidMask, flags); - } - } - dest.writeInt(mServiceDataUuid == null ? 0 : 1); - if (mServiceDataUuid != null) { - dest.writeParcelable(mServiceDataUuid, flags); - dest.writeInt(mServiceData == null ? 0 : 1); - if (mServiceData != null) { - dest.writeInt(mServiceData.length); - dest.writeByteArray(mServiceData); - - dest.writeInt(mServiceDataMask == null ? 0 : 1); - if (mServiceDataMask != null) { - dest.writeInt(mServiceDataMask.length); - dest.writeByteArray(mServiceDataMask); - } - } - } - dest.writeInt(mManufacturerId); - dest.writeInt(mManufacturerData == null ? 0 : 1); - if (mManufacturerData != null) { - dest.writeInt(mManufacturerData.length); - dest.writeByteArray(mManufacturerData); - - dest.writeInt(mManufacturerDataMask == null ? 0 : 1); - if (mManufacturerDataMask != null) { - dest.writeInt(mManufacturerDataMask.length); - dest.writeByteArray(mManufacturerDataMask); - } - } - - // IRK - if (mDeviceAddress != null) { - dest.writeInt(mAddressType); - dest.writeInt(mIrk == null ? 0 : 1); - if (mIrk != null) { - dest.writeByteArray(mIrk); - } - } - } - - /** - * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel. - */ - public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR = - new Creator<ScanFilter>() { - - @Override - public ScanFilter[] newArray(int size) { - return new ScanFilter[size]; - } - - @Override - public ScanFilter createFromParcel(Parcel in) { - Builder builder = new Builder(); - if (in.readInt() == 1) { - builder.setDeviceName(in.readString()); - } - String address = null; - // If we have a non-null address - if (in.readInt() == 1) { - address = in.readString(); - } - if (in.readInt() == 1) { - ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class); - builder.setServiceUuid(uuid); - if (in.readInt() == 1) { - ParcelUuid uuidMask = in.readParcelable( - ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class); - builder.setServiceUuid(uuid, uuidMask); - } - } - if (in.readInt() == 1) { - ParcelUuid solicitationUuid = in.readParcelable( - ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class); - builder.setServiceSolicitationUuid(solicitationUuid); - if (in.readInt() == 1) { - ParcelUuid solicitationUuidMask = in.readParcelable( - ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class); - builder.setServiceSolicitationUuid(solicitationUuid, - solicitationUuidMask); - } - } - if (in.readInt() == 1) { - ParcelUuid servcieDataUuid = - in.readParcelable(ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class); - if (in.readInt() == 1) { - int serviceDataLength = in.readInt(); - byte[] serviceData = new byte[serviceDataLength]; - in.readByteArray(serviceData); - if (in.readInt() == 0) { - builder.setServiceData(servcieDataUuid, serviceData); - } else { - int serviceDataMaskLength = in.readInt(); - byte[] serviceDataMask = new byte[serviceDataMaskLength]; - in.readByteArray(serviceDataMask); - builder.setServiceData( - servcieDataUuid, serviceData, serviceDataMask); - } - } - } - - int manufacturerId = in.readInt(); - if (in.readInt() == 1) { - int manufacturerDataLength = in.readInt(); - byte[] manufacturerData = new byte[manufacturerDataLength]; - in.readByteArray(manufacturerData); - if (in.readInt() == 0) { - builder.setManufacturerData(manufacturerId, manufacturerData); - } else { - int manufacturerDataMaskLength = in.readInt(); - byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; - in.readByteArray(manufacturerDataMask); - builder.setManufacturerData(manufacturerId, manufacturerData, - manufacturerDataMask); - } - } - - // IRK - if (address != null) { - final int addressType = in.readInt(); - if (in.readInt() == 1) { - final byte[] irk = new byte[16]; - in.readByteArray(irk); - builder.setDeviceAddress(address, addressType, irk); - } else { - builder.setDeviceAddress(address, addressType); - } - } - return builder.build(); - } - }; - - /** - * Returns the filter set the device name field of Bluetooth advertisement data. - */ - @Nullable - public String getDeviceName() { - return mDeviceName; - } - - /** - * Returns the filter set on the service uuid. - */ - @Nullable - public ParcelUuid getServiceUuid() { - return mServiceUuid; - } - - @Nullable - public ParcelUuid getServiceUuidMask() { - return mServiceUuidMask; - } - - /** - * Returns the filter set on the service Solicitation uuid. - */ - @Nullable - public ParcelUuid getServiceSolicitationUuid() { - return mServiceSolicitationUuid; - } - - /** - * Returns the filter set on the service Solicitation uuid mask. - */ - @Nullable - public ParcelUuid getServiceSolicitationUuidMask() { - return mServiceSolicitationUuidMask; - } - - @Nullable - public String getDeviceAddress() { - return mDeviceAddress; - } - - /** - * @hide - */ - @SystemApi - public @AddressType int getAddressType() { - return mAddressType; - } - - /** - * @hide - */ - @SystemApi - @Nullable - public byte[] getIrk() { - return mIrk; - } - - @Nullable - public byte[] getServiceData() { - return mServiceData; - } - - @Nullable - public byte[] getServiceDataMask() { - return mServiceDataMask; - } - - @Nullable - public ParcelUuid getServiceDataUuid() { - return mServiceDataUuid; - } - - /** - * Returns the manufacturer id. -1 if the manufacturer filter is not set. - */ - public int getManufacturerId() { - return mManufacturerId; - } - - @Nullable - public byte[] getManufacturerData() { - return mManufacturerData; - } - - @Nullable - public byte[] getManufacturerDataMask() { - return mManufacturerDataMask; - } - - /** - * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match - * if it matches all the field filters. - */ - public boolean matches(ScanResult scanResult) { - if (scanResult == null) { - return false; - } - BluetoothDevice device = scanResult.getDevice(); - // Device match. - if (mDeviceAddress != null - && (device == null || !mDeviceAddress.equals(device.getAddress()))) { - return false; - } - - ScanRecord scanRecord = scanResult.getScanRecord(); - - // Scan record is null but there exist filters on it. - if (scanRecord == null - && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null - || mServiceData != null || mServiceSolicitationUuid != null)) { - return false; - } - - // Local name match. - if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) { - return false; - } - - // UUID match. - if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, - scanRecord.getServiceUuids())) { - return false; - } - - // solicitation UUID match. - if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids( - mServiceSolicitationUuid, mServiceSolicitationUuidMask, - scanRecord.getServiceSolicitationUuids())) { - return false; - } - - // Service data match - if (mServiceDataUuid != null) { - if (!matchesPartialData(mServiceData, mServiceDataMask, - scanRecord.getServiceData(mServiceDataUuid))) { - return false; - } - } - - // Manufacturer data match. - if (mManufacturerId >= 0) { - if (!matchesPartialData(mManufacturerData, mManufacturerDataMask, - scanRecord.getManufacturerSpecificData(mManufacturerId))) { - return false; - } - } - // All filters match. - return true; - } - - /** - * Check if the uuid pattern is contained in a list of parcel uuids. - * - * @hide - */ - public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, - List<ParcelUuid> uuids) { - if (uuid == null) { - return true; - } - if (uuids == null) { - return false; - } - - for (ParcelUuid parcelUuid : uuids) { - UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); - if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { - return true; - } - } - return false; - } - - // Check if the uuid pattern matches the particular service uuid. - private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { - return BluetoothLeUtils.maskedEquals(data, uuid, mask); - } - - /** - * Check if the solicitation uuid pattern is contained in a list of parcel uuids. - * - */ - private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, - ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) { - if (solicitationUuid == null) { - return true; - } - if (solicitationUuids == null) { - return false; - } - - for (ParcelUuid parcelSolicitationUuid : solicitationUuids) { - UUID solicitationUuidMask = parcelSolicitationUuidMask == null - ? null : parcelSolicitationUuidMask.getUuid(); - if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask, - parcelSolicitationUuid.getUuid())) { - return true; - } - } - return false; - } - - // Check if the solicitation uuid pattern matches the particular service solicitation uuid. - private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid, - UUID solicitationUuidMask, UUID data) { - return BluetoothLeUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask); - } - - // Check whether the data pattern matches the parsed data. - private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) { - if (parsedData == null || parsedData.length < data.length) { - return false; - } - if (dataMask == null) { - for (int i = 0; i < data.length; ++i) { - if (parsedData[i] != data[i]) { - return false; - } - } - return true; - } - for (int i = 0; i < data.length; ++i) { - if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress=" - + mDeviceAddress - + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask - + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid - + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask - + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData=" - + Arrays.toString(mServiceData) + ", mServiceDataMask=" - + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId - + ", mManufacturerData=" + Arrays.toString(mManufacturerData) - + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]"; - } - - @Override - public int hashCode() { - return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, - Arrays.hashCode(mManufacturerData), - Arrays.hashCode(mManufacturerDataMask), - mServiceDataUuid, - Arrays.hashCode(mServiceData), - Arrays.hashCode(mServiceDataMask), - mServiceUuid, mServiceUuidMask, - mServiceSolicitationUuid, mServiceSolicitationUuidMask); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ScanFilter other = (ScanFilter) obj; - return Objects.equals(mDeviceName, other.mDeviceName) - && Objects.equals(mDeviceAddress, other.mDeviceAddress) - && mManufacturerId == other.mManufacturerId - && Objects.deepEquals(mManufacturerData, other.mManufacturerData) - && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) - && Objects.equals(mServiceDataUuid, other.mServiceDataUuid) - && Objects.deepEquals(mServiceData, other.mServiceData) - && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) - && Objects.equals(mServiceUuid, other.mServiceUuid) - && Objects.equals(mServiceUuidMask, other.mServiceUuidMask) - && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid) - && Objects.equals(mServiceSolicitationUuidMask, - other.mServiceSolicitationUuidMask); - } - - /** - * Checks if the scanfilter is empty - * - * @hide - */ - public boolean isAllFieldsEmpty() { - return EMPTY.equals(this); - } - - /** - * Builder class for {@link ScanFilter}. - */ - public static final class Builder { - - /** - * @hide - */ - @SystemApi - public static final int LEN_IRK_OCTETS = 16; - - private String mDeviceName; - private String mDeviceAddress; - private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC; - private byte[] mIrk; - - private ParcelUuid mServiceUuid; - private ParcelUuid mUuidMask; - - private ParcelUuid mServiceSolicitationUuid; - private ParcelUuid mServiceSolicitationUuidMask; - - private ParcelUuid mServiceDataUuid; - private byte[] mServiceData; - private byte[] mServiceDataMask; - - private int mManufacturerId = -1; - private byte[] mManufacturerData; - private byte[] mManufacturerDataMask; - - /** - * Set filter on device name. - */ - public Builder setDeviceName(String deviceName) { - mDeviceName = deviceName; - return this; - } - - /** - * Set filter on device address. - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link - * BluetoothAdapter#checkBluetoothAddress}. The @AddressType is defaulted to {@link - * BluetoothDevice#ADDRESS_TYPE_PUBLIC} - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - */ - public Builder setDeviceAddress(String deviceAddress) { - if (deviceAddress == null) { - mDeviceAddress = deviceAddress; - return this; - } - return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC); - } - - /** - * Set filter on Address with AddressType - * - * <p>This key is used to resolve a private address from a public address. - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link - * BluetoothAdapter#checkBluetoothAddress}. May be any type of address. - * @param addressType indication of the type of address - * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} - * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} - * - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - * @throws IllegalArgumentException If the {@code addressType} is invalid length - * @throws NullPointerException if {@code deviceAddress} is null. - * - * @hide - */ - @NonNull - @SystemApi - public Builder setDeviceAddress(@NonNull String deviceAddress, - @AddressType int addressType) { - return setDeviceAddressInternal(deviceAddress, addressType, null); - } - - /** - * Set filter on Address with AddressType and the Identity Resolving Key (IRK). - * - * <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from - * a PRIVATE_ADDRESS type. - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link - * BluetoothAdapter#checkBluetoothAddress}. This Address type must only be PUBLIC OR RANDOM - * STATIC. - * @param addressType indication of the type of address - * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} - * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} - * @param irk non-null byte array representing the Identity Resolving Key - * - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - * @throws IllegalArgumentException if the {@code irk} is invalid length. - * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not - * PUBLIC or RANDOM STATIC when an IRK is present. - * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null. - * - * @hide - */ - @NonNull - @SystemApi - public Builder setDeviceAddress(@NonNull String deviceAddress, - @AddressType int addressType, - @NonNull byte[] irk) { - requireNonNull(irk); - if (irk.length != LEN_IRK_OCTETS) { - throw new IllegalArgumentException("'irk' is invalid length!"); - } - return setDeviceAddressInternal(deviceAddress, addressType, irk); - } - - /** - * Set filter on Address with AddressType and the Identity Resolving Key (IRK). - * - * <p>Internal setter for the device address - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link - * BluetoothAdapter#checkBluetoothAddress}. - * @param addressType indication of the type of address - * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} - * @param irk non-null byte array representing the Identity Resolving Address; nullable - * internally. - * - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - * @throws IllegalArgumentException If the {@code addressType} is invalid length. - * @throws NullPointerException if {@code deviceAddress} is null. - * - * @hide - */ - @NonNull - private Builder setDeviceAddressInternal(@NonNull String deviceAddress, - @AddressType int addressType, - @Nullable byte[] irk) { - - // Make sure our deviceAddress is valid! - requireNonNull(deviceAddress); - if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) { - throw new IllegalArgumentException("invalid device address " + deviceAddress); - } - - // Verify type range - if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC - || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) { - throw new IllegalArgumentException("'addressType' is invalid!"); - } - - // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address. - if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) { - // Don't want a bad combination of address and irk! - if (irk != null) { - // Since there are 3 possible RANDOM subtypes we must check to make sure - // the correct type of address is used. - if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) { - throw new IllegalArgumentException( - "Invalid combination: IRK requires either a PUBLIC or " - + "RANDOM (STATIC) Address"); - } - } - } - - // PUBLIC doesn't require extra work - // Without an IRK any address may be accepted - - mDeviceAddress = deviceAddress; - mAddressType = addressType; - mIrk = irk; - return this; - } - - /** - * Set filter on service uuid. - */ - public Builder setServiceUuid(ParcelUuid serviceUuid) { - mServiceUuid = serviceUuid; - mUuidMask = null; // clear uuid mask - return this; - } - - /** - * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the - * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the - * bit in {@code serviceUuid}, and 0 to ignore that bit. - * - * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code - * uuidMask} is not {@code null}. - */ - public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) { - if (mUuidMask != null && mServiceUuid == null) { - throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); - } - mServiceUuid = serviceUuid; - mUuidMask = uuidMask; - return this; - } - - - /** - * Set filter on service solicitation uuid. - */ - public @NonNull Builder setServiceSolicitationUuid( - @Nullable ParcelUuid serviceSolicitationUuid) { - mServiceSolicitationUuid = serviceSolicitationUuid; - if (serviceSolicitationUuid == null) { - mServiceSolicitationUuidMask = null; - } - return this; - } - - - /** - * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the - * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to - * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to - * ignore that bit. - * - * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null. - * @param solicitationUuidMask can be null or a mask with no restriction. - * - * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but - * {@code serviceSolicitationUuidMask} is not {@code null}. - */ - public @NonNull Builder setServiceSolicitationUuid( - @Nullable ParcelUuid serviceSolicitationUuid, - @Nullable ParcelUuid solicitationUuidMask) { - if (solicitationUuidMask != null && serviceSolicitationUuid == null) { - throw new IllegalArgumentException( - "SolicitationUuid is null while SolicitationUuidMask is not null!"); - } - mServiceSolicitationUuid = serviceSolicitationUuid; - mServiceSolicitationUuidMask = solicitationUuidMask; - return this; - } - - /** - * Set filtering on service data. - * - * @throws IllegalArgumentException If {@code serviceDataUuid} is null. - */ - public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { - if (serviceDataUuid == null) { - throw new IllegalArgumentException("serviceDataUuid is null"); - } - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - mServiceDataMask = null; // clear service data mask - return this; - } - - /** - * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to - * match the one in service data, otherwise set it to 0 to ignore that bit. - * <p> - * The {@code serviceDataMask} must have the same length of the {@code serviceData}. - * - * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code - * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code - * serviceDataMask} and {@code serviceData} has different length. - */ - public Builder setServiceData(ParcelUuid serviceDataUuid, - byte[] serviceData, byte[] serviceDataMask) { - if (serviceDataUuid == null) { - throw new IllegalArgumentException("serviceDataUuid is null"); - } - if (mServiceDataMask != null) { - if (mServiceData == null) { - throw new IllegalArgumentException( - "serviceData is null while serviceDataMask is not null"); - } - // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two - // byte array need to be the same. - if (mServiceData.length != mServiceDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for service data and service data mask"); - } - } - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - mServiceDataMask = serviceDataMask; - return this; - } - - /** - * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. - * - * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. - */ - public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) { - if (manufacturerData != null && manufacturerId < 0) { - throw new IllegalArgumentException("invalid manufacture id"); - } - mManufacturerId = manufacturerId; - mManufacturerData = manufacturerData; - mManufacturerDataMask = null; // clear manufacturer data mask - return this; - } - - /** - * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs - * to match the one in manufacturer data, otherwise set it to 0. - * <p> - * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}. - * - * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code - * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code - * manufacturerData} and {@code manufacturerDataMask} have different length. - */ - public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData, - byte[] manufacturerDataMask) { - if (manufacturerData != null && manufacturerId < 0) { - throw new IllegalArgumentException("invalid manufacture id"); - } - if (mManufacturerDataMask != null) { - if (mManufacturerData == null) { - throw new IllegalArgumentException( - "manufacturerData is null while manufacturerDataMask is not null"); - } - // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths - // of the two byte array need to be the same. - if (mManufacturerData.length != mManufacturerDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for manufacturerData and manufacturerDataMask"); - } - } - mManufacturerId = manufacturerId; - mManufacturerData = manufacturerData; - mManufacturerDataMask = manufacturerDataMask; - return this; - } - - /** - * Build {@link ScanFilter}. - * - * @throws IllegalArgumentException If the filter cannot be built. - */ - public ScanFilter build() { - return new ScanFilter(mDeviceName, mDeviceAddress, - mServiceUuid, mUuidMask, mServiceSolicitationUuid, - mServiceSolicitationUuidMask, - mServiceDataUuid, mServiceData, mServiceDataMask, - mManufacturerId, mManufacturerData, mManufacturerDataMask, - mAddressType, mIrk); - } - } -} diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java deleted file mode 100644 index 9b8c2eaf4d19..000000000000 --- a/core/java/android/bluetooth/le/ScanRecord.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothUuid; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.ParcelUuid; -import android.util.ArrayMap; -import android.util.Log; -import android.util.SparseArray; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -/** - * Represents a scan record from Bluetooth LE scan. - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -public final class ScanRecord { - - private static final String TAG = "ScanRecord"; - - // The following data type values are assigned by Bluetooth SIG. - // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18. - private static final int DATA_TYPE_FLAGS = 0x01; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; - private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; - private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; - private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; - private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16; - private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20; - private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21; - private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT = 0x14; - private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT = 0x1F; - private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15; - private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; - - // Flags of the advertising data. - private final int mAdvertiseFlags; - - @Nullable - private final List<ParcelUuid> mServiceUuids; - @Nullable - private final List<ParcelUuid> mServiceSolicitationUuids; - - private final SparseArray<byte[]> mManufacturerSpecificData; - - private final Map<ParcelUuid, byte[]> mServiceData; - - // Transmission power level(in dB). - private final int mTxPowerLevel; - - // Local name of the Bluetooth LE device. - private final String mDeviceName; - - // Raw bytes of scan record. - private final byte[] mBytes; - - /** - * Returns the advertising flags indicating the discoverable mode and capability of the device. - * Returns -1 if the flag field is not set. - */ - public int getAdvertiseFlags() { - return mAdvertiseFlags; - } - - /** - * Returns a list of service UUIDs within the advertisement that are used to identify the - * bluetooth GATT services. - */ - public List<ParcelUuid> getServiceUuids() { - return mServiceUuids; - } - - /** - * Returns a list of service solicitation UUIDs within the advertisement that are used to - * identify the Bluetooth GATT services. - */ - @NonNull - public List<ParcelUuid> getServiceSolicitationUuids() { - return mServiceSolicitationUuids; - } - - /** - * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific - * data. - */ - public SparseArray<byte[]> getManufacturerSpecificData() { - return mManufacturerSpecificData; - } - - /** - * Returns the manufacturer specific data associated with the manufacturer id. Returns - * {@code null} if the {@code manufacturerId} is not found. - */ - @Nullable - public byte[] getManufacturerSpecificData(int manufacturerId) { - if (mManufacturerSpecificData == null) { - return null; - } - return mManufacturerSpecificData.get(manufacturerId); - } - - /** - * Returns a map of service UUID and its corresponding service data. - */ - public Map<ParcelUuid, byte[]> getServiceData() { - return mServiceData; - } - - /** - * Returns the service data byte array associated with the {@code serviceUuid}. Returns - * {@code null} if the {@code serviceDataUuid} is not found. - */ - @Nullable - public byte[] getServiceData(ParcelUuid serviceDataUuid) { - if (serviceDataUuid == null || mServiceData == null) { - return null; - } - return mServiceData.get(serviceDataUuid); - } - - /** - * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE} - * if the field is not set. This value can be used to calculate the path loss of a received - * packet using the following equation: - * <p> - * <code>pathloss = txPowerLevel - rssi</code> - */ - public int getTxPowerLevel() { - return mTxPowerLevel; - } - - /** - * Returns the local name of the BLE device. This is a UTF-8 encoded string. - */ - @Nullable - public String getDeviceName() { - return mDeviceName; - } - - /** - * Returns raw bytes of scan record. - */ - public byte[] getBytes() { - return mBytes; - } - - /** - * Test if any fields contained inside this scan record are matched by the - * given matcher. - * - * @hide - */ - public boolean matchesAnyField(@NonNull Predicate<byte[]> matcher) { - int pos = 0; - while (pos < mBytes.length) { - final int length = mBytes[pos] & 0xFF; - if (length == 0) { - break; - } - if (matcher.test(Arrays.copyOfRange(mBytes, pos, pos + length + 1))) { - return true; - } - pos += length + 1; - } - return false; - } - - private ScanRecord(List<ParcelUuid> serviceUuids, - List<ParcelUuid> serviceSolicitationUuids, - SparseArray<byte[]> manufacturerData, - Map<ParcelUuid, byte[]> serviceData, - int advertiseFlags, int txPowerLevel, - String localName, byte[] bytes) { - mServiceSolicitationUuids = serviceSolicitationUuids; - mServiceUuids = serviceUuids; - mManufacturerSpecificData = manufacturerData; - mServiceData = serviceData; - mDeviceName = localName; - mAdvertiseFlags = advertiseFlags; - mTxPowerLevel = txPowerLevel; - mBytes = bytes; - } - - /** - * Parse scan record bytes to {@link ScanRecord}. - * <p> - * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18. - * <p> - * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong> - * order. - * - * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. - * @hide - */ - @UnsupportedAppUsage - public static ScanRecord parseFromBytes(byte[] scanRecord) { - if (scanRecord == null) { - return null; - } - - int currentPos = 0; - int advertiseFlag = -1; - List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); - List<ParcelUuid> serviceSolicitationUuids = new ArrayList<ParcelUuid>(); - String localName = null; - int txPowerLevel = Integer.MIN_VALUE; - - SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>(); - Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>(); - - try { - while (currentPos < scanRecord.length) { - // length is unsigned int. - int length = scanRecord[currentPos++] & 0xFF; - if (length == 0) { - break; - } - // Note the length includes the length of the field type itself. - int dataLength = length - 1; - // fieldType is unsigned int. - int fieldType = scanRecord[currentPos++] & 0xFF; - switch (fieldType) { - case DATA_TYPE_FLAGS: - advertiseFlag = scanRecord[currentPos] & 0xFF; - break; - case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, - dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT: - parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_16_BIT, serviceSolicitationUuids); - break; - case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT: - parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_32_BIT, serviceSolicitationUuids); - break; - case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT: - parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_128_BIT, serviceSolicitationUuids); - break; - case DATA_TYPE_LOCAL_NAME_SHORT: - case DATA_TYPE_LOCAL_NAME_COMPLETE: - localName = new String( - extractBytes(scanRecord, currentPos, dataLength)); - break; - case DATA_TYPE_TX_POWER_LEVEL: - txPowerLevel = scanRecord[currentPos]; - break; - case DATA_TYPE_SERVICE_DATA_16_BIT: - case DATA_TYPE_SERVICE_DATA_32_BIT: - case DATA_TYPE_SERVICE_DATA_128_BIT: - int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT; - if (fieldType == DATA_TYPE_SERVICE_DATA_32_BIT) { - serviceUuidLength = BluetoothUuid.UUID_BYTES_32_BIT; - } else if (fieldType == DATA_TYPE_SERVICE_DATA_128_BIT) { - serviceUuidLength = BluetoothUuid.UUID_BYTES_128_BIT; - } - - byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, - serviceUuidLength); - ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom( - serviceDataUuidBytes); - byte[] serviceDataArray = extractBytes(scanRecord, - currentPos + serviceUuidLength, dataLength - serviceUuidLength); - serviceData.put(serviceDataUuid, serviceDataArray); - break; - case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: - // The first two bytes of the manufacturer specific data are - // manufacturer ids in little endian. - int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) - + (scanRecord[currentPos] & 0xFF); - byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2, - dataLength - 2); - manufacturerData.put(manufacturerId, manufacturerDataBytes); - break; - default: - // Just ignore, we don't handle such data type. - break; - } - currentPos += dataLength; - } - - if (serviceUuids.isEmpty()) { - serviceUuids = null; - } - return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData, - serviceData, advertiseFlag, txPowerLevel, localName, scanRecord); - } catch (Exception e) { - Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord)); - // As the record is invalid, ignore all the parsed results for this packet - // and return an empty record with raw scanRecord bytes in results - return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, scanRecord); - } - } - - @Override - public String toString() { - return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids - + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids - + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString( - mManufacturerSpecificData) - + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData) - + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]"; - } - - // Parse service UUIDs. - private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, - int uuidLength, List<ParcelUuid> serviceUuids) { - while (dataLength > 0) { - byte[] uuidBytes = extractBytes(scanRecord, currentPos, - uuidLength); - serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); - dataLength -= uuidLength; - currentPos += uuidLength; - } - return currentPos; - } - - /** - * Parse service Solicitation UUIDs. - */ - private static int parseServiceSolicitationUuid(byte[] scanRecord, int currentPos, - int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids) { - while (dataLength > 0) { - byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength); - serviceSolicitationUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); - dataLength -= uuidLength; - currentPos += uuidLength; - } - return currentPos; - } - - // Helper method to extract bytes from byte array. - private static byte[] extractBytes(byte[] scanRecord, int start, int length) { - byte[] bytes = new byte[length]; - System.arraycopy(scanRecord, start, bytes, 0, length); - return bytes; - } -} diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java deleted file mode 100644 index f437d867ea37..000000000000 --- a/core/java/android/bluetooth/le/ScanResult.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.bluetooth.Attributable; -import android.bluetooth.BluetoothDevice; -import android.content.AttributionSource; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Objects; - -/** - * ScanResult for Bluetooth LE scan. - */ -public final class ScanResult implements Parcelable, Attributable { - - /** - * For chained advertisements, inidcates tha the data contained in this - * scan result is complete. - */ - public static final int DATA_COMPLETE = 0x00; - - /** - * For chained advertisements, indicates that the controller was - * unable to receive all chained packets and the scan result contains - * incomplete truncated data. - */ - public static final int DATA_TRUNCATED = 0x02; - - /** - * Indicates that the secondary physical layer was not used. - */ - public static final int PHY_UNUSED = 0x00; - - /** - * Advertising Set ID is not present in the packet. - */ - public static final int SID_NOT_PRESENT = 0xFF; - - /** - * TX power is not present in the packet. - */ - public static final int TX_POWER_NOT_PRESENT = 0x7F; - - /** - * Periodic advertising interval is not present in the packet. - */ - public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0x00; - - /** - * Mask for checking whether event type represents legacy advertisement. - */ - private static final int ET_LEGACY_MASK = 0x10; - - /** - * Mask for checking whether event type represents connectable advertisement. - */ - private static final int ET_CONNECTABLE_MASK = 0x01; - - // Remote Bluetooth device. - private BluetoothDevice mDevice; - - // Scan record, including advertising data and scan response data. - @Nullable - private ScanRecord mScanRecord; - - // Received signal strength. - private int mRssi; - - // Device timestamp when the result was last seen. - private long mTimestampNanos; - - private int mEventType; - private int mPrimaryPhy; - private int mSecondaryPhy; - private int mAdvertisingSid; - private int mTxPower; - private int mPeriodicAdvertisingInterval; - - /** - * Constructs a new ScanResult. - * - * @param device Remote Bluetooth device found. - * @param scanRecord Scan record including both advertising data and scan response data. - * @param rssi Received signal strength. - * @param timestampNanos Timestamp at which the scan result was observed. - * @deprecated use {@link #ScanResult(BluetoothDevice, int, int, int, int, int, int, int, - * ScanRecord, long)} - */ - @Deprecated - public ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi, - long timestampNanos) { - mDevice = device; - mScanRecord = scanRecord; - mRssi = rssi; - mTimestampNanos = timestampNanos; - mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK; - mPrimaryPhy = BluetoothDevice.PHY_LE_1M; - mSecondaryPhy = PHY_UNUSED; - mAdvertisingSid = SID_NOT_PRESENT; - mTxPower = 127; - mPeriodicAdvertisingInterval = 0; - } - - /** - * Constructs a new ScanResult. - * - * @param device Remote Bluetooth device found. - * @param eventType Event type. - * @param primaryPhy Primary advertising phy. - * @param secondaryPhy Secondary advertising phy. - * @param advertisingSid Advertising set ID. - * @param txPower Transmit power. - * @param rssi Received signal strength. - * @param periodicAdvertisingInterval Periodic advertising interval. - * @param scanRecord Scan record including both advertising data and scan response data. - * @param timestampNanos Timestamp at which the scan result was observed. - */ - public ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy, - int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval, - ScanRecord scanRecord, long timestampNanos) { - mDevice = device; - mEventType = eventType; - mPrimaryPhy = primaryPhy; - mSecondaryPhy = secondaryPhy; - mAdvertisingSid = advertisingSid; - mTxPower = txPower; - mRssi = rssi; - mPeriodicAdvertisingInterval = periodicAdvertisingInterval; - mScanRecord = scanRecord; - mTimestampNanos = timestampNanos; - } - - private ScanResult(Parcel in) { - readFromParcel(in); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - if (mDevice != null) { - dest.writeInt(1); - mDevice.writeToParcel(dest, flags); - } else { - dest.writeInt(0); - } - if (mScanRecord != null) { - dest.writeInt(1); - dest.writeByteArray(mScanRecord.getBytes()); - } else { - dest.writeInt(0); - } - dest.writeInt(mRssi); - dest.writeLong(mTimestampNanos); - dest.writeInt(mEventType); - dest.writeInt(mPrimaryPhy); - dest.writeInt(mSecondaryPhy); - dest.writeInt(mAdvertisingSid); - dest.writeInt(mTxPower); - dest.writeInt(mPeriodicAdvertisingInterval); - } - - private void readFromParcel(Parcel in) { - if (in.readInt() == 1) { - mDevice = BluetoothDevice.CREATOR.createFromParcel(in); - } - if (in.readInt() == 1) { - mScanRecord = ScanRecord.parseFromBytes(in.createByteArray()); - } - mRssi = in.readInt(); - mTimestampNanos = in.readLong(); - mEventType = in.readInt(); - mPrimaryPhy = in.readInt(); - mSecondaryPhy = in.readInt(); - mAdvertisingSid = in.readInt(); - mTxPower = in.readInt(); - mPeriodicAdvertisingInterval = in.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - /** {@hide} */ - public void setAttributionSource(@NonNull AttributionSource attributionSource) { - Attributable.setAttributionSource(mDevice, attributionSource); - } - - /** - * Returns the remote Bluetooth device identified by the Bluetooth device address. - */ - public BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Returns the scan record, which is a combination of advertisement and scan response. - */ - @Nullable - public ScanRecord getScanRecord() { - return mScanRecord; - } - - /** - * Returns the received signal strength in dBm. The valid range is [-127, 126]. - */ - public int getRssi() { - return mRssi; - } - - /** - * Returns timestamp since boot when the scan record was observed. - */ - public long getTimestampNanos() { - return mTimestampNanos; - } - - /** - * Returns true if this object represents legacy scan result. - * Legacy scan results do not contain advanced advertising information - * as specified in the Bluetooth Core Specification v5. - */ - public boolean isLegacy() { - return (mEventType & ET_LEGACY_MASK) != 0; - } - - /** - * Returns true if this object represents connectable scan result. - */ - public boolean isConnectable() { - return (mEventType & ET_CONNECTABLE_MASK) != 0; - } - - /** - * Returns the data status. - * Can be one of {@link ScanResult#DATA_COMPLETE} or - * {@link ScanResult#DATA_TRUNCATED}. - */ - public int getDataStatus() { - // return bit 5 and 6 - return (mEventType >> 5) & 0x03; - } - - /** - * Returns the primary Physical Layer - * on which this advertisment was received. - * Can be one of {@link BluetoothDevice#PHY_LE_1M} or - * {@link BluetoothDevice#PHY_LE_CODED}. - */ - public int getPrimaryPhy() { - return mPrimaryPhy; - } - - /** - * Returns the secondary Physical Layer - * on which this advertisment was received. - * Can be one of {@link BluetoothDevice#PHY_LE_1M}, - * {@link BluetoothDevice#PHY_LE_2M}, {@link BluetoothDevice#PHY_LE_CODED} - * or {@link ScanResult#PHY_UNUSED} - if the advertisement - * was not received on a secondary physical channel. - */ - public int getSecondaryPhy() { - return mSecondaryPhy; - } - - /** - * Returns the advertising set id. - * May return {@link ScanResult#SID_NOT_PRESENT} if - * no set id was is present. - */ - public int getAdvertisingSid() { - return mAdvertisingSid; - } - - /** - * Returns the transmit power in dBm. - * Valid range is [-127, 126]. A value of {@link ScanResult#TX_POWER_NOT_PRESENT} - * indicates that the TX power is not present. - */ - public int getTxPower() { - return mTxPower; - } - - /** - * Returns the periodic advertising interval in units of 1.25ms. - * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of - * {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means periodic - * advertising interval is not present. - */ - public int getPeriodicAdvertisingInterval() { - return mPeriodicAdvertisingInterval; - } - - @Override - public int hashCode() { - return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos, - mEventType, mPrimaryPhy, mSecondaryPhy, - mAdvertisingSid, mTxPower, - mPeriodicAdvertisingInterval); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ScanResult other = (ScanResult) obj; - return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) - && Objects.equals(mScanRecord, other.mScanRecord) - && (mTimestampNanos == other.mTimestampNanos) - && mEventType == other.mEventType - && mPrimaryPhy == other.mPrimaryPhy - && mSecondaryPhy == other.mSecondaryPhy - && mAdvertisingSid == other.mAdvertisingSid - && mTxPower == other.mTxPower - && mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval; - } - - @Override - public String toString() { - return "ScanResult{" + "device=" + mDevice + ", scanRecord=" - + Objects.toString(mScanRecord) + ", rssi=" + mRssi - + ", timestampNanos=" + mTimestampNanos + ", eventType=" + mEventType - + ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy - + ", advertisingSid=" + mAdvertisingSid + ", txPower=" + mTxPower - + ", periodicAdvertisingInterval=" + mPeriodicAdvertisingInterval + '}'; - } - - public static final @android.annotation.NonNull Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() { - @Override - public ScanResult createFromParcel(Parcel source) { - return new ScanResult(source); - } - - @Override - public ScanResult[] newArray(int size) { - return new ScanResult[size]; - } - }; - -} diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java deleted file mode 100644 index 1aa7cb5111ce..000000000000 --- a/core/java/android/bluetooth/le/ScanSettings.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.SystemApi; -import android.bluetooth.BluetoothDevice; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan} to define the - * parameters for the scan. - */ -public final class ScanSettings implements Parcelable { - - /** - * A special Bluetooth LE scan mode. Applications using this scan mode will passively listen for - * other scan results without starting BLE scans themselves. - */ - public static final int SCAN_MODE_OPPORTUNISTIC = -1; - - /** - * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the - * least power. This mode is enforced if the scanning application is not in foreground. - */ - public static final int SCAN_MODE_LOW_POWER = 0; - - /** - * Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate that - * provides a good trade-off between scan frequency and power consumption. - */ - public static final int SCAN_MODE_BALANCED = 1; - - /** - * Scan using highest duty cycle. It's recommended to only use this mode when the application is - * running in the foreground. - */ - public static final int SCAN_MODE_LOW_LATENCY = 2; - - /** - * Perform Bluetooth LE scan in ambient discovery mode. This mode has lower duty cycle and more - * aggressive scan interval than balanced mode that provides a good trade-off between scan - * latency and power consumption. - * - * @hide - */ - @SystemApi - public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3; - - /** - * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria. - * If no filter is active, all advertisement packets are reported. - */ - public static final int CALLBACK_TYPE_ALL_MATCHES = 1; - - /** - * A result callback is only triggered for the first advertisement packet received that matches - * the filter criteria. - */ - public static final int CALLBACK_TYPE_FIRST_MATCH = 2; - - /** - * Receive a callback when advertisements are no longer received from a device that has been - * previously reported by a first match callback. - */ - public static final int CALLBACK_TYPE_MATCH_LOST = 4; - - - /** - * Determines how many advertisements to match per filter, as this is scarce hw resource - */ - /** - * Match one advertisement per filter - */ - public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1; - - /** - * Match few advertisement per filter, depends on current capability and availibility of - * the resources in hw - */ - public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2; - - /** - * Match as many advertisement per filter as hw could allow, depends on current - * capability and availibility of the resources in hw - */ - public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3; - - - /** - * In Aggressive mode, hw will determine a match sooner even with feeble signal strength - * and few number of sightings/match in a duration. - */ - public static final int MATCH_MODE_AGGRESSIVE = 1; - - /** - * For sticky mode, higher threshold of signal strength and sightings is required - * before reporting by hw - */ - public static final int MATCH_MODE_STICKY = 2; - - /** - * Request full scan results which contain the device, rssi, advertising data, scan response - * as well as the scan timestamp. - * - * @hide - */ - @SystemApi - public static final int SCAN_RESULT_TYPE_FULL = 0; - - /** - * Request abbreviated scan results which contain the device, rssi and scan timestamp. - * <p> - * <b>Note:</b> It is possible for an application to get more scan results than it asked for, if - * there are multiple apps using this type. - * - * @hide - */ - @SystemApi - public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1; - - /** - * Use all supported PHYs for scanning. - * This will check the controller capabilities, and start - * the scan on 1Mbit and LE Coded PHYs if supported, or on - * the 1Mbit PHY only. - */ - public static final int PHY_LE_ALL_SUPPORTED = 255; - - // Bluetooth LE scan mode. - private int mScanMode; - - // Bluetooth LE scan callback type - private int mCallbackType; - - // Bluetooth LE scan result type - private int mScanResultType; - - // Time of delay for reporting the scan result - private long mReportDelayMillis; - - private int mMatchMode; - - private int mNumOfMatchesPerFilter; - - // Include only legacy advertising results - private boolean mLegacy; - - private int mPhy; - - public int getScanMode() { - return mScanMode; - } - - public int getCallbackType() { - return mCallbackType; - } - - public int getScanResultType() { - return mScanResultType; - } - - /** - * @hide - */ - public int getMatchMode() { - return mMatchMode; - } - - /** - * @hide - */ - public int getNumOfMatches() { - return mNumOfMatchesPerFilter; - } - - /** - * Returns whether only legacy advertisements will be returned. - * Legacy advertisements include advertisements as specified - * by the Bluetooth core specification 4.2 and below. - */ - public boolean getLegacy() { - return mLegacy; - } - - /** - * Returns the physical layer used during a scan. - */ - public int getPhy() { - return mPhy; - } - - /** - * Returns report delay timestamp based on the device clock. - */ - public long getReportDelayMillis() { - return mReportDelayMillis; - } - - private ScanSettings(int scanMode, int callbackType, int scanResultType, - long reportDelayMillis, int matchMode, - int numOfMatchesPerFilter, boolean legacy, int phy) { - mScanMode = scanMode; - mCallbackType = callbackType; - mScanResultType = scanResultType; - mReportDelayMillis = reportDelayMillis; - mNumOfMatchesPerFilter = numOfMatchesPerFilter; - mMatchMode = matchMode; - mLegacy = legacy; - mPhy = phy; - } - - private ScanSettings(Parcel in) { - mScanMode = in.readInt(); - mCallbackType = in.readInt(); - mScanResultType = in.readInt(); - mReportDelayMillis = in.readLong(); - mMatchMode = in.readInt(); - mNumOfMatchesPerFilter = in.readInt(); - mLegacy = in.readInt() != 0; - mPhy = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mScanMode); - dest.writeInt(mCallbackType); - dest.writeInt(mScanResultType); - dest.writeLong(mReportDelayMillis); - dest.writeInt(mMatchMode); - dest.writeInt(mNumOfMatchesPerFilter); - dest.writeInt(mLegacy ? 1 : 0); - dest.writeInt(mPhy); - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<ScanSettings> CREATOR = - new Creator<ScanSettings>() { - @Override - public ScanSettings[] newArray(int size) { - return new ScanSettings[size]; - } - - @Override - public ScanSettings createFromParcel(Parcel in) { - return new ScanSettings(in); - } - }; - - /** - * Builder for {@link ScanSettings}. - */ - public static final class Builder { - private int mScanMode = SCAN_MODE_LOW_POWER; - private int mCallbackType = CALLBACK_TYPE_ALL_MATCHES; - private int mScanResultType = SCAN_RESULT_TYPE_FULL; - private long mReportDelayMillis = 0; - private int mMatchMode = MATCH_MODE_AGGRESSIVE; - private int mNumOfMatchesPerFilter = MATCH_NUM_MAX_ADVERTISEMENT; - private boolean mLegacy = true; - private int mPhy = PHY_LE_ALL_SUPPORTED; - - /** - * Set scan mode for Bluetooth LE scan. - * - * @param scanMode The scan mode can be one of {@link ScanSettings#SCAN_MODE_LOW_POWER}, - * {@link ScanSettings#SCAN_MODE_BALANCED} or {@link ScanSettings#SCAN_MODE_LOW_LATENCY}. - * @throws IllegalArgumentException If the {@code scanMode} is invalid. - */ - public Builder setScanMode(int scanMode) { - switch (scanMode) { - case SCAN_MODE_OPPORTUNISTIC: - case SCAN_MODE_LOW_POWER: - case SCAN_MODE_BALANCED: - case SCAN_MODE_LOW_LATENCY: - case SCAN_MODE_AMBIENT_DISCOVERY: - mScanMode = scanMode; - break; - default: - throw new IllegalArgumentException("invalid scan mode " + scanMode); - } - return this; - } - - /** - * Set callback type for Bluetooth LE scan. - * - * @param callbackType The callback type flags for the scan. - * @throws IllegalArgumentException If the {@code callbackType} is invalid. - */ - public Builder setCallbackType(int callbackType) { - - if (!isValidCallbackType(callbackType)) { - throw new IllegalArgumentException("invalid callback type - " + callbackType); - } - mCallbackType = callbackType; - return this; - } - - // Returns true if the callbackType is valid. - private boolean isValidCallbackType(int callbackType) { - if (callbackType == CALLBACK_TYPE_ALL_MATCHES - || callbackType == CALLBACK_TYPE_FIRST_MATCH - || callbackType == CALLBACK_TYPE_MATCH_LOST) { - return true; - } - return callbackType == (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST); - } - - /** - * Set scan result type for Bluetooth LE scan. - * - * @param scanResultType Type for scan result, could be either {@link - * ScanSettings#SCAN_RESULT_TYPE_FULL} or {@link ScanSettings#SCAN_RESULT_TYPE_ABBREVIATED}. - * @throws IllegalArgumentException If the {@code scanResultType} is invalid. - * @hide - */ - @SystemApi - public Builder setScanResultType(int scanResultType) { - if (scanResultType < SCAN_RESULT_TYPE_FULL - || scanResultType > SCAN_RESULT_TYPE_ABBREVIATED) { - throw new IllegalArgumentException( - "invalid scanResultType - " + scanResultType); - } - mScanResultType = scanResultType; - return this; - } - - /** - * Set report delay timestamp for Bluetooth LE scan. If set to 0, you will be notified of - * scan results immediately. If > 0, scan results are queued up and delivered after the - * requested delay or 5000 milliseconds (whichever is higher). Note scan results may be - * delivered sooner if the internal buffers fill up. - * - * @param reportDelayMillis how frequently scan results should be delivered in - * milliseconds - * @throws IllegalArgumentException if {@code reportDelayMillis} < 0 - */ - public Builder setReportDelay(long reportDelayMillis) { - if (reportDelayMillis < 0) { - throw new IllegalArgumentException("reportDelay must be > 0"); - } - mReportDelayMillis = reportDelayMillis; - return this; - } - - /** - * Set the number of matches for Bluetooth LE scan filters hardware match - * - * @param numOfMatches The num of matches can be one of - * {@link ScanSettings#MATCH_NUM_ONE_ADVERTISEMENT} - * or {@link ScanSettings#MATCH_NUM_FEW_ADVERTISEMENT} or {@link - * ScanSettings#MATCH_NUM_MAX_ADVERTISEMENT} - * @throws IllegalArgumentException If the {@code matchMode} is invalid. - */ - public Builder setNumOfMatches(int numOfMatches) { - if (numOfMatches < MATCH_NUM_ONE_ADVERTISEMENT - || numOfMatches > MATCH_NUM_MAX_ADVERTISEMENT) { - throw new IllegalArgumentException("invalid numOfMatches " + numOfMatches); - } - mNumOfMatchesPerFilter = numOfMatches; - return this; - } - - /** - * Set match mode for Bluetooth LE scan filters hardware match - * - * @param matchMode The match mode can be one of {@link ScanSettings#MATCH_MODE_AGGRESSIVE} - * or {@link ScanSettings#MATCH_MODE_STICKY} - * @throws IllegalArgumentException If the {@code matchMode} is invalid. - */ - public Builder setMatchMode(int matchMode) { - if (matchMode < MATCH_MODE_AGGRESSIVE - || matchMode > MATCH_MODE_STICKY) { - throw new IllegalArgumentException("invalid matchMode " + matchMode); - } - mMatchMode = matchMode; - return this; - } - - /** - * Set whether only legacy advertisments should be returned in scan results. - * Legacy advertisements include advertisements as specified by the - * Bluetooth core specification 4.2 and below. This is true by default - * for compatibility with older apps. - * - * @param legacy true if only legacy advertisements will be returned - */ - public Builder setLegacy(boolean legacy) { - mLegacy = legacy; - return this; - } - - /** - * Set the Physical Layer to use during this scan. - * This is used only if {@link ScanSettings.Builder#setLegacy} - * is set to false. - * {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported} - * may be used to check whether LE Coded phy is supported by calling - * {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported}. - * Selecting an unsupported phy will result in failure to start scan. - * - * @param phy Can be one of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_CODED} or {@link ScanSettings#PHY_LE_ALL_SUPPORTED} - */ - public Builder setPhy(int phy) { - mPhy = phy; - return this; - } - - /** - * Build {@link ScanSettings}. - */ - public ScanSettings build() { - return new ScanSettings(mScanMode, mCallbackType, mScanResultType, - mReportDelayMillis, mMatchMode, - mNumOfMatchesPerFilter, mLegacy, mPhy); - } - } -} diff --git a/core/java/android/bluetooth/le/TransportBlock.java b/core/java/android/bluetooth/le/TransportBlock.java deleted file mode 100644 index 18bad9c3c259..000000000000 --- a/core/java/android/bluetooth/le/TransportBlock.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * Wrapper for Transport Discovery Data Transport Blocks. - * This class represents a Transport Block from a Transport Discovery Data. - * - * @see TransportDiscoveryData - * @see AdvertiseData - */ -public final class TransportBlock implements Parcelable { - private static final String TAG = "TransportBlock"; - private final int mOrgId; - private final int mTdsFlags; - private final int mTransportDataLength; - private final byte[] mTransportData; - - /** - * Creates an instance of TransportBlock from raw data. - * - * @param orgId the Organization ID - * @param tdsFlags the TDS flags - * @param transportDataLength the total length of the Transport Data - * @param transportData the Transport Data - */ - public TransportBlock(int orgId, int tdsFlags, int transportDataLength, - @Nullable byte[] transportData) { - mOrgId = orgId; - mTdsFlags = tdsFlags; - mTransportDataLength = transportDataLength; - mTransportData = transportData; - } - - private TransportBlock(Parcel in) { - mOrgId = in.readInt(); - mTdsFlags = in.readInt(); - mTransportDataLength = in.readInt(); - if (mTransportDataLength > 0) { - mTransportData = new byte[mTransportDataLength]; - in.readByteArray(mTransportData); - } else { - mTransportData = null; - } - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mOrgId); - dest.writeInt(mTdsFlags); - dest.writeInt(mTransportDataLength); - if (mTransportData != null) { - dest.writeByteArray(mTransportData); - } - } - - /** - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - /** - * @hide - */ - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TransportBlock other = (TransportBlock) obj; - return Arrays.equals(toByteArray(), other.toByteArray()); - } - - public static final @NonNull Creator<TransportBlock> CREATOR = new Creator<TransportBlock>() { - @Override - public TransportBlock createFromParcel(Parcel in) { - return new TransportBlock(in); - } - - @Override - public TransportBlock[] newArray(int size) { - return new TransportBlock[size]; - } - }; - - /** - * Gets the Organization ID of the Transport Block which corresponds to one of the - * the Bluetooth SIG Assigned Numbers. - */ - public int getOrgId() { - return mOrgId; - } - - /** - * Gets the TDS flags of the Transport Block which represents the role of the device and - * information about its state and supported features. - */ - public int getTdsFlags() { - return mTdsFlags; - } - - /** - * Gets the total number of octets in the Transport Data field in this Transport Block. - */ - public int getTransportDataLength() { - return mTransportDataLength; - } - - /** - * Gets the Transport Data of the Transport Block which contains organization-specific data. - */ - @Nullable - public byte[] getTransportData() { - return mTransportData; - } - - /** - * Converts this TransportBlock to byte array - * - * @return byte array representation of this Transport Block or null if the conversion failed - */ - @Nullable - public byte[] toByteArray() { - try { - ByteBuffer buffer = ByteBuffer.allocate(totalBytes()); - buffer.put((byte) mOrgId); - buffer.put((byte) mTdsFlags); - buffer.put((byte) mTransportDataLength); - if (mTransportData != null) { - buffer.put(mTransportData); - } - return buffer.array(); - } catch (BufferOverflowException e) { - Log.e(TAG, "Error converting to byte array: " + e.toString()); - return null; - } - } - - /** - * @return total byte count of this TransportBlock - */ - public int totalBytes() { - // 3 uint8 + byte[] length - int size = 3 + mTransportDataLength; - return size; - } -} diff --git a/core/java/android/bluetooth/le/TransportDiscoveryData.java b/core/java/android/bluetooth/le/TransportDiscoveryData.java deleted file mode 100644 index 2b52f19798ad..000000000000 --- a/core/java/android/bluetooth/le/TransportDiscoveryData.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.nio.BufferOverflowException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Wrapper for Transport Discovery Data AD Type. - * This class contains the Transport Discovery Data AD Type Code as well as - * a list of potential Transport Blocks. - * - * @see AdvertiseData - */ -public final class TransportDiscoveryData implements Parcelable { - private static final String TAG = "TransportDiscoveryData"; - private final int mTransportDataType; - private final List<TransportBlock> mTransportBlocks; - - /** - * Creates a TransportDiscoveryData instance. - * - * @param transportDataType the Transport Discovery Data AD Type - * @param transportBlocks the list of Transport Blocks - */ - public TransportDiscoveryData(int transportDataType, - @NonNull List<TransportBlock> transportBlocks) { - mTransportDataType = transportDataType; - mTransportBlocks = transportBlocks; - } - - /** - * Creates a TransportDiscoveryData instance from byte arrays. - * - * Uses the transport discovery data bytes and parses them into an usable class. - * - * @param transportDiscoveryData the raw discovery data - */ - public TransportDiscoveryData(@NonNull byte[] transportDiscoveryData) { - ByteBuffer byteBuffer = ByteBuffer.wrap(transportDiscoveryData); - mTransportBlocks = new ArrayList(); - if (byteBuffer.remaining() > 0) { - mTransportDataType = byteBuffer.get(); - } else { - mTransportDataType = -1; - } - try { - while (byteBuffer.remaining() > 0) { - int orgId = byteBuffer.get(); - int tdsFlags = byteBuffer.get(); - int transportDataLength = byteBuffer.get(); - byte[] transportData = new byte[transportDataLength]; - byteBuffer.get(transportData, 0, transportDataLength); - mTransportBlocks.add(new TransportBlock(orgId, tdsFlags, - transportDataLength, transportData)); - } - } catch (BufferUnderflowException e) { - Log.e(TAG, "Error while parsing data: " + e.toString()); - } - } - - private TransportDiscoveryData(Parcel in) { - mTransportDataType = in.readInt(); - mTransportBlocks = in.createTypedArrayList(TransportBlock.CREATOR); - } - - /** - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - /** - * @hide - */ - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TransportDiscoveryData other = (TransportDiscoveryData) obj; - return Arrays.equals(toByteArray(), other.toByteArray()); - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mTransportDataType); - dest.writeTypedList(mTransportBlocks); - } - - public static final @NonNull Creator<TransportDiscoveryData> CREATOR = - new Creator<TransportDiscoveryData>() { - @Override - public TransportDiscoveryData createFromParcel(Parcel in) { - return new TransportDiscoveryData(in); - } - - @Override - public TransportDiscoveryData[] newArray(int size) { - return new TransportDiscoveryData[size]; - } - }; - - /** - * Gets the transport data type. - */ - public int getTransportDataType() { - return mTransportDataType; - } - - /** - * @return the list of {@link TransportBlock} in this TransportDiscoveryData - * or an empty list if there are no Transport Blocks - */ - @NonNull - public List<TransportBlock> getTransportBlocks() { - if (mTransportBlocks == null) { - return Collections.emptyList(); - } - return mTransportBlocks; - } - - /** - * Converts this TransportDiscoveryData to byte array - * - * @return byte array representation of this Transport Discovery Data or null if the - * conversion failed - */ - @Nullable - public byte[] toByteArray() { - try { - ByteBuffer buffer = ByteBuffer.allocate(totalBytes()); - buffer.put((byte) mTransportDataType); - for (TransportBlock transportBlock : getTransportBlocks()) { - buffer.put(transportBlock.toByteArray()); - } - return buffer.array(); - } catch (BufferOverflowException e) { - Log.e(TAG, "Error converting to byte array: " + e.toString()); - return null; - } - } - - /** - * @return total byte count of this TransportDataDiscovery - */ - public int totalBytes() { - int size = 1; // Counting Transport Data Type here. - for (TransportBlock transportBlock : getTransportBlocks()) { - size += transportBlock.totalBytes(); - } - return size; - } -} diff --git a/core/java/android/bluetooth/le/TruncatedFilter.java b/core/java/android/bluetooth/le/TruncatedFilter.java deleted file mode 100644 index 25925888a0d2..000000000000 --- a/core/java/android/bluetooth/le/TruncatedFilter.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.annotation.SuppressLint; -import android.annotation.SystemApi; - -import java.util.List; - -/** - * A special scan filter that lets the client decide how the scan record should be stored. - * - * @deprecated this is not used anywhere - * - * @hide - */ -@Deprecated -@SystemApi -@SuppressLint("AndroidFrameworkBluetoothPermission") -public final class TruncatedFilter { - private final ScanFilter mFilter; - private final List<ResultStorageDescriptor> mStorageDescriptors; - - /** - * Constructor for {@link TruncatedFilter}. - * - * @param filter Scan filter of the truncated filter. - * @param storageDescriptors Describes how the scan should be stored. - */ - public TruncatedFilter(ScanFilter filter, List<ResultStorageDescriptor> storageDescriptors) { - mFilter = filter; - mStorageDescriptors = storageDescriptors; - } - - /** - * Returns the scan filter. - */ - public ScanFilter getFilter() { - return mFilter; - } - - /** - * Returns a list of descriptor for scan result storage. - */ - public List<ResultStorageDescriptor> getStorageDescriptors() { - return mStorageDescriptors; - } - - -} diff --git a/core/java/android/bluetooth/package.html b/core/java/android/bluetooth/package.html deleted file mode 100644 index d9ca4f13101f..000000000000 --- a/core/java/android/bluetooth/package.html +++ /dev/null @@ -1,38 +0,0 @@ -<HTML> -<BODY> -<p>Provides classes that manage Bluetooth functionality, such as scanning for -devices, connecting with devices, and managing data transfer between devices. -The Bluetooth API supports both "Classic Bluetooth" and Bluetooth Low Energy.</p> - -<p>For more information about Classic Bluetooth, see the -<a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> guide. -For more information about Bluetooth Low Energy, see the -<a href="{@docRoot}guide/topics/connectivity/bluetooth-le.html"> -Bluetooth Low Energy</a> (BLE) guide.</p> -{@more} - -<p>The Bluetooth APIs let applications:</p> -<ul> - <li>Scan for other Bluetooth devices (including BLE devices).</li> - <li>Query the local Bluetooth adapter for paired Bluetooth devices.</li> - <li>Establish RFCOMM channels/sockets.</li> - <li>Connect to specified sockets on other devices.</li> - <li>Transfer data to and from other devices.</li> - <li>Communicate with BLE devices, such as proximity sensors, heart rate - monitors, fitness devices, and so on.</li> - <li>Act as a GATT client or a GATT server (BLE).</li> -</ul> - -<p> -To perform Bluetooth communication using these APIs, an application must -declare the {@link android.Manifest.permission#BLUETOOTH} permission. Some -additional functionality, such as requesting device discovery, -also requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} -permission. -</p> - -<p class="note"><strong>Note:</strong> -Not all Android-powered devices provide Bluetooth functionality.</p> - -</BODY> -</HTML> diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index f0566b856dbd..373a8d957282 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -54,13 +54,13 @@ public final class AssociationInfo implements Parcelable { private final @Nullable String mDeviceProfile; private final boolean mSelfManaged; - private boolean mNotifyOnDeviceNearby; + private final boolean mNotifyOnDeviceNearby; private final long mTimeApprovedMs; /** * A long value indicates the last time connected reported by selfManaged devices * Default value is Long.MAX_VALUE. */ - private long mLastTimeConnectedMs; + private final long mLastTimeConnectedMs; /** * Creates a new Association. @@ -160,22 +160,6 @@ public final class AssociationInfo implements Parcelable { return mSelfManaged; } - /** - * Should only be used by the CompanionDeviceManagerService. - * @hide - */ - public void setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) { - mNotifyOnDeviceNearby = notifyOnDeviceNearby; - } - - /** - * Should only be used by the CompanionDeviceManagerService. - * @hide - */ - public void setLastTimeConnected(long lastTimeConnectedMs) { - mLastTimeConnectedMs = lastTimeConnectedMs; - } - /** @hide */ public boolean isNotifyOnDeviceNearby() { return mNotifyOnDeviceNearby; @@ -330,4 +314,112 @@ public final class AssociationInfo implements Parcelable { return new AssociationInfo(in); } }; + + /** + * Use this method to obtain a builder that you can use to create a copy of the + * given {@link AssociationInfo} with modified values of {@code mLastTimeConnected} + * or {@code mNotifyOnDeviceNearby}. + * <p> + * Note that you <b>must</b> call either {@link Builder#setLastTimeConnected(long) + * setLastTimeConnected} or {@link Builder#setNotifyOnDeviceNearby(boolean) + * setNotifyOnDeviceNearby} before you will be able to call {@link Builder#build() build}. + * + * This is ensured statically at compile time. + * + * @hide + */ + @NonNull + public static NonActionableBuilder builder(@NonNull AssociationInfo info) { + return new Builder(info); + } + + /** + * @hide + */ + public static final class Builder implements NonActionableBuilder { + @NonNull + private final AssociationInfo mOriginalInfo; + private boolean mNotifyOnDeviceNearby; + private long mLastTimeConnectedMs; + + private Builder(@NonNull AssociationInfo info) { + mOriginalInfo = info; + mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; + mLastTimeConnectedMs = info.mLastTimeConnectedMs; + } + + /** + * Should only be used by the CompanionDeviceManagerService. + * @hide + */ + @Override + @NonNull + public Builder setLastTimeConnected(long lastTimeConnectedMs) { + if (lastTimeConnectedMs < 0) { + throw new IllegalArgumentException( + "lastTimeConnectedMs must not be negative! (Given " + lastTimeConnectedMs + + " )"); + } + mLastTimeConnectedMs = lastTimeConnectedMs; + return this; + } + + /** + * Should only be used by the CompanionDeviceManagerService. + * @hide + */ + @Override + @NonNull + public Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) { + mNotifyOnDeviceNearby = notifyOnDeviceNearby; + return this; + } + + /** + * @hide + */ + @NonNull + public AssociationInfo build() { + return new AssociationInfo( + mOriginalInfo.mId, + mOriginalInfo.mUserId, + mOriginalInfo.mPackageName, + mOriginalInfo.mDeviceMacAddress, + mOriginalInfo.mDisplayName, + mOriginalInfo.mDeviceProfile, + mOriginalInfo.mSelfManaged, + mNotifyOnDeviceNearby, + mOriginalInfo.mTimeApprovedMs, + mLastTimeConnectedMs + ); + } + } + + /** + * This interface is returned from the + * {@link AssociationInfo#builder(android.companion.AssociationInfo) builder} entry point + * to indicate that this builder is not yet in a state that can produce a meaningful + * {@link AssociationInfo} object that is different from the one originally passed in. + * + * <p> + * Only by calling one of the setter methods is this builder turned into one where calling + * {@link Builder#build() build()} makes sense. + * + * @hide + */ + public interface NonActionableBuilder { + /** + * Should only be used by the CompanionDeviceManagerService. + * @hide + */ + @NonNull + Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby); + + /** + * Should only be used by the CompanionDeviceManagerService. + * @hide + */ + @NonNull + Builder setLastTimeConnected(long lastTimeConnectedMs); + } } diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 12ced96a0ffb..610b7ee5befa 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -31,29 +31,71 @@ import android.util.Log; import java.util.Objects; /** - * Service to be implemented by apps that manage a companion device. + * A service that receives calls from the system when the associated companion device appears + * nearby or is connected, as well as when the device is no longer "present" or connected. + * See {@link #onDeviceAppeared(AssociationInfo)}/{@link #onDeviceDisappeared(AssociationInfo)}. * - * System will keep this service bound whenever an associated device is nearby for Bluetooth - * devices or companion app manages the connectivity and reports disappeared, ensuring app stays - * alive + * <p> + * Additionally, the service will receive a call from the system, if and when the system needs to + * transfer data to the companion device. + * See {@link #dispatchMessage(int, int, byte[])}). * - * An app must be {@link CompanionDeviceManager#associate associated} with at leas one device, - * before it can take advantage of this service. + * <p> + * Companion applications must create a service that {@code extends} + * {@link CompanionDeviceService}, and declare it in their AndroidManifest.xml with the + * "android.permission.BIND_COMPANION_DEVICE_SERVICE" permission + * (see {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}), + * as well as add an intent filter for the "android.companion.CompanionDeviceService" action + * (see {@link #SERVICE_INTERFACE}). * - * You must declare this service in your manifest with an - * intent-filter action of {@link #SERVICE_INTERFACE} and - * permission of {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE} + * <p> + * Following is an example of such declaration: + * <pre>{@code + * <service + * android:name=".CompanionService" + * android:label="@string/service_name" + * android:exported="true" + * android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE"> + * <intent-filter> + * <action android:name="android.companion.CompanionDeviceService" /> + * </intent-filter> + * </service> + * }</pre> * - * <p>If you want to declare more than one of these services, you must declare the meta-data in the - * service of your manifest with the corresponding name and value to true to indicate the - * primary service. - * Only the primary one will get the callback from - * {@link #onDeviceAppeared(AssociationInfo associationInfo)}.</p> + * <p> + * If the companion application has requested observing device presence (see + * {@link CompanionDeviceManager#startObservingDevicePresence(String)}) the system will + * <a href="https://developer.android.com/guide/components/bound-services"> bind the service</a> + * when it detects the device nearby (for BLE devices) or when the device is connected + * (for Bluetooth devices). * - * Example: + * <p> + * The system binding {@link CompanionDeviceService} elevates the priority of the process that + * the service is running in, and thus may prevent + * <a href="https://developer.android.com/topic/performance/memory-management#low-memory_killer"> + * the Low-memory killer</a> from killing the process at expense of other processes with lower + * priority. + * + * <p> + * It is possible for an application to declare multiple {@link CompanionDeviceService}-s. + * In such case, the system will bind all declared services, but will deliver + * {@link #onDeviceAppeared(AssociationInfo)}, {@link #onDeviceDisappeared(AssociationInfo)} and + * {@link #dispatchMessage(int, int, byte[])} only to one "primary" services. + * Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary" + * service using "android.companion.primary" tag. + * <pre>{@code * <meta-data - * android:name="primary" - * android:value="true" /> + * android:name="android.companion.primary" + * android:value="true" /> + * }</pre> + * + * <p> + * If the application declares multiple {@link CompanionDeviceService}-s, but does not indicate + * the "primary" one, the system will pick one of the declared services to use as "primary". + * + * <p> + * If the application declares multiple "primary" {@link CompanionDeviceService}-s, the system + * will pick single one of them to use as "primary". */ public abstract class CompanionDeviceService extends Service { diff --git a/core/java/android/companion/IOnAssociationsChangedListener.aidl b/core/java/android/companion/IOnAssociationsChangedListener.aidl index e6794b741e46..d3694564ab7b 100644 --- a/core/java/android/companion/IOnAssociationsChangedListener.aidl +++ b/core/java/android/companion/IOnAssociationsChangedListener.aidl @@ -20,5 +20,22 @@ import android.companion.AssociationInfo; /** @hide */ interface IOnAssociationsChangedListener { - oneway void onAssociationsChanged(in List<AssociationInfo> associations); + + /* + * IMPORTANT: This method is intentionally NOT "oneway". + * + * The method is intentionally "blocking" to make sure that the clients of the + * addOnAssociationsChangedListener() API (@SystemAPI guarded by a "signature" permission) are + * able to prevent race conditions that may arise if their own clients (applications) + * effectively get notified about the changes before system services do. + * + * This is safe for 2 reasons: + * 1. The addOnAssociationsChangedListener() is only available to the system components + * (guarded by a "signature" permission). + * See android.permission.MANAGE_COMPANION_DEVICES. + * 2. On the Java side addOnAssociationsChangedListener() in CDM takes an Executor, and the + * proxy implementation of onAssociationsChanged() simply "post" a Runnable to it. + * See CompanionDeviceManager.OnAssociationsChangedListenerProxy class. + */ + void onAssociationsChanged(in List<AssociationInfo> associations); }
\ No newline at end of file diff --git a/core/java/android/companion/SystemDataTransferRequest.aidl b/core/java/android/companion/SystemDataTransferRequest.aidl new file mode 100644 index 000000000000..19ae60effa7a --- /dev/null +++ b/core/java/android/companion/SystemDataTransferRequest.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.companion; + +parcelable SystemDataTransferRequest; diff --git a/core/java/android/companion/SystemDataTransferRequest.java b/core/java/android/companion/SystemDataTransferRequest.java new file mode 100644 index 000000000000..e3b0369e203d --- /dev/null +++ b/core/java/android/companion/SystemDataTransferRequest.java @@ -0,0 +1,157 @@ +/* + * 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 android.companion; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.OneTimeUseBuilder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A request for users to allow the companion app to transfer system data to the companion devices. + * + * @hide + */ +public final class SystemDataTransferRequest implements Parcelable { + + private final int mAssociationId; + private final boolean mPermissionSyncAllPackages; + private final List<String> mPermissionSyncPackages; + + /** + * @hide + */ + public SystemDataTransferRequest(int associationId, boolean syncAllPackages, + @Nullable List<String> permissionSyncPackages) { + mAssociationId = associationId; + mPermissionSyncAllPackages = syncAllPackages; + mPermissionSyncPackages = permissionSyncPackages; + } + + public int getAssociationId() { + return mAssociationId; + } + + @NonNull + public boolean isPermissionSyncAllPackages() { + return mPermissionSyncAllPackages; + } + + @NonNull + public List<String> getPermissionSyncPackages() { + return mPermissionSyncPackages; + } + + /** + * A builder for {@link SystemDataTransferRequest}. + * + * <p>You have to call one of the below methods to create a valid request</p> + * <br>1. {@link #setPermissionSyncAllPackages()} + * <br>2. {@link #setPermissionSyncPackages(List)} + */ + public static final class Builder extends OneTimeUseBuilder<SystemDataTransferRequest> { + + private final int mAssociationId; + private boolean mPermissionSyncAllPackages; + private List<String> mPermissionSyncPackages = new ArrayList<>(); + + public Builder(int associationId) { + mAssociationId = associationId; + } + + /** + * Call to sync permissions for all the packages. You can optionally call + * {@link #setPermissionSyncPackages(List)} to specify the packages to sync permissions. + * + * <p>The system will only sync permissions that are explicitly granted by the user.</p> + * + * <p>If a permission is granted or revoked by the system or a policy, even if the user has + * explicitly granted or revoked the permission earlier, the permission will be ignored.</p> + * + * <p>If a system or policy granted or revoked permission is granted or revoked by the user + * later, the permission will be ignored.</p> + * + * @see #setPermissionSyncPackages(List) + * + * @return the builder + */ + @NonNull + public Builder setPermissionSyncAllPackages() { + mPermissionSyncAllPackages = true; + return this; + } + + /** + * Set a list of packages to sync permissions. You can optionally call + * {@link #setPermissionSyncAllPackages()} to sync permissions for all the packages. + * + * @see #setPermissionSyncAllPackages() + * + * @param permissionSyncPackages packages to sync permissions + * @return builder + */ + @NonNull + public Builder setPermissionSyncPackages(@NonNull List<String> permissionSyncPackages) { + mPermissionSyncPackages = permissionSyncPackages; + return this; + } + + @Override + @NonNull + public SystemDataTransferRequest build() { + return new SystemDataTransferRequest(mAssociationId, mPermissionSyncAllPackages, + mPermissionSyncPackages); + } + } + + SystemDataTransferRequest(Parcel in) { + mAssociationId = in.readInt(); + mPermissionSyncAllPackages = in.readBoolean(); + mPermissionSyncPackages = Arrays.asList(in.createString8Array()); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mAssociationId); + dest.writeBoolean(mPermissionSyncAllPackages); + dest.writeString8Array(mPermissionSyncPackages.toArray(new String[0])); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator<SystemDataTransferRequest> CREATOR = + new Creator<SystemDataTransferRequest>() { + @Override + public SystemDataTransferRequest createFromParcel(Parcel in) { + return new SystemDataTransferRequest(in); + } + + @Override + public SystemDataTransferRequest[] newArray(int size) { + return new SystemDataTransferRequest[size]; + } + }; +} diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 82ad15057fe3..85855bedfbeb 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -16,12 +16,14 @@ package android.companion.virtual; +import android.app.PendingIntent; import android.graphics.Point; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; import android.hardware.input.VirtualMouseScrollEvent; import android.hardware.input.VirtualTouchEvent; +import android.os.ResultReceiver; /** * Interface for a virtual device. @@ -41,6 +43,7 @@ interface IVirtualDevice { * Closes the virtual device and frees all associated resources. */ void close(); + void createVirtualKeyboard( int displayId, String inputDeviceName, @@ -66,4 +69,10 @@ interface IVirtualDevice { boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event); boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event); boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event); + + /** + * Launches a pending intent on the given display that is owned by this virtual device. + */ + void launchPendingIntent( + int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index 2dfa2021fdfe..d80bee668f18 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -17,6 +17,7 @@ package android.companion.virtual; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.VirtualDeviceParams; /** * Interface for communication between VirtualDeviceManager and VirtualDeviceManagerService. @@ -33,6 +34,10 @@ interface IVirtualDeviceManager { * that this belongs to the calling UID. * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from * CDM. Virtual devices must have a corresponding association with CDM in order to be created. + * @param params The parameters for creating this virtual device. See {@link + * VirtualDeviceManager.VirtualDeviceParams}. */ - IVirtualDevice createVirtualDevice(in IBinder token, String packageName, int associationId); + IVirtualDevice createVirtualDevice( + in IBinder token, String packageName, int associationId, + in VirtualDeviceParams params); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index bace45bccbf4..8ab668873f33 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -23,6 +23,8 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.Activity; +import android.app.PendingIntent; import android.companion.AssociationInfo; import android.content.Context; import android.graphics.Point; @@ -33,15 +35,19 @@ import android.hardware.input.VirtualKeyboard; import android.hardware.input.VirtualMouse; import android.hardware.input.VirtualTouchscreen; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; +import android.os.ResultReceiver; import android.view.Surface; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.concurrent.Executor; /** * System level service for managing virtual devices. @@ -100,11 +106,11 @@ public final class VirtualDeviceManager { */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @Nullable - public VirtualDevice createVirtualDevice(int associationId) { + public VirtualDevice createVirtualDevice(int associationId, VirtualDeviceParams params) { // TODO(b/194949534): Unhide this API try { IVirtualDevice virtualDevice = mService.createVirtualDevice( - new Binder(), mContext.getPackageName(), associationId); + new Binder(), mContext.getPackageName(), associationId, params); return new VirtualDevice(mContext, virtualDevice); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -129,6 +135,49 @@ public final class VirtualDeviceManager { } /** + * Launches a given pending intent on the give display ID. + * + * @param displayId The display to launch the pending intent on. This display must be + * created from this virtual device. + * @param pendingIntent The pending intent to be launched. If the intent is an activity + * intent, the activity will be started on the virtual display using + * {@link android.app.ActivityOptions#setLaunchDisplayId}. If the intent is a service or + * broadcast intent, an attempt will be made to catch activities started as a result of + * sending the pending intent and move them to the given display. + * @param executor The executor to run {@code launchCallback} on. + * @param launchCallback Callback that is called when the pending intent launching is + * complete. + * + * @hide + */ + public void launchPendingIntent( + int displayId, + @NonNull PendingIntent pendingIntent, + @NonNull Executor executor, + @NonNull LaunchCallback launchCallback) { + try { + mVirtualDevice.launchPendingIntent( + displayId, + pendingIntent, + new ResultReceiver(new Handler(Looper.myLooper())) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + super.onReceiveResult(resultCode, resultData); + executor.execute(() -> { + if (resultCode == Activity.RESULT_OK) { + launchCallback.onLaunchSuccess(); + } else { + launchCallback.onLaunchFailed(); + } + }); + } + }); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Creates a virtual display for this virtual device. All displays created on the same * device belongs to the same display group. * @@ -273,6 +322,12 @@ public final class VirtualDeviceManager { } } + /** + * Returns the display flags that should be added to a particular virtual display. + * Additional device-level flags from {@link + * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will + * be added by DisplayManagerService. + */ private int getVirtualDisplayFlags(@DisplayFlags int flags) { int virtualDisplayFlags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; if ((flags & DISPLAY_FLAG_TRUSTED) != 0) { @@ -293,4 +348,22 @@ public final class VirtualDeviceManager { } } } + + /** + * Callback for launching pending intents on the virtual device. + * + * @hide + */ + // TODO(b/194949534): Unhide this API + public interface LaunchCallback { + /** + * Called when the pending intent launched successfully. + */ + void onLaunchSuccess(); + + /** + * Called when the pending intent failed to launch. + */ + void onLaunchFailed(); + } } diff --git a/core/java/android/app/communal/ICommunalManager.aidl b/core/java/android/companion/virtual/VirtualDeviceParams.aidl index df9d7cec05f7..9b3974a9c904 100644 --- a/core/java/android/app/communal/ICommunalManager.aidl +++ b/core/java/android/companion/virtual/VirtualDeviceParams.aidl @@ -14,14 +14,6 @@ * limitations under the License. */ -package android.app.communal; +package android.companion.virtual; -/** - * System private API for talking with the communal manager service that handles communal mode - * state. - * - * @hide - */ -interface ICommunalManager { - oneway void setCommunalViewShowing(boolean isShowing); -}
\ No newline at end of file +parcelable VirtualDeviceParams; diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java new file mode 100644 index 000000000000..169f4e1ad281 --- /dev/null +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual; + +import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; +import android.util.ArraySet; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +/** + * Params that can be configured when creating virtual devices. + * + * @hide + */ +// TODO(b/194949534): Unhide this API +public final class VirtualDeviceParams implements Parcelable { + + /** @hide */ + @IntDef(prefix = "LOCK_STATE_", + value = {LOCK_STATE_ALWAYS_LOCKED, LOCK_STATE_ALWAYS_UNLOCKED}) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface LockState {} + + /** + * Indicates that the lock state of the virtual device should be always locked. + * + * @hide // TODO(b/194949534): Unhide this API + */ + public static final int LOCK_STATE_ALWAYS_LOCKED = 0; + + /** + * Indicates that the lock state of the virtual device should be always unlocked. + * + * @hide // TODO(b/194949534): Unhide this API + */ + public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; + + private final int mLockState; + private final ArraySet<UserHandle> mUsersWithMatchingAccounts; + @Nullable private final ArraySet<ComponentName> mAllowedActivities; + @Nullable private final ArraySet<ComponentName> mBlockedActivities; + + private VirtualDeviceParams( + @LockState int lockState, + @NonNull Set<UserHandle> usersWithMatchingAccounts, + @Nullable Set<ComponentName> allowedActivities, + @Nullable Set<ComponentName> blockedActivities) { + mLockState = lockState; + mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); + mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); + mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); + } + + @SuppressWarnings("unchecked") + private VirtualDeviceParams(Parcel parcel) { + mLockState = parcel.readInt(); + mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null); + mAllowedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null); + mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null); + } + + /** + * Returns the lock state of the virtual device. + */ + @LockState + public int getLockState() { + return mLockState; + } + + /** + * Returns the user handles with matching managed accounts on the remote device to which + * this virtual device is streaming. + * + * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY + */ + @NonNull + public Set<UserHandle> getUsersWithMatchingAccounts() { + return Collections.unmodifiableSet(mUsersWithMatchingAccounts); + } + + /** + * Returns the set of activities allowed to be streamed, or {@code null} if this is not set. + * + * @see Builder#setAllowedActivities(Set) + */ + @Nullable + public Set<ComponentName> getAllowedActivities() { + if (mAllowedActivities == null) { + return null; + } + return Collections.unmodifiableSet(mAllowedActivities); + } + + /** + * Returns the set of activities that are blocked from streaming, or {@code null} if this is not + * set. + * + * @see Builder#setBlockedActivities(Set) + */ + @Nullable + public Set<ComponentName> getBlockedActivities() { + if (mBlockedActivities == null) { + return null; + } + return Collections.unmodifiableSet(mBlockedActivities); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mLockState); + dest.writeArraySet(mUsersWithMatchingAccounts); + dest.writeArraySet(mAllowedActivities); + dest.writeArraySet(mBlockedActivities); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VirtualDeviceParams)) { + return false; + } + VirtualDeviceParams that = (VirtualDeviceParams) o; + return mLockState == that.mLockState + && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts) + && Objects.equals(mAllowedActivities, that.mAllowedActivities) + && Objects.equals(mBlockedActivities, that.mBlockedActivities); + } + + @Override + public int hashCode() { + return Objects.hash(mLockState, mUsersWithMatchingAccounts); + } + + @Override + public String toString() { + return "VirtualDeviceParams(" + + " mLockState=" + mLockState + + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts + + " mAllowedActivities=" + mAllowedActivities + + " mBlockedActivities=" + mBlockedActivities + + ")"; + } + + public static final Parcelable.Creator<VirtualDeviceParams> CREATOR = + new Parcelable.Creator<VirtualDeviceParams>() { + public VirtualDeviceParams createFromParcel(Parcel in) { + return new VirtualDeviceParams(in); + } + + public VirtualDeviceParams[] newArray(int size) { + return new VirtualDeviceParams[size]; + } + }; + + /** + * Builder for {@link VirtualDeviceParams}. + */ + public static final class Builder { + + private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED; + private Set<UserHandle> mUsersWithMatchingAccounts; + @Nullable private Set<ComponentName> mBlockedActivities; + @Nullable private Set<ComponentName> mAllowedActivities; + + /** + * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} + * is required if this is set to {@link #LOCK_STATE_ALWAYS_UNLOCKED}. + * The default is {@link #LOCK_STATE_ALWAYS_LOCKED}. + * + * @param lockState The lock state, either {@link #LOCK_STATE_ALWAYS_LOCKED} or + * {@link #LOCK_STATE_ALWAYS_UNLOCKED}. + */ + @RequiresPermission(value = ADD_ALWAYS_UNLOCKED_DISPLAY, conditional = true) + @NonNull + public Builder setLockState(@LockState int lockState) { + mLockState = lockState; + return this; + } + + /** + * Sets the user handles with matching managed accounts on the remote device to which + * this virtual device is streaming. + * + * @param usersWithMatchingAccounts A set of user handles with matching managed + * accounts on the remote device this is streaming to. + * + * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY + */ + public Builder setUsersWithMatchingAccounts( + @NonNull Set<UserHandle> usersWithMatchingAccounts) { + mUsersWithMatchingAccounts = usersWithMatchingAccounts; + return this; + } + + /** + * Sets the activities allowed to be launched in the virtual device. If + * {@code allowedActivities} is non-null, all activities other than the ones in the set will + * be blocked from launching. + * + * <p>{@code allowedActivities} and the set in {@link #setBlockedActivities(Set)} cannot + * both be non-null at the same time. + * + * @throws IllegalArgumentException if {@link #setBlockedActivities(Set)} has been set to a + * non-null value. + * + * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched + * in the virtual device. + */ + public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) { + if (mBlockedActivities != null && allowedActivities != null) { + throw new IllegalArgumentException( + "Allowed activities and Blocked activities cannot both be set."); + } + mAllowedActivities = allowedActivities; + return this; + } + + /** + * Sets the activities blocked from launching in the virtual device. If the {@code + * blockedActivities} is non-null, activities in the set are blocked from launching in the + * virtual device. + * + * {@code blockedActivities} and the set in {@link #setAllowedActivities(Set)} cannot both + * be non-null at the same time. + * + * @throws IllegalArgumentException if {@link #setAllowedActivities(Set)} has been set to a + * non-null value. + * + * @param blockedActivities A set of {@link ComponentName} to be blocked launching from + * virtual device. + */ + public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) { + if (mAllowedActivities != null && blockedActivities != null) { + throw new IllegalArgumentException( + "Allowed activities and Blocked activities cannot both be set."); + } + mBlockedActivities = blockedActivities; + return this; + } + + /** + * Builds the {@link VirtualDeviceParams} instance. + */ + @NonNull + public VirtualDeviceParams build() { + if (mUsersWithMatchingAccounts == null) { + mUsersWithMatchingAccounts = Collections.emptySet(); + } + if (mAllowedActivities != null && mBlockedActivities != null) { + // Should never reach here because the setters block this as well. + throw new IllegalStateException( + "Allowed activities and Blocked activities cannot both be set."); + } + return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts, + mAllowedActivities, mBlockedActivities); + } + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index bccfacf70842..2309fb636d44 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -94,8 +94,11 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Interface to global information about an application environment. This is @@ -3598,10 +3601,18 @@ public abstract class Context { * Binds to a service in the given {@code user} in the same manner as * {@link #bindService(Intent, ServiceConnection, int)}. * - * <p>If the given {@code user} is in the same profile group and the target package is the - * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is - * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS} - * for interacting with other users. + * <p>Requires that one of the following conditions are met: + * <ul> + * <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}</li> + * <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS} and is the same + * package as the {@code service} (determined by its component's package) and the Android + * version is at least {@link android.os.Build.VERSION_CODES#S}</li> + * <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS} and is in same + * profile group as the given {@code user}</li> + * <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} and is in same + * profile group as the given {@code user} and is the same package as the {@code service} + * </li> + * </ul> * * @param service Identifies the service to connect to. The Intent must * specify an explicit component name. @@ -3623,8 +3634,9 @@ public abstract class Context { @SuppressWarnings("unused") @RequiresPermission(anyOf = { android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.INTERACT_ACROSS_PROFILES - }) + }, conditional = true) public boolean bindServiceAsUser( @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags, @NonNull UserHandle user) { @@ -3637,7 +3649,11 @@ public abstract class Context { * * @hide */ - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }, conditional = true) @UnsupportedAppUsage(trackingBug = 136728678) public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { @@ -5879,17 +5895,6 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a - * {@link android.app.communal.CommunalManager} for interacting with the global system state. - * - * @see #getSystemService(String) - * @see android.app.communal.CommunalManager - * @hide - */ - @SystemApi - public static final String COMMUNAL_SERVICE = "communal"; - - /** - * Use with {@link #getSystemService(String)} to retrieve a * {@link android.app.LocaleManager}. * * @see #getSystemService(String) @@ -6407,6 +6412,43 @@ public abstract class Context { @Nullable String writePermission, int pid, int uid, @Intent.AccessUriMode int modeFlags, @Nullable String message); + + /** + * Triggers the asynchronous revocation of a permission. + * + * @param permName The name of the permission to be revoked. + * @see #selfRevokePermissions(Collection) + */ + public void selfRevokePermission(@NonNull String permName) { + selfRevokePermissions(Collections.singletonList(permName)); + } + + /** + * Triggers the revocation of one or more permissions for the calling package. A package is only + * able to revoke a permission under the following conditions: + * <ul> + * <li>Each permission in {@code permissions} must be granted to the calling package. + * <li>Each permission in {@code permissions} must be a runtime permission. + * </ul> + * <p> + * For every permission in {@code permissions}, the entire permission group it belongs to will + * be revoked. The revocation happens asynchronously and kills all processes running in the + * calling UID. It will be triggered once it is safe to do so. In particular, it will not be + * triggered as long as the package remains in the foreground, or has any active manifest + * components (e.g. when another app is accessing a content provider in the package). + * <p> + * If you want to revoke the permissions right away, you could call {@code System.exit()}, but + * this could affect other apps that are accessing your app at the moment. For example, apps + * accessing a content provider in your app will all crash. + * + * @param permissions Collection of permissions to be revoked. + * @see PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer) + * @see PackageManager#getPlatformPermissionsForGroup(String, Executor, Consumer) + */ + public void selfRevokePermissions(@NonNull Collection<String> permissions) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + /** @hide */ @IntDef(flag = true, prefix = { "CONTEXT_" }, value = { CONTEXT_INCLUDE_CODE, diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 3a02004edb1f..805e499bba46 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -53,6 +53,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -1015,6 +1016,11 @@ public class ContextWrapper extends Context { } @Override + public void selfRevokePermissions(@NonNull Collection<String> permissions) { + mBase.selfRevokePermissions(permissions); + } + + @Override public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { return mBase.createPackageContext(packageName, flags); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index af84392b5169..58a7d8796ffb 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2974,6 +2974,9 @@ public class Intent implements Parcelable, Cloneable { * Broadcast Action: A uid has been removed from the system. The uid * number is stored in the extra data under {@link #EXTRA_UID}. * + * In certain instances, {@link #EXTRA_REPLACING} is set to true if the UID is not being + * fully removed. + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -4391,9 +4394,9 @@ public class Intent implements Parcelable, Cloneable { * restored from (corresponds to {@link android.os.Build.VERSION#SDK_INT}). The first three * values are represented as strings, the fourth one as int. * - * <p>This broadcast is sent only for settings provider entries known to require special handling - * around restore time. These entries are found in the BROADCAST_ON_RESTORE table within - * the provider's backup agent implementation. + * <p>This broadcast is sent only for settings provider entries known to require special + * handling around restore time to specific receivers. These entries are found in the + * BROADCAST_ON_RESTORE table within the provider's backup agent implementation. * * @see #EXTRA_SETTING_NAME * @see #EXTRA_SETTING_PREVIOUS_VALUE @@ -4401,15 +4404,46 @@ public class Intent implements Parcelable, Cloneable { * @see #EXTRA_SETTING_RESTORED_FROM_SDK_INT * {@hide} */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("ActionValue") public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED"; - /** {@hide} */ + /** + * String intent extra to be used with {@link ACTION_SETTING_RESTORED}. + * Contain the name of the restored setting. + * {@hide} + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("ActionValue") public static final String EXTRA_SETTING_NAME = "setting_name"; - /** {@hide} */ + + /** + * String intent extra to be used with {@link ACTION_SETTING_RESTORED}. + * Contain the value of the {@link EXTRA_SETTING_NAME} settings entry prior to the restore + * operation. + * {@hide} + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("ActionValue") public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value"; - /** {@hide} */ + + /** + * String intent extra to be used with {@link ACTION_SETTING_RESTORED}. + * Contain the value of the {@link EXTRA_SETTING_NAME} settings entry being restored. + * {@hide} + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("ActionValue") public static final String EXTRA_SETTING_NEW_VALUE = "new_value"; - /** {@hide} */ + + /** + * Int intent extra to be used with {@link ACTION_SETTING_RESTORED}. + * Contain the version of the SDK that the setting has been restored from (corresponds to + * {@link android.os.Build.VERSION#SDK_INT}). + * {@hide} + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("ActionValue") public static final String EXTRA_SETTING_RESTORED_FROM_SDK_INT = "restored_from_sdk_int"; /** diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 32827ae11e0b..b3435b1180c2 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -1182,7 +1182,7 @@ public class IntentFilter implements Parcelable { public int match(Uri data, boolean wildcardSupported) { String host = data.getHost(); if (host == null) { - if (wildcardSupported && mWild) { + if (wildcardSupported && mWild && mHost.isEmpty()) { // special case, if no host is provided, but the Authority is wildcard, match return MATCH_CATEGORY_HOST; } else { diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 806091e2158d..8d9ef8530bfc 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -423,7 +423,7 @@ public class AppSearchShortcutInfo extends GenericDocument { shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage, disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras, getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri, - disabledReason, persons, locusId, null); + disabledReason, persons, locusId, null, null); si.setImplicitRank(implicitRank); if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) { si.setRankChanged(); diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 76b4e5c4dba0..9e9dd1edd577 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1543,6 +1543,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { @Nullable private ArrayMap<String, String> mAppClassNamesByProcess; + /** + * Resource file providing the application's locales configuration. + */ + private int localeConfigRes; + public void dump(Printer pw, String prefix) { dump(pw, prefix, DUMP_FLAG_ALL); } @@ -1660,6 +1665,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { pw.println(prefix + "requestRawExternalStorageAccess=" + requestRawExternalStorageAccess); } + if (localeConfigRes != 0) { + pw.println(prefix + "localeConfigRes=0x" + + Integer.toHexString(localeConfigRes)); + } } pw.println(prefix + "createTimestamp=" + createTimestamp); super.dumpBack(pw, prefix); @@ -1891,6 +1900,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { memtagMode = orig.memtagMode; nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized; requestRawExternalStorageAccess = orig.requestRawExternalStorageAccess; + localeConfigRes = orig.localeConfigRes; createTimestamp = System.currentTimeMillis(); } @@ -1993,6 +2003,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString(mAppClassNamesByProcess.valueAt(i)); } } + dest.writeInt(localeConfigRes); } public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR @@ -2088,6 +2099,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { mAppClassNamesByProcess.put(source.readString(), source.readString()); } } + localeConfigRes = source.readInt(); } /** @@ -2631,4 +2643,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } return className; } + + /** @hide */ public void setLocaleConfigRes(int value) { localeConfigRes = value; } + + /** + * Return the resource id pointing to the resource file that provides the application's locales + * configuration. + * + * @hide + */ + public int getLocaleConfigRes() { + return localeConfigRes; + } } diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java index 7bade740c9be..1e0deffbf8cd 100644 --- a/core/java/android/content/pm/BaseParceledListSlice.java +++ b/core/java/android/content/pm/BaseParceledListSlice.java @@ -50,10 +50,12 @@ abstract class BaseParceledListSlice<T> implements Parcelable { */ private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); - private final List<T> mList; + private List<T> mList; private int mInlineCountLimit = Integer.MAX_VALUE; + private boolean mHasBeenParceled = false; + public BaseParceledListSlice(List<T> list) { mList = list; } @@ -151,9 +153,17 @@ abstract class BaseParceledListSlice<T> implements Parcelable { * Write this to another Parcel. Note that this discards the internal Parcel * and should not be used anymore. This is so we can pass this to a Binder * where we won't have a chance to call recycle on this. + * + * This method can only be called once per BaseParceledListSlice to ensure that + * the referenced list can be cleaned up before the recipient cleans up the + * Binder reference. */ @Override public void writeToParcel(Parcel dest, int flags) { + if (mHasBeenParceled) { + throw new IllegalStateException("Can't Parcel a ParceledListSlice more than once"); + } + mHasBeenParceled = true; final int N = mList.size(); final int callFlags = flags; dest.writeInt(N); @@ -180,9 +190,17 @@ abstract class BaseParceledListSlice<T> implements Parcelable { throws RemoteException { if (code != FIRST_CALL_TRANSACTION) { return super.onTransact(code, data, reply, flags); + } else if (mList == null) { + throw new IllegalArgumentException("Attempt to transfer null list, " + + "did transfer finish?"); } int i = data.readInt(); - if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N); + + if (DEBUG) { + Log.d(TAG, "Writing more @" + i + " of " + N + " to " + + Binder.getCallingPid() + ", sender=" + this); + } + while (i < N && reply.dataSize() < MAX_IPC_SIZE) { reply.writeInt(1); @@ -196,6 +214,9 @@ abstract class BaseParceledListSlice<T> implements Parcelable { if (i < N) { if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N); reply.writeInt(0); + } else { + if (DEBUG) Log.d(TAG, "Transfer complete, clearing mList reference"); + mList = null; } return true; } diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 48b634e52846..11b2ea1f6523 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -180,6 +180,7 @@ public class CrossProfileApps { * {@link #startMainActivity}, this can start any activity of the caller package, not just * the main activity. * The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} + * or {@link android.Manifest.permission#START_CROSS_PROFILE_ACTIVITIES} * permission and both the caller and target user profiles must be in the same profile group. * * @param component The ComponentName of the activity to launch. It must be exported. @@ -189,7 +190,9 @@ public class CrossProfileApps { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_PROFILES, + android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES}) public void startActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) { try { mService.startActivityAsUser(mContext.getIApplicationThread(), diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 495100b0ae52..08b07a73d4af 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2367,43 +2367,73 @@ public class PackageInstaller { private static final int[] NO_SESSIONS = {}; /** @hide */ - @IntDef(prefix = { "STAGED_SESSION_" }, value = { - STAGED_SESSION_NO_ERROR, - STAGED_SESSION_VERIFICATION_FAILED, - STAGED_SESSION_ACTIVATION_FAILED, - STAGED_SESSION_UNKNOWN, - STAGED_SESSION_CONFLICT}) + @IntDef(prefix = { "SESSION_" }, value = { + SESSION_NO_ERROR, + SESSION_VERIFICATION_FAILED, + SESSION_ACTIVATION_FAILED, + SESSION_UNKNOWN_ERROR, + SESSION_CONFLICT}) @Retention(RetentionPolicy.SOURCE) public @interface SessionErrorCode {} /** - * Constant indicating that no error occurred during the preparation or the activation of - * this staged session. + * @deprecated use {@link #SESSION_NO_ERROR}. */ + @Deprecated public static final int STAGED_SESSION_NO_ERROR = 0; /** - * Constant indicating that an error occurred during the verification phase (pre-reboot) of - * this staged session. + * @deprecated use {@link #SESSION_VERIFICATION_FAILED}. */ + @Deprecated public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; /** - * Constant indicating that an error occurred during the activation phase (post-reboot) of - * this staged session. + * @deprecated use {@link #SESSION_ACTIVATION_FAILED}. */ + @Deprecated public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; /** - * Constant indicating that an unknown error occurred while processing this staged session. + * @deprecated use {@link #SESSION_UNKNOWN_ERROR}. */ + @Deprecated public static final int STAGED_SESSION_UNKNOWN = 3; /** - * Constant indicating that the session was in conflict with another staged session and had - * to be sacrificed for resolution. + * @deprecated use {@link #SESSION_CONFLICT}. */ + @Deprecated public static final int STAGED_SESSION_CONFLICT = 4; + /** + * Constant indicating that no error occurred during the preparation or the activation of + * this session. + */ + public static final int SESSION_NO_ERROR = 0; + + /** + * Constant indicating that an error occurred during the verification phase of + * this session. + */ + public static final int SESSION_VERIFICATION_FAILED = 1; + + /** + * Constant indicating that an error occurred during the activation phase of + * this session. + */ + public static final int SESSION_ACTIVATION_FAILED = 2; + + /** + * Constant indicating that an unknown error occurred while processing this session. + */ + public static final int SESSION_UNKNOWN_ERROR = 3; + + /** + * Constant indicating that the session was in conflict with another session and had + * to be sacrificed for resolution. + */ + public static final int SESSION_CONFLICT = 4; + private static String userActionToString(int requireUserAction) { switch(requireUserAction) { case SessionParams.USER_ACTION_REQUIRED: diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a6d846b43396..c8f88f2edc62 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -50,6 +50,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.dex.ArtManager; +import android.content.pm.pkg.FrameworkPackageUserState; import android.content.pm.verify.domain.DomainVerificationManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -88,6 +89,7 @@ import com.android.internal.util.DataClass; import dalvik.system.VMRuntime; +import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.cert.Certificate; @@ -2734,6 +2736,8 @@ public abstract class PackageManager { * API shipped in Android 11. * <li><code>202101</code>: corresponds to the features included in the Identity Credential * API shipped in Android 12. + * <li><code>202201</code>: corresponds to the features included in the Identity Credential + * API shipped in Android 13. * </ul> */ @SdkConstant(SdkConstantType.FEATURE) @@ -7889,8 +7893,7 @@ public abstract class PackageManager { @Deprecated @Nullable public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, int flags) { - throw new UnsupportedOperationException( - "getPackageArchiveInfo() not implemented in subclass"); + return getPackageArchiveInfo(archiveFilePath, PackageInfoFlags.of(flags)); } /** @@ -7899,8 +7902,29 @@ public abstract class PackageManager { @Nullable public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, @NonNull PackageInfoFlags flags) { - throw new UnsupportedOperationException( - "getPackageArchiveInfo() not implemented in subclass"); + long flagsBits = flags.getValue(); + final PackageParser parser = new PackageParser(); + parser.setCallback(new PackageParser.CallbackImpl(this)); + final File apkFile = new File(archiveFilePath); + try { + if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) { + // Caller expressed an explicit opinion about what encryption + // aware/unaware components they want to see, so fall through and + // give them what they want + } else { + // Caller expressed no opinion, so match everything + flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; + } + + PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false); + if ((flagsBits & GET_SIGNATURES) != 0) { + PackageParser.collectCertificates(pkg, false /* skipVerify */); + } + return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null, + FrameworkPackageUserState.DEFAULT); + } catch (PackageParser.PackageParserException e) { + return null; + } } /** diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index f31f78fb81f3..98cc8f6b0670 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * 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. @@ -37,6 +37,8 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTEN import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; +import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; +import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; @@ -55,11 +57,9 @@ import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.pkg.FrameworkPackageUserState; -import android.content.pm.pkg.PackageUserStateUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; +import android.content.pm.pkg.FrameworkPackageUserState; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; @@ -68,6 +68,7 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Build; import android.os.Bundle; +import android.os.Debug; import android.os.FileUtils; import android.os.Parcel; import android.os.Parcelable; @@ -83,6 +84,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; +import android.util.DebugUtils; import android.util.DisplayMetrics; import android.util.IntArray; import android.util.Log; @@ -128,6 +130,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -148,7 +151,7 @@ import java.util.UUID; * </ul> * * @deprecated This class is mostly unused and no new changes should be added to it. Use - * {@link android.content.pm.parsing.ParsingPackageUtils} and related parsing v2 infrastructure in + * ParsingPackageUtils and related parsing v2 infrastructure in * the core/services parsing subpackages. Or for a quick parse of a provided APK, use * {@link PackageManager#getPackageArchiveInfo(String, int)}. * @@ -655,7 +658,7 @@ public class PackageParser { // If available for the target user, or trying to match uninstalled packages and it's // a system app. - return PackageUserStateUtils.isAvailable(state, flags) + return isAvailable(state, flags) || (appInfo != null && appInfo.isSystemApp() && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0 || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0)); @@ -765,7 +768,7 @@ public class PackageParser { final ActivityInfo[] res = new ActivityInfo[N]; for (int i = 0; i < N; i++) { final Activity a = p.activities.get(i); - if (PackageUserStateUtils.isMatch(state, a.info, flags)) { + if (isMatch(state, a.info, flags)) { if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) { continue; } @@ -782,7 +785,7 @@ public class PackageParser { final ActivityInfo[] res = new ActivityInfo[N]; for (int i = 0; i < N; i++) { final Activity a = p.receivers.get(i); - if (PackageUserStateUtils.isMatch(state, a.info, flags)) { + if (isMatch(state, a.info, flags)) { res[num++] = generateActivityInfo(a, flags, state, userId); } } @@ -796,7 +799,7 @@ public class PackageParser { final ServiceInfo[] res = new ServiceInfo[N]; for (int i = 0; i < N; i++) { final Service s = p.services.get(i); - if (PackageUserStateUtils.isMatch(state, s.info, flags)) { + if (isMatch(state, s.info, flags)) { res[num++] = generateServiceInfo(s, flags, state, userId); } } @@ -810,7 +813,7 @@ public class PackageParser { final ProviderInfo[] res = new ProviderInfo[N]; for (int i = 0; i < N; i++) { final Provider pr = p.providers.get(i); - if (PackageUserStateUtils.isMatch(state, pr.info, flags)) { + if (isMatch(state, pr.info, flags)) { res[num++] = generateProviderInfo(pr, flags, state, userId); } } @@ -7428,7 +7431,7 @@ public class PackageParser { mCompileSdkVersionCodename = dest.readString(); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); - mKeySetMapping = ParsingPackageUtils.readKeySetMapping(dest); + mKeySetMapping = readKeySetMapping(dest); cpuAbiOverride = dest.readString(); use32bitAbi = (dest.readInt() == 1); @@ -7554,7 +7557,7 @@ public class PackageParser { dest.writeInt(mCompileSdkVersion); dest.writeString(mCompileSdkVersionCodename); dest.writeArraySet(mUpgradeKeySets); - ParsingPackageUtils.writeKeySetMapping(dest, mKeySetMapping); + writeKeySetMapping(dest, mKeySetMapping); dest.writeString(cpuAbiOverride); dest.writeInt(use32bitAbi ? 1 : 0); dest.writeByteArray(restrictUpdateHash); @@ -7977,7 +7980,7 @@ public class PackageParser { if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } - ai.seInfoUser = SELinuxUtil.getSeinfoUser(state); + ai.seInfoUser = getSeinfoUser(state); final OverlayPaths overlayPaths = state.getAllOverlayPaths(); if (overlayPaths != null) { ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]); @@ -9074,4 +9077,194 @@ public class PackageParser { return mCachedSplitApks[0][0]; } } + + + + public static boolean isMatch(@NonNull FrameworkPackageUserState state, + ComponentInfo componentInfo, long flags) { + return isMatch(state, componentInfo.applicationInfo.isSystemApp(), + componentInfo.applicationInfo.enabled, componentInfo.enabled, + componentInfo.directBootAware, componentInfo.name, flags); + } + + public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, + boolean isPackageEnabled, ComponentInfo component, long flags) { + return isMatch(state, isSystem, isPackageEnabled, component.isEnabled(), + component.directBootAware, component.name, flags); + } + + /** + * Test if the given component is considered installed, enabled and a match for the given + * flags. + * + * <p> + * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and {@link + * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}. + * </p> + */ + public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, + boolean isPackageEnabled, boolean isComponentEnabled, + boolean isComponentDirectBootAware, String componentName, long flags) { + final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0; + if (!isAvailable(state, flags) && !(isSystem && matchUninstalled)) { + return reportIfDebug(false, flags); + } + + if (!isEnabled(state, isPackageEnabled, isComponentEnabled, componentName, flags)) { + return reportIfDebug(false, flags); + } + + if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { + if (!isSystem) { + return reportIfDebug(false, flags); + } + } + + final boolean matchesUnaware = ((flags & PackageManager.MATCH_DIRECT_BOOT_UNAWARE) != 0) + && !isComponentDirectBootAware; + final boolean matchesAware = ((flags & PackageManager.MATCH_DIRECT_BOOT_AWARE) != 0) + && isComponentDirectBootAware; + return reportIfDebug(matchesUnaware || matchesAware, flags); + } + + public static boolean isAvailable(@NonNull FrameworkPackageUserState state, long flags) { + // True if it is installed for this user and it is not hidden. If it is hidden, + // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES + final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0; + final boolean matchUninstalled = (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0; + return matchAnyUser + || (state.isInstalled() + && (!state.isHidden() || matchUninstalled)); + } + + public static boolean reportIfDebug(boolean result, long flags) { + if (DEBUG_PARSER && !result) { + Slog.i(TAG, "No match!; flags: " + + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " " + + Debug.getCaller()); + } + return result; + } + + public static boolean isEnabled(@NonNull FrameworkPackageUserState state, ComponentInfo componentInfo, + long flags) { + return isEnabled(state, componentInfo.applicationInfo.enabled, componentInfo.enabled, + componentInfo.name, flags); + } + + public static boolean isEnabled(@NonNull FrameworkPackageUserState state, boolean isPackageEnabled, + ComponentInfo parsedComponent, long flags) { + return isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), + parsedComponent.name, flags); + } + + /** + * Test if the given component is considered enabled. + */ + public static boolean isEnabled(@NonNull FrameworkPackageUserState state, + boolean isPackageEnabled, boolean isComponentEnabled, String componentName, + long flags) { + if ((flags & MATCH_DISABLED_COMPONENTS) != 0) { + return true; + } + + // First check if the overall package is disabled; if the package is + // enabled then fall through to check specific component + switch (state.getEnabledState()) { + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: + return false; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: + if ((flags & MATCH_DISABLED_UNTIL_USED_COMPONENTS) == 0) { + return false; + } + // fallthrough + case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: + if (!isPackageEnabled) { + return false; + } + // fallthrough + case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: + break; + } + + // Check if component has explicit state before falling through to + // the manifest default + if (state.isComponentEnabled(componentName)) { + return true; + } else if (state.isComponentDisabled(componentName)) { + return false; + } + + return isComponentEnabled; + } + + /** + * Writes the keyset mapping to the provided package. {@code null} mappings are permitted. + */ + public static void writeKeySetMapping(@NonNull Parcel dest, + @NonNull Map<String, ArraySet<PublicKey>> keySetMapping) { + if (keySetMapping == null) { + dest.writeInt(-1); + return; + } + + final int N = keySetMapping.size(); + dest.writeInt(N); + + for (String key : keySetMapping.keySet()) { + dest.writeString(key); + ArraySet<PublicKey> keys = keySetMapping.get(key); + if (keys == null) { + dest.writeInt(-1); + continue; + } + + final int M = keys.size(); + dest.writeInt(M); + for (int j = 0; j < M; j++) { + dest.writeSerializable(keys.valueAt(j)); + } + } + } + + /** + * Reads a keyset mapping from the given parcel at the given data position. May return + * {@code null} if the serialized mapping was {@code null}. + */ + @NonNull + public static ArrayMap<String, ArraySet<PublicKey>> readKeySetMapping(@NonNull Parcel in) { + final int N = in.readInt(); + if (N == -1) { + return null; + } + + ArrayMap<String, ArraySet<PublicKey>> keySetMapping = new ArrayMap<>(); + for (int i = 0; i < N; ++i) { + String key = in.readString(); + final int M = in.readInt(); + if (M == -1) { + keySetMapping.put(key, null); + continue; + } + + ArraySet<PublicKey> keys = new ArraySet<>(M); + for (int j = 0; j < M; ++j) { + PublicKey pk = + in.readSerializable(PublicKey.class.getClassLoader(), PublicKey.class); + keys.add(pk); + } + + keySetMapping.put(key, keys); + } + + return keySetMapping; + } + + public static String getSeinfoUser(FrameworkPackageUserState userState) { + if (userState.isInstantApp()) { + return ":ephemeralapp:complete"; + } + return ":complete"; + } } diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 7696cbe0b631..78984bda24a7 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -174,7 +174,7 @@ public abstract class RegisteredServicesCache<V> { // Register for user-related events IntentFilter userFilter = new IntentFilter(); - sdFilter.addAction(Intent.ACTION_USER_REMOVED); + userFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler); } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 7d4f7ecef29c..ab827aacbdc1 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -41,6 +41,7 @@ import android.os.Parcelable; import android.os.PersistableBundle; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.view.contentcapture.ContentCaptureContext; @@ -50,9 +51,15 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Represents a shortcut that can be published via {@link ShortcutManager}. @@ -463,6 +470,9 @@ public final class ShortcutInfo implements Parcelable { private int mExcludedSurfaces; + @Nullable + private Map<String, Map<String, List<String>>> mCapabilityBindings; + private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); @@ -490,7 +500,7 @@ public final class ShortcutInfo implements Parcelable { mRank = b.mRank; mExtras = b.mExtras; mLocusId = b.mLocusId; - + mCapabilityBindings = b.mCapabilityBindings; mStartingThemeResName = b.mStartingThemeResId != 0 ? b.mContext.getResources().getResourceName(b.mStartingThemeResId) : null; updateTimestamp(); @@ -602,7 +612,7 @@ public final class ShortcutInfo implements Parcelable { mLocusId = source.mLocusId; mExcludedSurfaces = source.mExcludedSurfaces; - // Just always keep it since it's cheep. + // Just always keep it since it's cheap. mIconResId = source.mIconResId; if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { @@ -641,6 +651,7 @@ public final class ShortcutInfo implements Parcelable { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; } + mCapabilityBindings = source.mCapabilityBindings; mStartingThemeResName = source.mStartingThemeResName; } @@ -968,6 +979,9 @@ public final class ShortcutInfo implements Parcelable { if (source.mStartingThemeResName != null && !source.mStartingThemeResName.isEmpty()) { mStartingThemeResName = source.mStartingThemeResName; } + if (source.mCapabilityBindings != null) { + mCapabilityBindings = source.mCapabilityBindings; + } } /** @@ -1039,6 +1053,9 @@ public final class ShortcutInfo implements Parcelable { private int mStartingThemeResId; + @Nullable + private Map<String, Map<String, List<String>>> mCapabilityBindings; + private int mExcludedSurfaces; /** @@ -1401,6 +1418,53 @@ public final class ShortcutInfo implements Parcelable { } /** + * Associates a shortcut with a capability, and a parameter of that capability. Used when + * the shortcut is an instance of a capability. + * + * <P>This method can be called multiple times to add multiple parameters to the same + * capability. + * + * @param capability capability associated with the shortcut. e.g. actions.intent + * .START_EXERCISE. + * @param parameterName name of the parameter associated with given capability. + * e.g. exercise.name. + * @param parameterValues a list of values for that parameters. The first value will be + * the primary name, while the rest will be alternative names. If + * the values are empty, then the parameter will not be saved in + * the shortcut. + */ + @NonNull + public Builder addCapabilityBinding(@NonNull String capability, + @Nullable String parameterName, @Nullable List<String> parameterValues) { + Objects.requireNonNull(capability); + if (capability.contains("/")) { + throw new IllegalArgumentException("Illegal character '/' is found in capability"); + } + if (mCapabilityBindings == null) { + mCapabilityBindings = new ArrayMap<>(1); + } + if (!mCapabilityBindings.containsKey(capability)) { + mCapabilityBindings.put(capability, new ArrayMap<>(0)); + } + if (parameterName == null || parameterValues == null || parameterValues.isEmpty()) { + return this; + } + if (parameterName.contains("/")) { + throw new IllegalArgumentException( + "Illegal character '/' is found in parameter name"); + } + final Map<String, List<String>> params = mCapabilityBindings.get(capability); + if (!params.containsKey(parameterName)) { + params.put(parameterName, parameterValues); + return this; + } + params.put(parameterName, + Stream.of(params.get(parameterName), parameterValues) + .flatMap(Collection::stream).collect(Collectors.toList())); + return this; + } + + /** * Sets which surfaces a shortcut will be excluded from. * * If the shortcut is set to be excluded from {@link #SURFACE_LAUNCHER}, shortcuts will be @@ -2176,6 +2240,44 @@ public final class ShortcutInfo implements Parcelable { return (mExcludedSurfaces & surface) == 0; } + /** + * @hide + */ + public Map<String, Map<String, List<String>>> getCapabilityBindings() { + return mCapabilityBindings; + } + + /** + * Return true if the shortcut is or can be used in specified capability. + */ + public boolean hasCapability(@NonNull String capability) { + Objects.requireNonNull(capability); + return mCapabilityBindings != null && mCapabilityBindings.containsKey(capability); + } + + /** + * Returns the values of specified parameter in associated with given capability. + * + * @param capability capability associated with the shortcut. e.g. actions.intent + * .START_EXERCISE. + * @param parameterName name of the parameter associated with given capability. + * e.g. exercise.name. + */ + @NonNull + public List<String> getCapabilityParameterValues( + @NonNull String capability, @NonNull String parameterName) { + Objects.requireNonNull(capability); + Objects.requireNonNull(parameterName); + if (mCapabilityBindings == null) { + return Collections.emptyList(); + } + final Map<String, List<String>> param = mCapabilityBindings.get(capability); + if (param == null || !param.containsKey(parameterName)) { + return Collections.emptyList(); + } + return param.get(parameterName); + } + private ShortcutInfo(Parcel source) { final ClassLoader cl = getClass().getClassLoader(); @@ -2225,6 +2327,15 @@ public final class ShortcutInfo implements Parcelable { mIconUri = source.readString8(); mStartingThemeResName = source.readString8(); mExcludedSurfaces = source.readInt(); + + final Map<String, Map> rawCapabilityBindings = source.readHashMap( + /*loader*/ null, /*clazzKey*/ String.class, /*clazzValue*/ HashMap.class); + if (rawCapabilityBindings != null && !rawCapabilityBindings.isEmpty()) { + final Map<String, Map<String, List<String>>> capabilityBindings = + new ArrayMap<>(rawCapabilityBindings.size()); + rawCapabilityBindings.forEach(capabilityBindings::put); + mCapabilityBindings = capabilityBindings; + } } @Override @@ -2278,6 +2389,7 @@ public final class ShortcutInfo implements Parcelable { dest.writeString8(mIconUri); dest.writeString8(mStartingThemeResName); dest.writeInt(mExcludedSurfaces); + dest.writeMap(mCapabilityBindings); } public static final @NonNull Creator<ShortcutInfo> CREATOR = @@ -2529,7 +2641,8 @@ public final class ShortcutInfo implements Parcelable { long lastChangedTimestamp, int flags, int iconResId, String iconResName, String bitmapPath, String iconUri, int disabledReason, Person[] persons, LocusId locusId, - @Nullable String startingThemeResName) { + @Nullable String startingThemeResName, + @Nullable Map<String, Map<String, List<String>>> capabilityBindings) { mUserId = userId; mId = id; mPackageName = packageName; @@ -2559,5 +2672,6 @@ public final class ShortcutInfo implements Parcelable { mPersons = persons; mLocusId = locusId; mStartingThemeResName = startingThemeResName; + mCapabilityBindings = capabilityBindings; } } diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index d04c97c1e915..a503d14b6de4 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -25,6 +25,9 @@ "path": "cts/hostsidetests/packagemanager" }, { + "path": "cts/hostsidetests/os/test_mappings/packagemanager" + }, + { "path": "system/apex/tests" } ], @@ -128,6 +131,23 @@ "exclude-annotation": "org.junit.Ignore" } ] + }, + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-annotation": "android.platform.test.annotations.Presubmit" + }, + { + "exclude-annotation": "android.platform.test.annotations.Postsubmit" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] } ], "postsubmit": [ diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 1639ee92b882..d5498a0dc8cd 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -18,13 +18,6 @@ package android.content.pm.parsing; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; -import static android.content.pm.parsing.ParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY; -import static android.content.pm.parsing.ParsingPackageUtils.checkRequiredSystemProperties; -import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; -import static android.content.pm.parsing.ParsingPackageUtils.validateName; -import static android.content.pm.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; -import static android.content.pm.parsing.ParsingUtils.DEFAULT_MIN_SDK_VERSION; -import static android.content.pm.parsing.ParsingUtils.DEFAULT_TARGET_SDK_VERSION; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.annotation.NonNull; @@ -37,6 +30,7 @@ import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.ApkAssets; import android.content.res.XmlResourceParser; +import android.os.Build; import android.os.Trace; import android.text.TextUtils; import android.util.ArrayMap; @@ -66,7 +60,7 @@ import java.util.Set; /** @hide */ public class ApkLiteParseUtils { - private static final String TAG = ParsingUtils.TAG; + private static final String TAG = "ApkLiteParseUtils"; private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; @@ -75,6 +69,26 @@ public class ApkLiteParseUtils { public static final String APK_FILE_EXTENSION = ".apk"; + + // Constants copied from services.jar side since they're not accessible + private static final String ANDROID_RES_NAMESPACE = + "http://schemas.android.com/apk/res/android"; + private static final int DEFAULT_MIN_SDK_VERSION = 1; + private static final int DEFAULT_TARGET_SDK_VERSION = 0; + public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; + private static final int PARSE_IS_SYSTEM_DIR = 1 << 4; + private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5; + private static final String TAG_APPLICATION = "application"; + private static final String TAG_PACKAGE_VERIFIER = "package-verifier"; + private static final String TAG_PROFILEABLE = "profileable"; + private static final String TAG_RECEIVER = "receiver"; + private static final String TAG_OVERLAY = "overlay"; + private static final String TAG_USES_SDK = "uses-sdk"; + private static final String TAG_USES_SPLIT = "uses-split"; + private static final String TAG_MANIFEST = "manifest"; + private static final int SDK_VERSION = Build.VERSION.SDK_INT; + private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES; + /** * Parse only lightweight details about the package at the given location. * Automatically detects if the package is a monolithic style (single APK @@ -312,15 +326,16 @@ public class ApkLiteParseUtils { "Failed to parse " + apkPath, e); } - parser = apkAssets.openXml(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); + parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME); final SigningDetails signingDetails; - if ((flags & ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES) != 0) { - final boolean skipVerify = (flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0; + if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { + final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { final ParseResult<SigningDetails> result = - ParsingPackageUtils.getSigningDetails(input, apkFile.getAbsolutePath(), + FrameworkParsingPackageUtils.getSigningDetails(input, + apkFile.getAbsolutePath(), skipVerify, /* isStaticSharedLibrary */ false, SigningDetails.UNKNOWN, DEFAULT_TARGET_SDK_VERSION); if (result.isError()) { @@ -417,12 +432,12 @@ public class ApkLiteParseUtils { continue; } - if (ParsingPackageUtils.TAG_PACKAGE_VERIFIER.equals(parser.getName())) { + if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) { final VerifierInfo verifier = parseVerifier(parser); if (verifier != null) { verifiers.add(verifier); } - } else if (ParsingPackageUtils.TAG_APPLICATION.equals(parser.getName())) { + } else if (TAG_APPLICATION.equals(parser.getName())) { debuggable = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "debuggable", false); multiArch = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "multiArch", @@ -453,15 +468,15 @@ public class ApkLiteParseUtils { continue; } - if (ParsingPackageUtils.TAG_PROFILEABLE.equals(parser.getName())) { + if (TAG_PROFILEABLE.equals(parser.getName())) { profilableByShell = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "shell", profilableByShell); - } else if (ParsingPackageUtils.TAG_RECEIVER.equals(parser.getName())) { + } else if (TAG_RECEIVER.equals(parser.getName())) { hasDeviceAdminReceiver |= isDeviceAdminReceiver( parser, hasBindDeviceAdminPermission); } } - } else if (ParsingPackageUtils.TAG_OVERLAY.equals(parser.getName())) { + } else if (TAG_OVERLAY.equals(parser.getName())) { requiredSystemPropertyName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "requiredSystemPropertyName"); requiredSystemPropertyValue = parser.getAttributeValue(ANDROID_RES_NAMESPACE, @@ -470,7 +485,7 @@ public class ApkLiteParseUtils { overlayIsStatic = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isStatic", false); overlayPriority = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "priority", 0); - } else if (ParsingPackageUtils.TAG_USES_SPLIT.equals(parser.getName())) { + } else if (TAG_USES_SPLIT.equals(parser.getName())) { if (usesSplitName != null) { Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others."); continue; @@ -481,8 +496,8 @@ public class ApkLiteParseUtils { return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "<uses-split> tag requires 'android:name' attribute"); } - } else if (ParsingPackageUtils.TAG_USES_SDK.equals(parser.getName())) { - // Mirrors ParsingPackageUtils#parseUsesSdk until lite and full parsing is combined + } else if (TAG_USES_SDK.equals(parser.getName())) { + // Mirrors FrameworkParsingPackageUtils#parseUsesSdk until lite and full parsing is combined String minSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "minSdkVersion"); String targetSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE, @@ -515,16 +530,15 @@ public class ApkLiteParseUtils { targetCode = minCode; } - ParseResult<Integer> targetResult = ParsingPackageUtils.computeTargetSdkVersion( - targetVer, targetCode, ParsingPackageUtils.SDK_CODENAMES, input); + ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion( + targetVer, targetCode, SDK_CODENAMES, input); if (targetResult.isError()) { return input.error(targetResult); } targetSdkVersion = targetResult.getResult(); - ParseResult<Integer> minResult = ParsingPackageUtils.computeMinSdkVersion( - minVer, minCode, ParsingPackageUtils.SDK_VERSION, - ParsingPackageUtils.SDK_CODENAMES, input); + ParseResult<Integer> minResult = FrameworkParsingPackageUtils.computeMinSdkVersion( + minVer, minCode, SDK_VERSION, SDK_CODENAMES, input); if (minResult.isError()) { return input.error(minResult); } @@ -533,9 +547,9 @@ public class ApkLiteParseUtils { } // Check to see if overlay should be excluded based on system property condition - if ((flags & PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY) == 0 - && !checkRequiredSystemProperties( - requiredSystemPropertyName, requiredSystemPropertyValue)) { + if ((flags & FrameworkParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY) + == 0 && !FrameworkParsingPackageUtils.checkRequiredSystemProperties( + requiredSystemPropertyName, requiredSystemPropertyValue)) { String message = "Skipping target and overlay pair " + targetPackage + " and " + codePath + ": overlay ignored due to required system property: " + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue; @@ -600,14 +614,15 @@ public class ApkLiteParseUtils { return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "No start tag found"); } - if (!parser.getName().equals(ParsingPackageUtils.TAG_MANIFEST)) { + if (!parser.getName().equals(TAG_MANIFEST)) { return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "No <manifest> tag"); } final String packageName = parser.getAttributeValue(null, "package"); if (!"android".equals(packageName)) { - final ParseResult<?> nameResult = validateName(input, packageName, true, true); + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, + packageName, true, true); if (nameResult.isError()) { return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, "Invalid manifest package: " + nameResult.getErrorMessage()); @@ -619,7 +634,8 @@ public class ApkLiteParseUtils { if (splitName.length() == 0) { splitName = null; } else { - final ParseResult<?> nameResult = validateName(input, splitName, false, false); + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, + splitName, false, false); if (nameResult.isError()) { return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, "Invalid manifest split: " + nameResult.getErrorMessage()); @@ -666,7 +682,7 @@ public class ApkLiteParseUtils { final String type = value.trim(); // Using requireFilename as true because it limits length of the name to the // {@link #MAX_FILE_NAME_SIZE}. - final ParseResult<?> nameResult = validateName(input, type, + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, type, false /* requireSeparator */, true /* requireFilename */); if (nameResult.isError()) { return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, @@ -688,7 +704,7 @@ public class ApkLiteParseUtils { return null; } - final PublicKey publicKey = parsePublicKey(encodedPublicKey); + final PublicKey publicKey = FrameworkParsingPackageUtils.parsePublicKey(encodedPublicKey); if (publicKey == null) { Slog.i(TAG, "Unable to parse verifier public key for " + packageName); return null; diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java new file mode 100644 index 000000000000..8b86a16075e9 --- /dev/null +++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java @@ -0,0 +1,397 @@ +/* + * 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 android.content.pm.parsing; + +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; + +import android.annotation.CheckResult; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.content.pm.SigningDetails; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.os.Build; +import android.os.FileUtils; +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Slog; +import android.util.apk.ApkSignatureVerifier; + +import com.android.internal.util.ArrayUtils; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +/** @hide */ +public class FrameworkParsingPackageUtils { + + private static final String TAG = "FrameworkParsingPackageUtils"; + + /** + * For those names would be used as a part of the file name. Limits size to 223 and reserves 32 + * for the OS. + */ + private static final int MAX_FILE_NAME_SIZE = 223; + + public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; + + /** + * Check if the given name is valid. + * + * @param name The name to check. + * @param requireSeparator {@code true} if the name requires containing a separator at least. + * @param requireFilename {@code true} to apply file name validation to the given name. It also + * limits length of the name to the {@link #MAX_FILE_NAME_SIZE}. + * @return Success if it's valid. + */ + public static String validateName(String name, boolean requireSeparator, + boolean requireFilename) { + final int N = name.length(); + boolean hasSep = false; + boolean front = true; + for (int i = 0; i < N; i++) { + final char c = name.charAt(i); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + front = false; + continue; + } + if (!front) { + if ((c >= '0' && c <= '9') || c == '_') { + continue; + } + } + if (c == '.') { + hasSep = true; + front = true; + continue; + } + return "bad character '" + c + "'"; + } + if (requireFilename) { + if (!FileUtils.isValidExtFilename(name)) { + return "Invalid filename"; + } else if (N > MAX_FILE_NAME_SIZE) { + return "the length of the name is greater than " + MAX_FILE_NAME_SIZE; + } + } + return hasSep || !requireSeparator ? null : "must have at least one '.' separator"; + } + + /** + * @see #validateName(String, boolean, boolean) + */ + public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator, + boolean requireFilename) { + final String errorMessage = validateName(name, requireSeparator, requireFilename); + if (errorMessage != null) { + return input.error(errorMessage); + } + return input.success(null); + } + + /** + * @return {@link PublicKey} of a given encoded public key. + */ + public static PublicKey parsePublicKey(final String encodedPublicKey) { + if (encodedPublicKey == null) { + Slog.w(TAG, "Could not parse null public key"); + return null; + } + + try { + return parsePublicKey(Base64.decode(encodedPublicKey, Base64.DEFAULT)); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); + return null; + } + } + + /** + * @return {@link PublicKey} of the given byte array of a public key. + */ + public static PublicKey parsePublicKey(final byte[] publicKey) { + if (publicKey == null) { + Slog.w(TAG, "Could not parse null public key"); + return null; + } + + final EncodedKeySpec keySpec; + try { + keySpec = new X509EncodedKeySpec(publicKey); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); + return null; + } + + /* First try the key as an RSA key. */ + try { + final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + Slog.wtf(TAG, "Could not parse public key: RSA KeyFactory not included in build"); + } catch (InvalidKeySpecException e) { + // Not a RSA public key. + } + + /* Now try it as a ECDSA key. */ + try { + final KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + Slog.wtf(TAG, "Could not parse public key: EC KeyFactory not included in build"); + } catch (InvalidKeySpecException e) { + // Not a ECDSA public key. + } + + /* Now try it as a DSA key. */ + try { + final KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + Slog.wtf(TAG, "Could not parse public key: DSA KeyFactory not included in build"); + } catch (InvalidKeySpecException e) { + // Not a DSA public key. + } + + /* Not a supported key type */ + return null; + } + + /** + * Returns {@code true} if both the property name and value are empty or if the given system + * property is set to the specified value. Properties can be one or more, and if properties are + * more than one, they must be separated by comma, and count of names and values must be equal, + * and also every given system property must be set to the corresponding value. + * In all other cases, returns {@code false} + */ + public static boolean checkRequiredSystemProperties(@Nullable String rawPropNames, + @Nullable String rawPropValues) { + if (TextUtils.isEmpty(rawPropNames) || TextUtils.isEmpty(rawPropValues)) { + if (!TextUtils.isEmpty(rawPropNames) || !TextUtils.isEmpty(rawPropValues)) { + // malformed condition - incomplete + Slog.w(TAG, "Disabling overlay - incomplete property :'" + rawPropNames + + "=" + rawPropValues + "' - require both requiredSystemPropertyName" + + " AND requiredSystemPropertyValue to be specified."); + return false; + } + // no valid condition set - so no exclusion criteria, overlay will be included. + return true; + } + + final String[] propNames = rawPropNames.split(","); + final String[] propValues = rawPropValues.split(","); + + if (propNames.length != propValues.length) { + Slog.w(TAG, "Disabling overlay - property :'" + rawPropNames + + "=" + rawPropValues + "' - require both requiredSystemPropertyName" + + " AND requiredSystemPropertyValue lists to have the same size."); + return false; + } + for (int i = 0; i < propNames.length; i++) { + // Check property value: make sure it is both set and equal to expected value + final String currValue = SystemProperties.get(propNames[i]); + if (!TextUtils.equals(currValue, propValues[i])) { + return false; + } + } + return true; + } + + @CheckResult + public static ParseResult<SigningDetails> getSigningDetails(ParseInput input, + String baseCodePath, boolean skipVerify, boolean isStaticSharedLibrary, + @NonNull SigningDetails existingSigningDetails, int targetSdk) { + int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( + targetSdk); + if (isStaticSharedLibrary) { + // must use v2 signing scheme + minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2; + } + final ParseResult<SigningDetails> verified; + if (skipVerify) { + // systemDir APKs are already trusted, save time by not verifying; since the + // signature is not verified and some system apps can have their V2+ signatures + // stripped allow pulling the certs from the jar signature. + verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(input, baseCodePath, + SigningDetails.SignatureSchemeVersion.JAR); + } else { + verified = ApkSignatureVerifier.verify(input, baseCodePath, minSignatureScheme); + } + + if (verified.isError()) { + return input.error(verified); + } + + // Verify that entries are signed consistently with the first pkg + // we encountered. Note that for splits, certificates may have + // already been populated during an earlier parse of a base APK. + if (existingSigningDetails == SigningDetails.UNKNOWN) { + return verified; + } else { + if (!Signature.areExactMatch(existingSigningDetails.getSignatures(), + verified.getResult().getSignatures())) { + return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + baseCodePath + " has mismatched certificates"); + } + + return input.success(existingSigningDetails); + } + } + + /** + * Computes the minSdkVersion to use at runtime. If the package is not compatible with this + * platform, populates {@code outError[0]} with an error message. + * <p> + * If {@code minCode} is not specified, e.g. the value is {@code null}, then behavior varies + * based on the {@code platformSdkVersion}: + * <ul> + * <li>If the platform SDK version is greater than or equal to the + * {@code minVers}, returns the {@code mniVers} unmodified. + * <li>Otherwise, returns -1 to indicate that the package is not + * compatible with this platform. + * </ul> + * <p> + * Otherwise, the behavior varies based on whether the current platform + * is a pre-release version, e.g. the {@code platformSdkCodenames} array + * has length > 0: + * <ul> + * <li>If this is a pre-release platform and the value specified by + * {@code targetCode} is contained within the array of allowed pre-release + * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. + * <li>If this is a released platform, this method will return -1 to + * indicate that the package is not compatible with this platform. + * </ul> + * + * @param minVers minSdkVersion number, if specified in the application manifest, + * or 1 otherwise + * @param minCode minSdkVersion code, if specified in the application manifest, or + * {@code null} otherwise + * @param platformSdkVersion platform SDK version number, typically Build.VERSION.SDK_INT + * @param platformSdkCodenames array of allowed prerelease SDK codenames for this platform + * @return the minSdkVersion to use at runtime if successful + */ + public static ParseResult<Integer> computeMinSdkVersion(@IntRange(from = 1) int minVers, + @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion, + @NonNull String[] platformSdkCodenames, @NonNull ParseInput input) { + // If it's a release SDK, make sure we meet the minimum SDK requirement. + if (minCode == null) { + if (minVers <= platformSdkVersion) { + return input.success(minVers); + } + + // We don't meet the minimum SDK requirement. + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires newer sdk version #" + minVers + + " (current version is #" + platformSdkVersion + ")"); + } + + // If it's a pre-release SDK and the codename matches this platform, we + // definitely meet the minimum SDK requirement. + if (matchTargetCode(platformSdkCodenames, minCode)) { + return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + // Otherwise, we're looking at an incompatible pre-release SDK. + if (platformSdkCodenames.length > 0) { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires development platform " + minCode + + " (current platform is any of " + + Arrays.toString(platformSdkCodenames) + ")"); + } else { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires development platform " + minCode + + " but this is a release platform."); + } + } + + /** + * Computes the targetSdkVersion to use at runtime. If the package is not compatible with this + * platform, populates {@code outError[0]} with an error message. + * <p> + * If {@code targetCode} is not specified, e.g. the value is {@code null}, then the {@code + * targetVers} will be returned unmodified. + * <p> + * Otherwise, the behavior varies based on whether the current platform is a pre-release + * version, e.g. the {@code platformSdkCodenames} array has length > 0: + * <ul> + * <li>If this is a pre-release platform and the value specified by + * {@code targetCode} is contained within the array of allowed pre-release + * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. + * <li>If this is a released platform, this method will return -1 to + * indicate that the package is not compatible with this platform. + * </ul> + * + * @param targetVers targetSdkVersion number, if specified in the application + * manifest, or 0 otherwise + * @param targetCode targetSdkVersion code, if specified in the application manifest, + * or {@code null} otherwise + * @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform + * @return the targetSdkVersion to use at runtime if successful + */ + public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers, + @Nullable String targetCode, @NonNull String[] platformSdkCodenames, + @NonNull ParseInput input) { + // If it's a release SDK, return the version number unmodified. + if (targetCode == null) { + return input.success(targetVers); + } + + // If it's a pre-release SDK and the codename matches this platform, it + // definitely targets this SDK. + if (matchTargetCode(platformSdkCodenames, targetCode)) { + return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + // Otherwise, we're looking at an incompatible pre-release SDK. + if (platformSdkCodenames.length > 0) { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires development platform " + targetCode + + " (current platform is any of " + + Arrays.toString(platformSdkCodenames) + ")"); + } else { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires development platform " + targetCode + + " but this is a release platform."); + } + } + + /** + * Matches a given {@code targetCode} against a set of release codeNames. Target codes can + * either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form {@code + * [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}). + */ + private static boolean matchTargetCode(@NonNull String[] codeNames, + @NonNull String targetCode) { + final String targetCodeName; + final int targetCodeIdx = targetCode.indexOf('.'); + if (targetCodeIdx == -1) { + targetCodeName = targetCode; + } else { + targetCodeName = targetCode.substring(0, targetCodeIdx); + } + return ArrayUtils.contains(codeNames, targetCodeName); + } +} diff --git a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java index 1a3fc850243e..c32370441e97 100644 --- a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java +++ b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java @@ -16,14 +16,11 @@ package android.content.pm.parsing.result; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.parsing.ParsingUtils; import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; @@ -35,7 +32,7 @@ import com.android.internal.util.CollectionUtils; /** @hide */ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { - private static final String TAG = ParsingUtils.TAG; + private static final String TAG = "ParseTypeImpl"; public static final boolean DEBUG_FILL_STACK_TRACE = false; @@ -64,7 +61,7 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { private ArrayMap<Long, String> mDeferredErrors = null; private String mPackageName; - private int mTargetSdkVersion = NOT_SET; + private int mTargetSdkVersion = -1; /** * Specifically for {@link PackageManager#getPackageArchiveInfo(String, int)} where @@ -121,7 +118,7 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { // how many APKs they're going through. mDeferredErrors.erase(); } - mTargetSdkVersion = NOT_SET; + mTargetSdkVersion = -1; return this; } @@ -141,7 +138,7 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { if (DEBUG_THROW_ALL_ERRORS) { return error(parseError); } - if (mTargetSdkVersion != NOT_SET) { + if (mTargetSdkVersion != -1) { if (mDeferredErrors != null && mDeferredErrors.containsKey(deferredError)) { // If the map already contains the key, that means it's already been checked and // found to be disabled. Otherwise it would've failed when mTargetSdkVersion was diff --git a/core/java/android/content/pm/pkg/FrameworkPackageUserState.java b/core/java/android/content/pm/pkg/FrameworkPackageUserState.java index 0daf6cf4319a..bac29b49217c 100644 --- a/core/java/android/content/pm/pkg/FrameworkPackageUserState.java +++ b/core/java/android/content/pm/pkg/FrameworkPackageUserState.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -31,8 +31,9 @@ import java.util.Set; * See the services variant for method documentation. * * @hide - * @deprecated Unless you know exactly what you're doing, you probably want the services variant. + * @deprecated Unused by framework. */ +@Deprecated public interface FrameworkPackageUserState { FrameworkPackageUserState DEFAULT = new FrameworkPackageUserStateDefault(); diff --git a/core/java/android/content/pm/pkg/FrameworkPackageUserStateDefault.java b/core/java/android/content/pm/pkg/FrameworkPackageUserStateDefault.java index 27255da1e64a..359062064ebf 100644 --- a/core/java/android/content/pm/pkg/FrameworkPackageUserStateDefault.java +++ b/core/java/android/content/pm/pkg/FrameworkPackageUserStateDefault.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -25,7 +25,11 @@ import java.util.Collections; import java.util.Map; import java.util.Set; -/** @hide */ +/** + * @hide + * @deprecated Unused by framework. + */ +@Deprecated class FrameworkPackageUserStateDefault implements FrameworkPackageUserState { @Override diff --git a/core/java/android/content/pm/split/OWNERS b/core/java/android/content/pm/split/OWNERS deleted file mode 100644 index 3d126d297e60..000000000000 --- a/core/java/android/content/pm/split/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -# Bug component: 36137 - -toddke@android.com -toddke@google.com -patb@google.com diff --git a/core/java/android/hardware/CameraStreamStats.java b/core/java/android/hardware/CameraStreamStats.java index 41d1e2523a9b..ed22de8dd594 100644 --- a/core/java/android/hardware/CameraStreamStats.java +++ b/core/java/android/hardware/CameraStreamStats.java @@ -15,6 +15,7 @@ */ package android.hardware; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -45,6 +46,7 @@ public class CameraStreamStats implements Parcelable { private int mHistogramType; private float[] mHistogramBins; private long[] mHistogramCounts; + private int mDynamicRangeProfile; private static final String TAG = "CameraStreamStats"; @@ -60,11 +62,12 @@ public class CameraStreamStats implements Parcelable { mMaxHalBuffers = 0; mMaxAppBuffers = 0; mHistogramType = HISTOGRAM_TYPE_UNKNOWN; + mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; } public CameraStreamStats(int width, int height, int format, int dataSpace, long usage, long requestCount, long errorCount, - int startLatencyMs, int maxHalBuffers, int maxAppBuffers) { + int startLatencyMs, int maxHalBuffers, int maxAppBuffers, int dynamicRangeProfile) { mWidth = width; mHeight = height; mFormat = format; @@ -76,6 +79,7 @@ public class CameraStreamStats implements Parcelable { mMaxHalBuffers = maxHalBuffers; mMaxAppBuffers = maxAppBuffers; mHistogramType = HISTOGRAM_TYPE_UNKNOWN; + mDynamicRangeProfile = dynamicRangeProfile; } public static final @android.annotation.NonNull Parcelable.Creator<CameraStreamStats> CREATOR = @@ -121,6 +125,7 @@ public class CameraStreamStats implements Parcelable { dest.writeInt(mHistogramType); dest.writeFloatArray(mHistogramBins); dest.writeLongArray(mHistogramCounts); + dest.writeInt(mDynamicRangeProfile); } public void readFromParcel(Parcel in) { @@ -137,6 +142,7 @@ public class CameraStreamStats implements Parcelable { mHistogramType = in.readInt(); mHistogramBins = in.createFloatArray(); mHistogramCounts = in.createLongArray(); + mDynamicRangeProfile = in.readInt(); } public int getWidth() { @@ -190,4 +196,8 @@ public class CameraStreamStats implements Parcelable { public long[] getHistogramCounts() { return mHistogramCounts; } + + public int getDynamicRangeProfile() { + return mDynamicRangeProfile; + } } diff --git a/core/java/android/hardware/ISerialManager.aidl b/core/java/android/hardware/ISerialManager.aidl index 74d30f7afefe..65a0fa4f893e 100644 --- a/core/java/android/hardware/ISerialManager.aidl +++ b/core/java/android/hardware/ISerialManager.aidl @@ -22,8 +22,10 @@ import android.os.ParcelFileDescriptor; interface ISerialManager { /* Returns a list of all available serial ports */ + @EnforcePermission("SERIAL_PORT") String[] getSerialPorts(); /* Returns a file descriptor for the serial port. */ + @EnforcePermission("SERIAL_PORT") ParcelFileDescriptor openSerialPort(String name); } diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 08f5a8a582db..37cfb4935f0d 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -22,6 +22,8 @@ import android.compat.annotation.UnsupportedAppUsage; import android.hardware.input.InputSensorInfo; import android.os.Build; +import java.util.UUID; + /** * Class representing a sensor. Use {@link SensorManager#getSensorList} to get * the list of available sensors. For more information about Android sensors, @@ -710,6 +712,20 @@ public final class Sensor { public static final String STRING_TYPE_HINGE_ANGLE = "android.sensor.hinge_angle"; /** + * A constant describing a head tracker sensor. + * + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final int TYPE_HEAD_TRACKER = 37; + + /** + * A constant string describing a head tracker sensor. + * + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final String STRING_TYPE_HEAD_TRACKER = "android.sensor.head_tracker"; + + /** * A constant describing all sensor types. */ @@ -829,6 +845,7 @@ public final class Sensor { 1, // SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT 6, // SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED 1, // SENSOR_TYPE_HINGE_ANGLE + 6, // SENSOR_TYPE_HEAD_TRACKER (discontinuity count is excluded) }; /** @@ -925,6 +942,7 @@ public final class Sensor { @UnsupportedAppUsage private int mFlags; private int mId; + private UUID mUuid; Sensor() { } @@ -951,6 +969,8 @@ public final class Sensor { this.mMaxDelay = sensorInfo.getMaxDelay(); this.mFlags = sensorInfo.getFlags(); this.mId = sensorInfo.getId(); + // The UUID is never specified when creating a sensor from Input manager + this.mUuid = new UUID((long) this.mId, 0); } /** @@ -1040,11 +1060,9 @@ public final class Sensor { } /** - * Do not use. - * - * This method throws an UnsupportedOperationException. - * - * Use getId() if you want a unique ID. + * Reserved for system and audio servers. + * When called from an unauthorized context, the UUID will contain the + * sensor ID in the MSB and 0 in the LSB. * * @see getId * @@ -1052,7 +1070,7 @@ public final class Sensor { */ @SystemApi public java.util.UUID getUuid() { - throw new UnsupportedOperationException(); + return mUuid; } /** @@ -1280,23 +1298,33 @@ public final class Sensor { case TYPE_HINGE_ANGLE: mStringType = STRING_TYPE_HINGE_ANGLE; return true; + case TYPE_HEAD_TRACKER: + mStringType = STRING_TYPE_HEAD_TRACKER; + return true; default: return false; } } /** - * Sets the ID associated with the sensor. + * Sets the UUID associated with the sensor. * - * The method name is misleading; while this ID is based on the UUID, - * we do not pass in the actual UUID. + * NOTE: to be used only by native bindings in SensorManager. + * + * @see #getUuid + */ + private void setUuid(long msb, long lsb) { + mUuid = new UUID(msb, lsb); + } + + /** + * Sets the ID associated with the sensor. * * NOTE: to be used only by native bindings in SensorManager. * * @see #getId */ - private void setUuid(long msb, long lsb) { - // TODO(b/29547335): Rename this method to setId. - mId = (int) msb; + private void setId(int id) { + mId = id; } } diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java index 232f23429720..c77c8cc635e6 100644 --- a/core/java/android/hardware/SensorEvent.java +++ b/core/java/android/hardware/SensorEvent.java @@ -641,6 +641,41 @@ public class SensorEvent { * <li> values[0]: Measured hinge angle between 0 and 360 degrees inclusive</li> * </ul> * + * <h4>{@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER}:</h4> + * + * A sensor of this type measures the orientation of a user's head relative to an arbitrary + * reference frame, as well as the rate of rotation. + * + * Events produced by this sensor follow a special head-centric coordinate frame, where: + * <ul> + * <li> The X axis crosses through the user's ears, with the positive X direction extending + * out of the user's right ear</li> + * <li> The Y axis crosses from the back of the user's head through their nose, with the + * positive direction extending out of the nose, and the X/Y plane being nominally + * parallel to the ground when the user is upright and looking straight ahead</li> + * <li> The Z axis crosses from the neck through the top of the user's head, with the + * positive direction extending out from the top of the head</li> + * </ul> + * + * Data is provided in Euler vector representation, which is a vector whose direction indicates + * the axis of rotation and magnitude indicates the angle to rotate around that axis, in + * radians. + * + * The first three elements provide the transform from the (arbitrary, possibly slowly drifting) + * reference frame to the head frame. The magnitude of this vector is in range [0, π] + * radians, while the value of individual axes is in range [-π, π]. The next three + * elements provide the estimated rotational velocity of the user's head relative to itself, in + * radians per second. + * + * <ul> + * <li> values[0] : X component of Euler vector representing rotation</li> + * <li> values[1] : Y component of Euler vector representing rotation</li> + * <li> values[2] : Z component of Euler vector representing rotation</li> + * <li> values[3] : X component of Euler vector representing angular velocity</li> + * <li> values[4] : Y component of Euler vector representing angular velocity</li> + * <li> values[5] : Z component of Euler vector representing angular velocity</li> + * </ul> + * * @see GeomagneticField */ public final float[] values; diff --git a/core/java/android/hardware/SensorEventCallback.java b/core/java/android/hardware/SensorEventCallback.java index bac212ab5b20..7b0092da1196 100644 --- a/core/java/android/hardware/SensorEventCallback.java +++ b/core/java/android/hardware/SensorEventCallback.java @@ -16,6 +16,8 @@ package android.hardware; +import android.annotation.NonNull; + /** * Used for receiving sensor additional information frames. */ @@ -52,4 +54,21 @@ public abstract class SensorEventCallback implements SensorEventListener2 { * reported from sensor hardware. */ public void onSensorAdditionalInfo(SensorAdditionalInfo info) {} + + /** + * Called when the next {@link android.hardware.SensorEvent SensorEvent} to be delivered via the + * {@link #onSensorChanged(SensorEvent) onSensorChanged} method represents the first event after + * a discontinuity. + * + * The exact meaning of discontinuity depends on the sensor type. For {@link + * android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER}, this means that the + * reference frame has suddenly and significantly changed. + * + * Note that this concept is either not relevant to or not supported by most sensor types, + * {@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER} being the notable + * exception. + * + * @param sensor The {@link android.hardware.Sensor Sensor} which experienced the discontinuity. + */ + public void onSensorDiscontinuity(@NonNull Sensor sensor) {} } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 3a8513b9323f..32a5ee77508e 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -676,6 +676,7 @@ public class SystemSensorManager extends SensorManager { private long mNativeSensorEventQueue; private final SparseBooleanArray mActiveSensors = new SparseBooleanArray(); protected final SparseIntArray mSensorAccuracies = new SparseIntArray(); + protected final SparseIntArray mSensorDiscontinuityCounts = new SparseIntArray(); private final CloseGuard mCloseGuard = CloseGuard.get(); protected final SystemSensorManager mManager; @@ -875,10 +876,21 @@ public class SystemSensorManager extends SensorManager { // call onAccuracyChanged() only if the value changes final int accuracy = mSensorAccuracies.get(handle); - if ((t.accuracy >= 0) && (accuracy != t.accuracy)) { + if (t.accuracy >= 0 && accuracy != t.accuracy) { mSensorAccuracies.put(handle, t.accuracy); mListener.onAccuracyChanged(t.sensor, t.accuracy); } + + // call onSensorDiscontinuity() if the discontinuity counter changed + if (t.sensor.getType() == Sensor.TYPE_HEAD_TRACKER + && mListener instanceof SensorEventCallback) { + final int lastCount = mSensorDiscontinuityCounts.get(handle); + final int curCount = Float.floatToIntBits(values[6]); + if (lastCount >= 0 && lastCount != curCount) { + ((SensorEventCallback) mListener).onSensorDiscontinuity(t.sensor); + } + } + mListener.onSensorChanged(t); } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 6b5bec99e674..dc65beffa6e5 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -38,6 +38,7 @@ import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; import android.security.identity.IdentityCredential; +import android.security.identity.PresentationSession; import android.security.keystore.KeyProperties; import android.text.TextUtils; import android.util.Log; @@ -666,8 +667,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * A wrapper class for the cryptographic operations supported by BiometricPrompt. * - * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, and - * {@link IdentityCredential}. + * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, + * {@link IdentityCredential}, and {@link PresentationSession}. * * <p>Cryptographic operations in Android can be split into two categories: auth-per-use and * time-based. This is specified during key creation via the timeout parameter of the @@ -697,10 +698,21 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan super(mac); } + /** + * Create from a {@link IdentityCredential} object. + * + * @param credential a {@link IdentityCredential} object. + * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. + */ + @Deprecated public CryptoObject(@NonNull IdentityCredential credential) { super(credential); } + public CryptoObject(@NonNull PresentationSession session) { + super(session); + } + /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -728,10 +740,20 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * Get {@link IdentityCredential} object. * @return {@link IdentityCredential} object or null if this doesn't contain one. + * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. */ + @Deprecated public @Nullable IdentityCredential getIdentityCredential() { return super.getIdentityCredential(); } + + /** + * Get {@link PresentationSession} object. + * @return {@link PresentationSession} object or null if this doesn't contain one. + */ + public @Nullable PresentationSession getPresentationSession() { + return super.getPresentationSession(); + } } /** diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java index 7648cf241298..d41570682fe1 100644 --- a/core/java/android/hardware/biometrics/CryptoObject.java +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -18,6 +18,7 @@ package android.hardware.biometrics; import android.annotation.NonNull; import android.security.identity.IdentityCredential; +import android.security.identity.PresentationSession; import android.security.keystore2.AndroidKeyStoreProvider; import java.security.Signature; @@ -27,8 +28,8 @@ import javax.crypto.Mac; /** * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager. - * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac} and - * {@link IdentityCredential} objects. + * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, + * {@link IdentityCredential}, and {@link PresentationSession} objects. * @hide */ public class CryptoObject { @@ -46,10 +47,21 @@ public class CryptoObject { mCrypto = mac; } + /** + * Create from a {@link IdentityCredential} object. + * + * @param credential a {@link IdentityCredential} object. + * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. + */ + @Deprecated public CryptoObject(@NonNull IdentityCredential credential) { mCrypto = credential; } + public CryptoObject(@NonNull PresentationSession session) { + mCrypto = session; + } + /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -77,12 +89,22 @@ public class CryptoObject { /** * Get {@link IdentityCredential} object. * @return {@link IdentityCredential} object or null if this doesn't contain one. + * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. */ + @Deprecated public IdentityCredential getIdentityCredential() { return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null; } /** + * Get {@link PresentationSession} object. + * @return {@link PresentationSession} object or null if this doesn't contain one. + */ + public PresentationSession getPresentationSession() { + return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null; + } + + /** * @hide * @return the opId associated with this object or 0 if none */ @@ -91,6 +113,8 @@ public class CryptoObject { return 0; } else if (mCrypto instanceof IdentityCredential) { return ((IdentityCredential) mCrypto).getCredstoreOperationHandle(); + } else if (mCrypto instanceof PresentationSession) { + return ((PresentationSession) mCrypto).getCredstoreOperationHandle(); } return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto); } diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index bd4746369811..691690c09e0e 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -321,6 +321,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * can submit reprocess capture requests. Submitting a reprocess request to a regular capture * session will result in an {@link IllegalArgumentException}.</p> * + * <p>Submitting a request that targets Surfaces with an unsupported dynamic range combination + * will result in an {@link IllegalArgumentException}.</p> + * * @param request the settings for this capture * @param listener The callback object to notify once this request has been * processed. If null, no metadata will be produced for this capture, @@ -347,13 +350,15 @@ public abstract class CameraCaptureSession implements AutoCloseable { * a different session; or the capture targets a Surface in * the middle of being {@link #prepare prepared}; or the * handler is null, the listener is not null, and the calling - * thread has no looper. + * thread has no looper; or the request targets Surfaces with + * an unsupported dynamic range combination * * @see #captureBurst * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures * @see CameraDevice#createReprocessableCaptureSession + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public abstract int capture(@NonNull CaptureRequest request, @Nullable CaptureCallback listener, @Nullable Handler handler) @@ -389,13 +394,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * request was created with a {@link TotalCaptureResult} from * a different session; or the capture targets a Surface in * the middle of being {@link #prepare prepared}; or the - * executor is null, or the listener is not null. + * executor is null, or the listener is not null; + * or the request targets Surfaces with an unsupported dynamic + * range combination; * * @see #captureBurst * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures * @see CameraDevice#createReprocessableCaptureSession + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public int captureSingleRequest(@NonNull CaptureRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) @@ -427,6 +435,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * can submit reprocess capture requests. Submitting a reprocess request to a regular * capture session will result in an {@link IllegalArgumentException}.</p> * + * <p>Submitting a request that targets Surfaces with an unsupported dynamic range combination + * will result in an {@link IllegalArgumentException}.</p> + * * @param requests the list of settings for this burst capture * @param listener The callback object to notify each time one of the * requests in the burst has been processed. If null, no metadata will be @@ -454,12 +465,15 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link TotalCaptureResult} from a different session; or one * of the captures targets a Surface in the middle of being * {@link #prepare prepared}; or if the handler is null, the - * listener is not null, and the calling thread has no looper. + * listener is not null, and the calling thread has no looper; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public abstract int captureBurst(@NonNull List<CaptureRequest> requests, @Nullable CaptureCallback listener, @Nullable Handler handler) @@ -499,12 +513,15 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link TotalCaptureResult} from a different session; or one * of the captures targets a Surface in the middle of being * {@link #prepare prepared}; or if the executor is null; or if - * the listener is null. + * the listener is null; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public int captureBurstRequests(@NonNull List<CaptureRequest> requests, @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) @@ -545,6 +562,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * single reprocess input image. The request must be capturing images from the camera. If a * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> * + * <p>Submitting a request that targets Surfaces with an unsupported dynamic range combination + * will result in an {@link IllegalArgumentException}.</p> + * * @param request the request to repeat indefinitely * @param listener The callback object to notify every time the * request finishes processing. If null, no metadata will be @@ -567,13 +587,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * is a reprocess capture request; or the capture targets a * Surface in the middle of being {@link #prepare prepared}; or * the handler is null, the listener is not null, and the - * calling thread has no looper; or no requests were passed in. + * calling thread has no looper; or no requests were passed in; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #captureBurst * @see #setRepeatingBurst * @see #stopRepeating * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public abstract int setRepeatingRequest(@NonNull CaptureRequest request, @Nullable CaptureCallback listener, @Nullable Handler handler) @@ -604,13 +627,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * that are not currently configured as outputs; or the request * is a reprocess capture request; or the capture targets a * Surface in the middle of being {@link #prepare prepared}; or - * the executor is null; or the listener is null. + * the executor is null; or the listener is null; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #captureBurst * @see #setRepeatingBurst * @see #stopRepeating * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public int setSingleRepeatingRequest(@NonNull CaptureRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) @@ -655,6 +681,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * single reprocess input image. The request must be capturing images from the camera. If a * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> * + * <p>Submitting a request that targets Surfaces with an unsupported dynamic range combination + * will result in an {@link IllegalArgumentException}.</p> + * * @param requests the list of requests to cycle through indefinitely * @param listener The callback object to notify each time one of the * requests in the repeating bursts has finished processing. If null, no @@ -678,13 +707,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * targets a Surface in the middle of being * {@link #prepare prepared}; or the handler is null, the * listener is not null, and the calling thread has no looper; - * or no requests were passed in. + * or no requests were passed in; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #captureBurst * @see #setRepeatingRequest * @see #stopRepeating * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public abstract int setRepeatingBurst(@NonNull List<CaptureRequest> requests, @Nullable CaptureCallback listener, @Nullable Handler handler) @@ -717,13 +749,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * is a reprocess capture request; or one of the captures * targets a Surface in the middle of being * {@link #prepare prepared}; or the executor is null; or the - * listener is null. + * listener is null; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #captureBurst * @see #setRepeatingRequest * @see #stopRepeating * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public int setRepeatingBurstRequests(@NonNull List<CaptureRequest> requests, @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 48a9121ef7f2..d2dc314585d6 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -461,7 +461,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri public @Nullable RecommendedStreamConfigurationMap getRecommendedStreamConfigurationMap( @RecommendedStreamConfigurationMap.RecommendedUsecase int usecase) { if (((usecase >= RecommendedStreamConfigurationMap.USECASE_PREVIEW) && - (usecase <= RecommendedStreamConfigurationMap.USECASE_LOW_LATENCY_SNAPSHOT)) || + (usecase <= RecommendedStreamConfigurationMap.USECASE_10BIT_OUTPUT)) || ((usecase >= RecommendedStreamConfigurationMap.USECASE_VENDOR_START) && (usecase < RecommendedStreamConfigurationMap.MAX_USECASE_COUNT))) { if (mRecommendedConfigurations == null) { @@ -2213,6 +2213,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR ULTRA_HIGH_RESOLUTION_SENSOR}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING REMOSAIC_REPROCESSING}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT DYNAMIC_RANGE_TEN_BIT}</li> * </ul> * * <p>This key is available on all devices.</p> @@ -2236,6 +2237,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR * @see #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING + * @see #REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT */ @PublicKey @NonNull @@ -2379,6 +2381,86 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.request.characteristicKeysNeedingPermission", int[].class); /** + * <p>Devices supporting the 10-bit output capability + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * must list their supported dynamic range profiles along with capture request + * constraints for specific profile combinations.</p> + * <p>Camera clients can retrieve the list of supported 10-bit dynamic range profiles by calling + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getSupportedProfiles }. + * Any of them can be configured by setting OutputConfiguration dynamic range profile in + * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }. + * Clients can also check if there are any constraints that limit the combination + * of supported profiles that can be referenced within a single capture request by calling + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.DynamicRangeProfiles> REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES = + new Key<android.hardware.camera2.params.DynamicRangeProfiles>("android.request.availableDynamicRangeProfiles", android.hardware.camera2.params.DynamicRangeProfiles.class); + + /** + * <p>A map of all available 10-bit dynamic range profiles along with their + * capture request constraints.</p> + * <p>Devices supporting the 10-bit output capability + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * must list their supported dynamic range profiles. In case the camera is not able to + * support every possible profile combination within a single capture request, then the + * constraints must be listed here as well.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD STANDARD}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10 HLG10}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10 HDR10}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS HDR10_PLUS}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF DOLBY_VISION_10B_HDR_REF}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO DOLBY_VISION_10B_HDR_REF_PO}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM DOLBY_VISION_10B_HDR_OEM}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO DOLBY_VISION_10B_HDR_OEM_PO}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF DOLBY_VISION_8B_HDR_REF}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO DOLBY_VISION_8B_HDR_REF_PO}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM DOLBY_VISION_8B_HDR_OEM}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO DOLBY_VISION_8B_HDR_OEM_PO}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX MAX}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10 + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10 + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX + * @hide + */ + public static final Key<int[]> REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP = + new Key<int[]>("android.request.availableDynamicRangeProfilesMap", int[].class); + + /** + * <p>Recommended 10-bit dynamic range profile.</p> + * <p>Devices supporting the 10-bit output capability + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * must list a 10-bit supported dynamic range profile that is expected to perform + * optimally in terms of image quality, power and performance. + * The value advertised can be used as a hint by camera clients when configuring the dynamic + * range profile when calling + * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + public static final Key<Integer> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE = + new Key<Integer>("android.request.recommendedTenBitDynamicRangeProfile", int.class); + + /** * <p>The list of image formats that are supported by this * camera device for output streams.</p> * <p>All camera devices will support JPEG and YUV_420_888 formats.</p> @@ -3340,6 +3422,32 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryMaximumResolutionStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); /** + * <p>An array of mandatory stream combinations which are applicable when device support the + * 10-bit output capability + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * This is an app-readable conversion of the maximum resolution mandatory stream combination + * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p> + * <p>The array of + * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is + * generated according to the documented + * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} for each + * device which has the + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * capability. + * Clients can use the array as a quick reference to find an appropriate camera stream + * combination. + * The mandatory stream combination array will be {@code null} in case the device is not an + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * device.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS = + new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryTenBitOutputStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); + + /** * <p>Whether the camera device supports multi-resolution input or output streams</p> * <p>A logical multi-camera or an ultra high resolution camera may support multi-resolution * input or output streams. With multi-resolution output streams, the camera device is able diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 3c1ec3e629a9..47eb79d07469 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -402,7 +402,9 @@ public abstract class CameraDevice implements AutoCloseable { * registered surfaces do not meet the device-specific * extension requirements such as dimensions and/or * (output format)/(surface type), or if the extension is not - * supported. + * supported, or if any of the output configurations select + * a dynamic range different from + * {@link android.hardware.camera2.params.DynamicRangeProfiles#STANDARD} * @see CameraExtensionCharacteristics#getSupportedExtensions * @see CameraExtensionCharacteristics#getExtensionSupportedSizes */ @@ -822,7 +824,36 @@ public abstract class CameraDevice implements AutoCloseable { * be chosen from. {@code DEFAULT} refers to the default sensor pixel mode {@link * StreamConfigurationMap} and {@code MAX_RES} refers to the maximum resolution {@link * StreamConfigurationMap}. The same capture request must not mix targets from - * {@link StreamConfigurationMap}s corresponding to different sensor pixel modes. + * {@link StreamConfigurationMap}s corresponding to different sensor pixel modes. </p> + * + * <p> 10-bit output capable + * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT} + * devices support at least the following stream combinations: </p> + * <table> + * <tr><th colspan="7">10-bit output additional guaranteed configurations</th></tr> + * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> }</td> <td colspan="4" id="rb"></td> <td>Simple preview, GPU video processing, or no-preview video recording.</td> </tr> + * <tr> <td>{@code YUV}</td><td id="rb">{@code MAXIMUM}</td> }</td> <td colspan="4" id="rb"></td> <td>In-application video/image processing.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM }</td> <td colspan="2" id="rb"></td> <td>Standard still imaging.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM }</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution in-app processing with preview.</td> </tr> + * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code MAXIMUM }</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution two-input in-app processing.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code RECORD }</td> <td colspan="2" id="rb"></td> <td>High-resolution video recording with preview.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code RECORD }</td> <td>{@code YUV}</td><td id="rb">{@code RECORD }</td> <td>High-resolution recording with in-app snapshot.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV }</td><td id="rb">{@code RECORD }</td> <td>{@code JPEG}</td><td id="rb">{@code RECORD }</td> <td>High-resolution recording with video snapshot.</td> </tr> + * </table><br> + * <p>Here PRIV can be either 8 or 10-bit {@link android.graphics.ImageFormat#PRIVATE} pixel + * format. YUV can be either {@link android.graphics.ImageFormat#YUV_420_888} or + * {@link android.graphics.ImageFormat#YCBCR_P010}. + * For the maximum size column, PREVIEW refers to the best size match to the device's screen + * resolution, or to 1080p (1920x1080), whichever is smaller. RECORD refers to the camera + * device's maximum supported recording resolution, as determined by + * {@link android.media.CamcorderProfile}. MAXIMUM refers to the camera device's maximum output + * resolution for that format or target from {@link StreamConfigurationMap#getOutputSizes(int)}. + * Do note that invalid combinations such as having a camera surface configured to use pixel + * format {@link android.graphics.ImageFormat#YUV_420_888} with a 10-bit profile + * will cause a capture session initialization failure. + * </p> * * <p>Since the capabilities of camera devices vary greatly, a given camera device may support * target combinations with sizes outside of these guarantees, but this can only be tested for @@ -907,6 +938,13 @@ public abstract class CameraDevice implements AutoCloseable { * guaranteed output targets that can be submitted in a regular or reprocess * {@link CaptureRequest} simultaneously.</p> * + * <p>Reprocessing with 10-bit output targets on 10-bit capable + * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT} devices is + * not supported. Trying to initialize a repreocessable capture session with one ore more + * output configurations set {@link OutputConfiguration#setDynamicRangeProfile(int)} to use + * a 10-bit dynamic range profile {@link android.hardware.camera2.params.DynamicRangeProfiles} + * will trigger {@link IllegalArgumentException}.</p> + * * <style scoped> * #rb { border-right-width: thick; } * </style> @@ -1083,13 +1121,17 @@ public abstract class CameraDevice implements AutoCloseable { * * @throws IllegalArgumentException In case the session configuration is invalid; or the output * configurations are empty; or the session configuration - * executor is invalid. + * executor is invalid; + * or the output dynamic range combination is + * invalid/unsupported. * @throws CameraAccessException In case the camera device is no longer connected or has * encountered a fatal error. * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler) * @see #createCaptureSessionByOutputConfigurations * @see #createReprocessableCaptureSession * @see #createConstrainedHighSpeedCaptureSession + * @see OutputConfiguration#setDynamicRangeProfile(int) + * @see android.hardware.camera2.params.DynamicRangeProfiles */ public void createCaptureSession( SessionConfiguration config) throws CameraAccessException { diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 639abe9d1abf..803684da6ddb 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1190,6 +1190,135 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING = 17; + /** + * <p>The device supports one or more 10-bit camera outputs according to the dynamic range + * profiles specified in + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getSupportedProfiles }. + * They can be configured as part of the capture session initialization via + * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }. + * Cameras that enable this capability must also support the following: + * * Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 } + * * All mandatory stream combinations for this specific capability as per + * documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession } + * * In case the device is not able to capture some combination of supported + * standard 8-bit and/or 10-bit dynamic range profiles within the same capture request, + * then those constraints must be listed in + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints } + * * Recommended dynamic range profile listed in + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18; + + // + // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + // + + /** + * <p>8-bit SDR profile which is the default for all non 10-bit output capable devices.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD = 0x1; + + /** + * <p>10-bit pixel samples encoded using the Hybrid log-gamma transfer function.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10 = 0x2; + + /** + * <p>10-bit pixel samples encoded using the SMPTE ST 2084 transfer function. + * This profile utilizes internal static metadata to increase the quality + * of the capture.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10 = 0x4; + + /** + * <p>10-bit pixel samples encoded using the SMPTE ST 2084 transfer function. + * In contrast to HDR10, this profile uses internal per-frame metadata + * to further enhance the quality of the capture.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS = 0x8; + + /** + * <p>This is a camera mode for Dolby Vision capture optimized for a more scene + * accurate capture. This would typically differ from what a specific device + * might want to tune for a consumer optimized Dolby Vision general capture.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF = 0x10; + + /** + * <p>This is the power optimized mode for 10-bit Dolby Vision HDR Reference Mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO = 0x20; + + /** + * <p>This is the camera mode for the default Dolby Vision capture mode for the + * specific device. This would be tuned by each specific device for consumer + * pleasing results that resonate with their particular audience. We expect + * that each specific device would have a different look for their default + * Dolby Vision capture.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM = 0x40; + + /** + * <p>This is the power optimized mode for 10-bit Dolby Vision HDR device specific + * capture Mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO = 0x80; + + /** + * <p>This is the 8-bit version of the Dolby Vision reference capture mode optimized + * for scene accuracy.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF = 0x100; + + /** + * <p>This is the power optimized mode for 8-bit Dolby Vision HDR Reference Mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO = 0x200; + + /** + * <p>This is the 8-bit version of device specific tuned and optimized Dolby Vision + * capture mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM = 0x400; + + /** + * <p>This is the power optimized mode for 8-bit Dolby Vision HDR device specific + * capture Mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO = 0x800; + + /** + * + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX = 0x1000; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index b8443fb6d14b..9d2c901ed049 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -35,7 +35,6 @@ import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.extension.CameraOutputConfig; import android.hardware.camera2.extension.CameraSessionConfig; -import android.hardware.camera2.extension.CaptureStageImpl; import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.ICaptureCallback; import android.hardware.camera2.extension.IImageProcessorImpl; @@ -49,6 +48,7 @@ import android.hardware.camera2.extension.ParcelCaptureResult; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.extension.ParcelTotalCaptureResult; import android.hardware.camera2.extension.Request; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; @@ -130,6 +130,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes config.getOutputConfigurations().size() + " expected <= 2"); } + for (OutputConfiguration c : config.getOutputConfigurations()) { + if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) { + throw new IllegalArgumentException("Unsupported dynamic range profile: " + + c.getDynamicRangeProfile()); + } + } + int suitableSurfaceCount = 0; List<Size> supportedPreviewSizes = extensionChars.getExtensionSupportedSizes( config.getExtension(), SurfaceTexture.class); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 71047af69b87..c8ecfd0bdea9 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -39,6 +39,7 @@ import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.IRequestUpdateProcessorImpl; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; @@ -145,6 +146,13 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { config.getOutputConfigurations().size() + " expected <= 2"); } + for (OutputConfiguration c : config.getOutputConfigurations()) { + if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) { + throw new IllegalArgumentException("Unsupported dynamic range profile: " + + c.getDynamicRangeProfile()); + } + } + Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = CameraExtensionCharacteristics.initializeExtension(config.getExtension()); diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index e393a66eb733..0f8bdf64e132 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -51,6 +51,7 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration import android.hardware.camera2.marshal.impl.MarshalQueryableString; import android.hardware.camera2.params.Capability; import android.hardware.camera2.params.DeviceStateSensorOrientationMap; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.Face; import android.hardware.camera2.params.HighSpeedVideoConfiguration; import android.hardware.camera2.params.LensShadingMap; @@ -331,6 +332,7 @@ public class CameraMetadataNative implements Parcelable { private static final int MANDATORY_STREAM_CONFIGURATIONS_DEFAULT = 0; private static final int MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION = 1; private static final int MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT = 2; + private static final int MANDATORY_STREAM_CONFIGURATIONS_10BIT = 3; private static String translateLocationProviderToProcess(final String provider) { if (provider == null) { @@ -678,6 +680,16 @@ public class CameraMetadataNative implements Parcelable { }); sGetCommandMap.put( + CameraCharacteristics.SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getMandatory10BitStreamCombinations(); + } + }); + + sGetCommandMap.put( CameraCharacteristics.SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS.getNativeKey(), new GetCommand() { @Override @@ -771,6 +783,15 @@ public class CameraMetadataNative implements Parcelable { } }); sGetCommandMap.put( + CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getDynamicRangeProfiles(); + } + }); + sGetCommandMap.put( CaptureResult.STATISTICS_OIS_SAMPLES.getNativeKey(), new GetCommand() { @Override @@ -1015,6 +1036,17 @@ public class CameraMetadataNative implements Parcelable { return map; } + private DynamicRangeProfiles getDynamicRangeProfiles() { + int[] profileArray = getBase( + CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP); + + if (profileArray == null) { + return null; + } + + return new DynamicRangeProfiles(profileArray); + } + private Location getGpsLocation() { String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD); double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES); @@ -1378,6 +1410,9 @@ public class CameraMetadataNative implements Parcelable { case MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION: combs = build.getAvailableMandatoryMaximumResolutionStreamCombinations(); break; + case MANDATORY_STREAM_CONFIGURATIONS_10BIT: + combs = build.getAvailableMandatory10BitStreamCombinations(); + break; default: combs = build.getAvailableMandatoryStreamCombinations(); } @@ -1389,6 +1424,10 @@ public class CameraMetadataNative implements Parcelable { return null; } + private MandatoryStreamCombination[] getMandatory10BitStreamCombinations() { + return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_10BIT); + } + private MandatoryStreamCombination[] getMandatoryConcurrentStreamCombinations() { if (!mHasMandatoryConcurrentStreams) { return null; diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java new file mode 100644 index 000000000000..5c1a4aa120ea --- /dev/null +++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.params; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import android.hardware.camera2.CameraMetadata; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Immutable class with information about supported 10-bit dynamic range profiles. + * + * <p>An instance of this class can be queried by retrieving the value of + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES}. + * </p> + * + * <p>All camera devices supporting the + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT} + * capability must advertise the supported 10-bit dynamic range profiles in + * {@link #getSupportedProfiles}</p> + * + * <p>Some devices may not be able to support 8-bit and/or 10-bit output with different dynamic + * range profiles within the same capture request. Such device specific constraints can be queried + * by calling {@link #getProfileCaptureRequestConstraints(int)}. Do note that unsupported + * combinations will result in {@link IllegalArgumentException} when trying to submit a capture + * request. Capture requests that only reference outputs configured using the same dynamic range + * profile value will never fail due to such constraints.</p> + * + * @see OutputConfiguration#setDynamicRangeProfile(int) + */ +public final class DynamicRangeProfiles { + /** + * This the default 8-bit standard profile that will be used in case where camera clients do not + * explicitly configure a supported dynamic range profile by calling + * {@link OutputConfiguration#setDynamicRangeProfile(int)}. + */ + public static final int STANDARD = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD; + + /** + * 10-bit pixel samples encoded using the Hybrid log-gamma transfer function + * + * <p>All 10-bit output capable devices are required to support this profile.</p> + */ + public static final int HLG10 = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10; + + /** + * 10-bit pixel samples encoded using the SMPTE ST 2084 transfer function. + * + * <p>This profile utilizes internal static metadata to increase the quality + * of the capture.</p> + */ + public static final int HDR10 = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10; + + /** + * 10-bit pixel samples encoded using the SMPTE ST 2084 transfer function. + * + * <p>In contrast to HDR10, this profile uses internal per-frame metadata + * to further enhance the quality of the capture.</p> + */ + public static final int HDR10_PLUS = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS; + + /** + * <p>This is a camera mode for Dolby Vision capture optimized for a more scene + * accurate capture. This would typically differ from what a specific device + * might want to tune for a consumer optimized Dolby Vision general capture.</p> + */ + public static final int DOLBY_VISION_10B_HDR_REF = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF; + + /** + * <p>This is the power optimized mode for 10-bit Dolby Vision HDR Reference Mode.</p> + */ + public static final int DOLBY_VISION_10B_HDR_REF_PO = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO; + + /** + * <p>This is the camera mode for the default Dolby Vision capture mode for the + * specific device. This would be tuned by each specific device for consumer + * pleasing results that resonate with their particular audience. We expect + * that each specific device would have a different look for their default + * Dolby Vision capture.</p> + */ + public static final int DOLBY_VISION_10B_HDR_OEM = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM; + + /** + * <p>This is the power optimized mode for 10-bit Dolby Vision HDR device specific capture + * Mode.</p> + */ + public static final int DOLBY_VISION_10B_HDR_OEM_PO = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO; + + /** + * <p>This is the 8-bit version of the Dolby Vision reference capture mode optimized + * for scene accuracy.</p> + */ + public static final int DOLBY_VISION_8B_HDR_REF = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF; + + /** + * <p>This is the power optimized mode for 8-bit Dolby Vision HDR Reference Mode.</p> + */ + public static final int DOLBY_VISION_8B_HDR_REF_PO = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO; + + /** + * <p>This is the 8-bit version of device specific tuned and optimized Dolby Vision + * capture mode.</p> + */ + public static final int DOLBY_VISION_8B_HDR_OEM = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM; + + /** + * <p>This is the power optimized mode for 8-bit Dolby Vision HDR device specific + * capture Mode.</p> + */ + public static final int DOLBY_VISION_8B_HDR_OEM_PO = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO; + + /* + * @hide + */ + public static final int PUBLIC_MAX = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"PROFILE_"}, value = + {STANDARD, + HLG10, + HDR10, + HDR10_PLUS, + DOLBY_VISION_10B_HDR_REF, + DOLBY_VISION_10B_HDR_REF_PO, + DOLBY_VISION_10B_HDR_OEM, + DOLBY_VISION_10B_HDR_OEM_PO, + DOLBY_VISION_8B_HDR_REF, + DOLBY_VISION_8B_HDR_REF_PO, + DOLBY_VISION_8B_HDR_OEM, + DOLBY_VISION_8B_HDR_OEM_PO}) + public @interface Profile { + } + + private final HashMap<Integer, Set<Integer>> mProfileMap = new HashMap<>(); + + /** + * Create a new immutable DynamicRangeProfiles instance. + * + * <p>This constructor takes over the array; do not write to the array afterwards.</p> + * + * <p>Do note that the constructor is available for testing purposes only! + * Camera clients must always retrieve the value of + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES}. + * for a given camera id in order to retrieve the device capabilities.</p> + * + * @param elements + * An array of elements describing the map. It contains two elements per entry which + * describe the supported dynamic range profile value in the first element and in the + * second element a bitmap of concurrently supported dynamic range profiles within the + * same capture request. Bitmap values of 0 indicate that there are no constraints. + * + * @throws IllegalArgumentException + * if the {@code elements} array length is invalid, not divisible by 2 or contains + * invalid element values + * @throws NullPointerException + * if {@code elements} is {@code null} + * + */ + public DynamicRangeProfiles(@NonNull final int[] elements) { + if ((elements.length % 2) != 0) { + throw new IllegalArgumentException("Dynamic range profile map length " + + elements.length + " is not even!"); + } + + for (int i = 0; i < elements.length; i += 2) { + checkProfileValue(elements[i]); + // STANDARD is not expected to be included + if (elements[i] == STANDARD) { + throw new IllegalArgumentException("Dynamic range profile map must not include a" + + " STANDARD profile entry!"); + } + HashSet<Integer> profiles = new HashSet<>(); + + if (elements[i+1] != 0) { + for (int profile = STANDARD; profile < PUBLIC_MAX; profile <<= 1) { + if ((elements[i+1] & profile) != 0) { + profiles.add(profile); + } + } + } + + mProfileMap.put(elements[i], profiles); + } + + // Build the STANDARD constraints depending on the advertised 10-bit limitations + HashSet<Integer> standardConstraints = new HashSet<>(); + standardConstraints.add(STANDARD); + for(Integer profile : mProfileMap.keySet()) { + if (mProfileMap.get(profile).isEmpty() || mProfileMap.get(profile).contains(STANDARD)) { + standardConstraints.add(profile); + } + } + + mProfileMap.put(STANDARD, standardConstraints); + } + + + /** + * @hide + */ + public static void checkProfileValue(int profile) { + switch (profile) { + case STANDARD: + case HLG10: + case HDR10: + case HDR10_PLUS: + case DOLBY_VISION_10B_HDR_REF: + case DOLBY_VISION_10B_HDR_REF_PO: + case DOLBY_VISION_10B_HDR_OEM: + case DOLBY_VISION_10B_HDR_OEM_PO: + case DOLBY_VISION_8B_HDR_REF: + case DOLBY_VISION_8B_HDR_REF_PO: + case DOLBY_VISION_8B_HDR_OEM: + case DOLBY_VISION_8B_HDR_OEM_PO: + //No-op + break; + default: + throw new IllegalArgumentException("Unknown profile " + profile); + } + } + + /** + * Return a set of supported dynamic range profiles. + * + * @return non-modifiable set of dynamic range profiles + */ + public @NonNull Set<Integer> getSupportedProfiles() { + return Collections.unmodifiableSet(mProfileMap.keySet()); + } + + /** + * Return a list of supported dynamic range profiles that + * can be referenced in a single capture request along with a given + * profile. + * + * <p>For example if assume that a particular 10-bit output capable device + * returns ({@link #STANDARD}, {@link #HLG10}, {@link #HDR10}) as result from calling + * {@link #getSupportedProfiles()} and {@link #getProfileCaptureRequestConstraints(int)} + * returns ({@link #STANDARD}, {@link #HLG10}) when given an argument of {@link #STANDARD}. + * This means that the corresponding camera device will only accept and process capture requests + * that reference outputs configured using {@link #HDR10} dynamic profile or alternatively + * some combination of {@link #STANDARD} and {@link #HLG10}. However trying to + * queue capture requests to outputs that reference both {@link #HDR10} and + * {@link #STANDARD}/{@link #HLG10} will result in {@link IllegalArgumentException}.</p> + * + * <p>The list will be empty in case there are no constraints for the given + * profile.</p> + * + * @return non-modifiable set of dynamic range profiles + * @throws IllegalArgumentException - If the profile argument is not + * within the list returned by + * getSupportedProfiles() + * + * @see OutputConfiguration#setDynamicRangeProfile(int) + */ + public @NonNull Set<Integer> getProfileCaptureRequestConstraints(@Profile int profile) { + Set<Integer> ret = mProfileMap.get(profile); + if (ret == null) { + throw new IllegalArgumentException("Unsupported profile!"); + } + + return Collections.unmodifiableSet(ret); + } +} diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index a6789213b50b..32c15da4a909 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -18,8 +18,6 @@ package android.hardware.camera2.params; import static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormat; -import static com.android.internal.util.Preconditions.*; - import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.ImageFormat; @@ -28,7 +26,6 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; -import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.HashCodeHelpers; import android.media.CamcorderProfile; import android.util.Log; @@ -40,6 +37,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; /** @@ -64,6 +62,7 @@ public final class MandatoryStreamCombination { private final boolean mIsInput; private final boolean mIsUltraHighResolution; private final boolean mIsMaximumSize; + private final boolean mIs10BitCapable; /** * Create a new {@link MandatoryStreamInformation}. @@ -119,6 +118,29 @@ public final class MandatoryStreamCombination { */ public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format, boolean isMaximumSize, boolean isInput, boolean isUltraHighResolution) { + this(availableSizes, format, isMaximumSize, isInput, isUltraHighResolution, + /*is10bitCapable*/ false); + } + + /** + * Create a new {@link MandatoryStreamInformation}. + * + * @param availableSizes List of possible stream sizes. + * @param format Image format. + * @param isMaximumSize Whether this is a maximum size stream. + * @param isInput Flag indicating whether this stream is input. + * @param isUltraHighResolution Flag indicating whether this is a ultra-high resolution + * stream. + * @param is10BitCapable Flag indicating whether this stream is able to support 10-bit + * + * @throws IllegalArgumentException + * if sizes is empty or if the format was not user-defined in + * ImageFormat/PixelFormat. + * @hide + */ + public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format, + boolean isMaximumSize, boolean isInput, boolean isUltraHighResolution, + boolean is10BitCapable) { if (availableSizes.isEmpty()) { throw new IllegalArgumentException("No available sizes"); } @@ -127,6 +149,7 @@ public final class MandatoryStreamCombination { mIsMaximumSize = isMaximumSize; mIsInput = isInput; mIsUltraHighResolution = isUltraHighResolution; + mIs10BitCapable = is10BitCapable; } /** @@ -180,6 +203,27 @@ public final class MandatoryStreamCombination { } /** + * Indicates whether this stream is able to support 10-bit output. + * + * <p>10-bit capable streams can be configured to output 10-bit sample data via calls to + * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile} and + * selecting the appropriate output Surface pixel format which can be queried via + * {@link #get10BitFormat()} and will be either + * {@link ImageFormat#PRIVATE} (the default for Surfaces initialized by + * {@link android.view.SurfaceView}, {@link android.view.TextureView}, + * {@link android.media.MediaRecorder}, {@link android.media.MediaCodec} etc.) or + * {@link ImageFormat#YCBCR_P010}.</p> + * + * @return true if stream is able to output 10-bit pixels + * + * @see android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT + * @see OutputConfiguration#setDynamicRangeProfile(int) + */ + public boolean is10BitCapable() { + return mIs10BitCapable; + } + + /** * Return the list of available sizes for this mandatory stream. * * <p>Per documented {@link CameraDevice#createCaptureSession guideline} the largest @@ -201,6 +245,29 @@ public final class MandatoryStreamCombination { * @return integer format. */ public @Format int getFormat() { + // P010 YUV streams must be supported along with SDR 8-bit YUV streams + if ((mIs10BitCapable) && (mFormat == ImageFormat.YCBCR_P010)) { + return ImageFormat.YUV_420_888; + } + return mFormat; + } + + /** + * Retrieve the mandatory stream 10-bit {@code format} for 10-bit capable streams. + * + * <p>In case {@link #is10BitCapable()} returns {@code true}, then this method + * will return the corresponding 10-bit output Surface pixel format. Depending on + * the stream type it will be either {@link ImageFormat#PRIVATE} or + * {@link ImageFormat#YCBCR_P010}.</p> + * + * @return integer format. + * @throws UnsupportedOperationException in case the stream is not capable of 10-bit output + * @see #is10BitCapable() + */ + public @Format int get10BitFormat() { + if (!mIs10BitCapable) { + throw new UnsupportedOperationException("10-bit output is not supported!"); + } return mFormat; } @@ -932,6 +999,41 @@ public final class MandatoryStreamCombination { /*reprocessType*/ ReprocessType.PRIVATE), }; + private static StreamCombinationTemplate s10BitOutputStreamCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM)}, + "Simple preview, GPU video processing, or no-preview video recording"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.MAXIMUM)}, + "In-application video/image processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "Standard still imaging"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "Maximum-resolution in-app processing with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.PREVIEW)}, + "Maximum-resolution two-input in-app processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "High-resolution video recording with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "High-resolution recording with in-app snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "High-resolution recording with video snapshot"), + }; + /** * Helper builder class to generate a list of available mandatory stream combinations. * @hide @@ -971,6 +1073,86 @@ public final class MandatoryStreamCombination { } /** + * Retrieve a list of all available mandatory 10-bit output capable stream combinations. + * + * @return a non-modifiable list of supported mandatory 10-bit capable stream combinations, + * null in case device is not 10-bit output capable. + */ + public @NonNull List<MandatoryStreamCombination> + getAvailableMandatory10BitStreamCombinations() { + // Since 10-bit streaming support is optional, we mandate these stream + // combinations regardless of camera device capabilities. + + StreamCombinationTemplate []chosenStreamCombinations = s10BitOutputStreamCombinations; + if (!is10BitOutputSupported()) { + Log.v(TAG, "Device is not able to output 10-bit!"); + return null; + } + + HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes = + enumerateAvailableSizes(); + if (availableSizes == null) { + Log.e(TAG, "Available size enumeration failed!"); + return null; + } + + ArrayList<MandatoryStreamCombination> availableStreamCombinations = new ArrayList<>(); + availableStreamCombinations.ensureCapacity(chosenStreamCombinations.length); + for (StreamCombinationTemplate combTemplate : chosenStreamCombinations) { + ArrayList<MandatoryStreamInformation> streamsInfo = new ArrayList<>(); + streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length); + for (StreamTemplate template : combTemplate.mStreamTemplates) { + List<Size> sizes = null; + Pair<SizeThreshold, Integer> pair; + pair = new Pair<>(template.mSizeThreshold, new Integer(template.mFormat)); + sizes = availableSizes.get(pair); + if (template.mFormat == ImageFormat.YCBCR_P010) { + // Make sure that exactly the same 10 and 8-bit YUV streams sizes are + // supported + pair = new Pair<>(template.mSizeThreshold, + new Integer(ImageFormat.YUV_420_888)); + HashSet<Size> sdrYuvSizes = new HashSet<>(availableSizes.get(pair)); + if (!sdrYuvSizes.equals(new HashSet<>(sizes))) { + Log.e(TAG, "The supported 10-bit YUV sizes are different from the" + + " supported 8-bit YUV sizes!"); + return null; + } + } + + MandatoryStreamInformation streamInfo; + boolean isMaximumSize = + (template.mSizeThreshold == SizeThreshold.MAXIMUM); + try { + streamInfo = new MandatoryStreamInformation(sizes, template.mFormat, + isMaximumSize, /*isInput*/ false, + /*isUltraHighResolution*/ false, + /*is10BitCapable*/ template.mFormat != ImageFormat.JPEG); + } catch (IllegalArgumentException e) { + Log.e(TAG, "No available sizes found for format: " + template.mFormat + + " size threshold: " + template.mSizeThreshold + " combination: " + + combTemplate.mDescription); + return null; + } + streamsInfo.add(streamInfo); + } + + MandatoryStreamCombination streamCombination; + try { + streamCombination = new MandatoryStreamCombination(streamsInfo, + combTemplate.mDescription, /*isReprocessable*/ false); + } catch (IllegalArgumentException e) { + Log.e(TAG, "No stream information for mandatory combination: " + + combTemplate.mDescription); + return null; + } + + availableStreamCombinations.add(streamCombination); + } + + return Collections.unmodifiableList(availableStreamCombinations); + } + + /** * Retrieve a list of all available mandatory concurrent stream combinations. * This method should only be called for devices which are listed in combinations returned * by CameraManager.getConcurrentCameraIds. @@ -1444,7 +1626,8 @@ public final class MandatoryStreamCombination { final int[] formats = { ImageFormat.PRIVATE, ImageFormat.YUV_420_888, - ImageFormat.JPEG + ImageFormat.JPEG, + ImageFormat.YCBCR_P010 }; Size recordingMaxSize = new Size(0, 0); Size previewMaxSize = new Size(0, 0); @@ -1464,7 +1647,11 @@ public final class MandatoryStreamCombination { HashMap<Integer, Size[]> allSizes = new HashMap<Integer, Size[]>(); for (int format : formats) { Integer intFormat = new Integer(format); - allSizes.put(intFormat, mStreamConfigMap.getOutputSizes(format)); + Size[] sizes = mStreamConfigMap.getOutputSizes(format); + if (sizes == null) { + sizes = new Size[0]; + } + allSizes.put(intFormat, sizes); } List<Size> previewSizes = getSizesWithinBound( @@ -1646,6 +1833,14 @@ public final class MandatoryStreamCombination { } /** + * Check whether the current device supports 10-bit output. + */ + private boolean is10BitOutputSupported() { + return isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT); + } + + /** * Check whether the current device supports private reprocessing. */ private boolean isPrivateReprocessingSupported() { diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 5bb7201eff65..f2b881ba7758 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -29,6 +29,8 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.MultiResolutionImageReader; +import android.hardware.camera2.params.DynamicRangeProfiles; +import android.hardware.camera2.params.DynamicRangeProfiles.Profile; import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.SurfaceUtils; @@ -258,6 +260,39 @@ public final class OutputConfiguration implements Parcelable { } /** + * Set a specific device supported dynamic range profile. + * + * <p>Clients can choose from any profile advertised as supported in + * CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES + * queried using {@link DynamicRangeProfiles#getSupportedProfiles()}. + * If this is not explicitly set, then the default profile will be + * {@link DynamicRangeProfiles#STANDARD}.</p> + * + * <p>Do note that invalid combinations between the registered output + * surface pixel format and the configured dynamic range profile will + * cause capture session initialization failure. Invalid combinations + * include any 10-bit dynamic range profile advertised in + * {@link DynamicRangeProfiles#getSupportedProfiles()} combined with + * an output Surface pixel format different from {@link ImageFormat#PRIVATE} + * (the default for Surfaces initialized by {@link android.view.SurfaceView}, + * {@link android.view.TextureView}, {@link android.media.MediaRecorder}, + * {@link android.media.MediaCodec} etc.) + * or {@link ImageFormat#YCBCR_P010}.</p> + */ + public void setDynamicRangeProfile(@Profile int profile) { + mDynamicRangeProfile = profile; + } + + /** + * Return current dynamic range profile. + * + * @return the currently set dynamic range profile + */ + public @Profile int getDynamicRangeProfile() { + return mDynamicRangeProfile; + } + + /** * Create a new {@link OutputConfiguration} instance. * * <p>This constructor takes an argument for desired camera rotation</p> @@ -319,6 +354,7 @@ public final class OutputConfiguration implements Parcelable { mPhysicalCameraId = null; mIsMultiResolution = false; mSensorPixelModesUsed = new ArrayList<Integer>(); + mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; } /** @@ -416,6 +452,7 @@ public final class OutputConfiguration implements Parcelable { mPhysicalCameraId = null; mIsMultiResolution = false; mSensorPixelModesUsed = new ArrayList<Integer>(); + mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; } /** @@ -718,6 +755,7 @@ public final class OutputConfiguration implements Parcelable { this.mPhysicalCameraId = other.mPhysicalCameraId; this.mIsMultiResolution = other.mIsMultiResolution; this.mSensorPixelModesUsed = other.mSensorPixelModesUsed; + this.mDynamicRangeProfile = other.mDynamicRangeProfile; } /** @@ -737,6 +775,8 @@ public final class OutputConfiguration implements Parcelable { boolean isMultiResolutionOutput = source.readInt() == 1; int[] sensorPixelModesUsed = source.createIntArray(); checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); + int dynamicRangeProfile = source.readInt(); + DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile); mSurfaceGroupId = surfaceSetId; mRotation = rotation; @@ -760,6 +800,7 @@ public final class OutputConfiguration implements Parcelable { mPhysicalCameraId = physicalCameraId; mIsMultiResolution = isMultiResolutionOutput; mSensorPixelModesUsed = convertIntArrayToIntegerList(sensorPixelModesUsed); + mDynamicRangeProfile = dynamicRangeProfile; } /** @@ -875,6 +916,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mIsMultiResolution ? 1 : 0); // writeList doesn't seem to work well with Integer list. dest.writeIntArray(convertIntegerToIntList(mSensorPixelModesUsed)); + dest.writeInt(mDynamicRangeProfile); } /** @@ -920,6 +962,9 @@ public final class OutputConfiguration implements Parcelable { if (mSurfaces.get(i) != other.mSurfaces.get(i)) return false; } + if (mDynamicRangeProfile != other.mDynamicRangeProfile) { + return false; + } return true; } @@ -939,7 +984,8 @@ public final class OutputConfiguration implements Parcelable { mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), - mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode()); + mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), + mDynamicRangeProfile); } return HashCodeHelpers.hashCode( @@ -947,7 +993,8 @@ public final class OutputConfiguration implements Parcelable { mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), - mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode()); + mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), + mDynamicRangeProfile); } private static final String TAG = "OutputConfiguration"; @@ -979,4 +1026,6 @@ public final class OutputConfiguration implements Parcelable { private boolean mIsMultiResolution; // The sensor pixel modes that this OutputConfiguration will use private ArrayList<Integer> mSensorPixelModesUsed; + // Dynamic range profile + private int mDynamicRangeProfile; } diff --git a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java index 2d725989af17..80db38fc9d8f 100644 --- a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java @@ -16,6 +16,8 @@ package android.hardware.camera2.params; +import static com.android.internal.R.string.hardware; + import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -149,6 +151,16 @@ public final class RecommendedStreamConfigurationMap { public static final int USECASE_LOW_LATENCY_SNAPSHOT = 0x6; /** + * If supported, the recommended 10-bit output stream configurations must include + * a subset of the advertised {@link android.graphics.ImageFormat#YCBCR_P010} and + * {@link android.graphics.ImageFormat#PRIVATE} outputs that are optimized for power + * and performance when registered along with a supported 10-bit dynamic range profile. + * {@see android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile} for + * details. + */ + public static final int USECASE_10BIT_OUTPUT = 0x8; + + /** * Device specific use cases. * @hide */ @@ -163,7 +175,8 @@ public final class RecommendedStreamConfigurationMap { USECASE_SNAPSHOT, USECASE_ZSL, USECASE_RAW, - USECASE_LOW_LATENCY_SNAPSHOT}) + USECASE_LOW_LATENCY_SNAPSHOT, + USECASE_10BIT_OUTPUT}) public @interface RecommendedUsecase {}; /** diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index de5c9adbc599..89ac8bf2d7bc 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1132,41 +1132,48 @@ public final class DisplayManager { } /** - * Sets the default display mode, according to the refresh rate and the resolution chosen by the - * user. - * + * Sets the global default {@link Display.Mode}. The display mode includes preference for + * resolution and refresh rate. The mode change is applied globally, i.e. to all the connected + * displays. If the mode specified is not supported by a connected display, then no mode change + * occurs for that display. + * + * @param mode The {@link Display.Mode} to set, which can include resolution and/or + * refresh-rate. It is created using {@link Display.Mode.Builder}. + *` * @hide */ @TestApi @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) - public void setUserPreferredDisplayMode(@NonNull Display.Mode mode) { + public void setGlobalUserPreferredDisplayMode(@NonNull Display.Mode mode) { // Create a new object containing default values for the unused fields like mode ID and // alternative refresh rates. Display.Mode preferredMode = new Display.Mode(mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate()); - mGlobal.setUserPreferredDisplayMode(preferredMode); + mGlobal.setUserPreferredDisplayMode(Display.INVALID_DISPLAY, preferredMode); } /** - * Removes the user preferred display mode. + * Removes the global user preferred display mode. + * User preferred display mode is cleared for all the connected displays. * * @hide */ @TestApi @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) - public void clearUserPreferredDisplayMode() { - mGlobal.setUserPreferredDisplayMode(null); + public void clearGlobalUserPreferredDisplayMode() { + mGlobal.setUserPreferredDisplayMode(Display.INVALID_DISPLAY, null); } /** - * Returns the user preferred display mode. + * Returns the global user preferred display mode. + * If no user preferred mode has been set, or it has been cleared, this method returns null. * * @hide */ @TestApi @Nullable - public Display.Mode getUserPreferredDisplayMode() { - return mGlobal.getUserPreferredDisplayMode(); + public Display.Mode getGlobalUserPreferredDisplayMode() { + return mGlobal.getUserPreferredDisplayMode(Display.INVALID_DISPLAY); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index bf6e6651741c..1a7a63ae8b69 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -896,9 +896,9 @@ public final class DisplayManagerGlobal { * Sets the default display mode, according to the refresh rate and the resolution chosen by the * user. */ - public void setUserPreferredDisplayMode(Display.Mode mode) { + public void setUserPreferredDisplayMode(int displayId, Display.Mode mode) { try { - mDm.setUserPreferredDisplayMode(mode); + mDm.setUserPreferredDisplayMode(displayId, mode); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -907,9 +907,9 @@ public final class DisplayManagerGlobal { /** * Returns the user preferred display mode. */ - public Display.Mode getUserPreferredDisplayMode() { + public Display.Mode getUserPreferredDisplayMode(int displayId) { try { - return mDm.getUserPreferredDisplayMode(); + return mDm.getUserPreferredDisplayMode(displayId); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index d38d388ca8a3..35663af189f4 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -166,8 +166,8 @@ interface IDisplayManager { // Sets the user preferred display mode. // Requires MODIFY_USER_PREFERRED_DISPLAY_MODE permission. - void setUserPreferredDisplayMode(in Mode mode); - Mode getUserPreferredDisplayMode(); + void setUserPreferredDisplayMode(int displayId, in Mode mode); + Mode getUserPreferredDisplayMode(int displayId); // When enabled the app requested display resolution and refresh rate is always selected // in DisplayModeDirector regardless of user settings and policies for low brightness, low diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index acf9427b1241..7e070bc06056 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -58,6 +58,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.security.identity.IdentityCredential; +import android.security.identity.PresentationSession; import android.util.Slog; import android.view.Surface; @@ -271,10 +272,21 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * Get {@link IdentityCredential} object. * @return {@link IdentityCredential} object or null if this doesn't contain one. * @hide + * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. */ + @Deprecated public IdentityCredential getIdentityCredential() { return super.getIdentityCredential(); } + + /** + * Get {@link PresentationSession} object. + * @return {@link PresentationSession} object or null if this doesn't contain one. + * @hide + */ + public PresentationSession getPresentationSession() { + return super.getPresentationSession(); + } } /** diff --git a/core/java/android/hardware/hdmi/DeviceFeatures.java b/core/java/android/hardware/hdmi/DeviceFeatures.java new file mode 100644 index 000000000000..dc530ffbc68b --- /dev/null +++ b/core/java/android/hardware/hdmi/DeviceFeatures.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.hdmi; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** + * Immutable class that stores support status for features in the [Device Features] operand. + * Each feature may be supported, be not supported, or have an unknown support status. + * + * @hide + */ +public class DeviceFeatures { + + @IntDef({ + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, + FEATURE_SUPPORT_UNKNOWN + }) + public @interface FeatureSupportStatus {}; + + public static final int FEATURE_NOT_SUPPORTED = 0; + public static final int FEATURE_SUPPORTED = 1; + public static final int FEATURE_SUPPORT_UNKNOWN = 2; + + /** + * Instance representing no knowledge of any feature's support. + */ + @NonNull + public static final DeviceFeatures ALL_FEATURES_SUPPORT_UNKNOWN = + new Builder(FEATURE_SUPPORT_UNKNOWN).build(); + + /** + * Instance representing no support for any feature. + */ + @NonNull + public static final DeviceFeatures NO_FEATURES_SUPPORTED = + new Builder(FEATURE_NOT_SUPPORTED).build(); + + @FeatureSupportStatus private final int mRecordTvScreenSupport; + @FeatureSupportStatus private final int mSetOsdStringSupport; + @FeatureSupportStatus private final int mDeckControlSupport; + @FeatureSupportStatus private final int mSetAudioRateSupport; + @FeatureSupportStatus private final int mArcTxSupport; + @FeatureSupportStatus private final int mArcRxSupport; + @FeatureSupportStatus private final int mSetAudioVolumeLevelSupport; + + private DeviceFeatures(@NonNull Builder builder) { + this.mRecordTvScreenSupport = builder.mRecordTvScreenSupport; + this.mSetOsdStringSupport = builder.mOsdStringSupport; + this.mDeckControlSupport = builder.mDeckControlSupport; + this.mSetAudioRateSupport = builder.mSetAudioRateSupport; + this.mArcTxSupport = builder.mArcTxSupport; + this.mArcRxSupport = builder.mArcRxSupport; + this.mSetAudioVolumeLevelSupport = builder.mSetAudioVolumeLevelSupport; + } + + /** + * Converts an instance to a builder. + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Constructs an instance from a [Device Features] operand. + * + * Bit 7 of [Device Features] is currently ignored. It indicates whether the operand spans more + * than one byte, but only the first byte contains information as of CEC 2.0. + * + * @param deviceFeaturesOperand The [Device Features] operand to parse. + * @return Instance representing the [Device Features] operand. + */ + @NonNull + public static DeviceFeatures fromOperand(@NonNull byte[] deviceFeaturesOperand) { + Builder builder = new Builder(FEATURE_SUPPORT_UNKNOWN); + + // Read feature support status from the bits of [Device Features] + if (deviceFeaturesOperand.length >= 1) { + byte b = deviceFeaturesOperand[0]; + builder + .setRecordTvScreenSupport(bitToFeatureSupportStatus(((b >> 6) & 1) == 1)) + .setSetOsdStringSupport(bitToFeatureSupportStatus(((b >> 5) & 1) == 1)) + .setDeckControlSupport(bitToFeatureSupportStatus(((b >> 4) & 1) == 1)) + .setSetAudioRateSupport(bitToFeatureSupportStatus(((b >> 3) & 1) == 1)) + .setArcTxSupport(bitToFeatureSupportStatus(((b >> 2) & 1) == 1)) + .setArcRxSupport(bitToFeatureSupportStatus(((b >> 1) & 1) == 1)) + .setSetAudioVolumeLevelSupport(bitToFeatureSupportStatus((b & 1) == 1)); + } + return builder.build(); + } + + /** + * Returns the input that is not {@link #FEATURE_SUPPORT_UNKNOWN}. If neither is equal to + * {@link #FEATURE_SUPPORT_UNKNOWN}, returns the second input. + */ + private static @FeatureSupportStatus int updateFeatureSupportStatus( + @FeatureSupportStatus int oldStatus, @FeatureSupportStatus int newStatus) { + if (newStatus == FEATURE_SUPPORT_UNKNOWN) { + return oldStatus; + } else { + return newStatus; + } + } + + /** + * Returns the [Device Features] operand corresponding to this instance. + * {@link #FEATURE_SUPPORT_UNKNOWN} maps to 0, indicating no support. + * + * As of CEC 2.0, the returned byte array will always be of length 1. + */ + @NonNull + public byte[] toOperand() { + byte result = 0; + + if (mRecordTvScreenSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 6); + if (mSetOsdStringSupport == FEATURE_SUPPORTED) result = (byte) (1 << 5); + if (mDeckControlSupport == FEATURE_SUPPORTED) result = (byte) (1 << 4); + if (mSetAudioRateSupport == FEATURE_SUPPORTED) result = (byte) (1 << 3); + if (mArcTxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 2); + if (mArcRxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 1); + if (mSetAudioVolumeLevelSupport == FEATURE_SUPPORTED) result = (byte) 1; + + return new byte[]{ result }; + } + + @FeatureSupportStatus + private static int bitToFeatureSupportStatus(boolean bit) { + return bit ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED; + } + + /** + * Returns whether the device is a TV that supports <Record TV Screen>. + */ + @FeatureSupportStatus + public int getRecordTvScreenSupport() { + return mRecordTvScreenSupport; + } + + /** + * Returns whether the device is a TV that supports <Set OSD String>. + */ + @FeatureSupportStatus + public int getSetOsdStringSupport() { + return mSetOsdStringSupport; + } + + /** + * Returns whether the device supports being controlled by Deck Control. + */ + @FeatureSupportStatus + public int getDeckControlSupport() { + return mDeckControlSupport; + } + + /** + * Returns whether the device is a Source that supports <Set Audio Rate>. + */ + @FeatureSupportStatus + public int getSetAudioRateSupport() { + return mSetAudioRateSupport; + } + + /** + * Returns whether the device is a Sink that supports ARC Tx. + */ + @FeatureSupportStatus + public int getArcTxSupport() { + return mArcTxSupport; + } + + /** + * Returns whether the device is a Source that supports ARC Rx. + */ + @FeatureSupportStatus + public int getArcRxSupport() { + return mArcRxSupport; + } + + /** + * Returns whether the device supports <Set Audio Volume Level>. + */ + @FeatureSupportStatus + public int getSetAudioVolumeLevelSupport() { + return mSetAudioVolumeLevelSupport; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof DeviceFeatures)) { + return false; + } + + DeviceFeatures other = (DeviceFeatures) obj; + return mRecordTvScreenSupport == other.mRecordTvScreenSupport + && mSetOsdStringSupport == other.mSetOsdStringSupport + && mDeckControlSupport == other.mDeckControlSupport + && mSetAudioRateSupport == other.mSetAudioRateSupport + && mArcTxSupport == other.mArcTxSupport + && mArcRxSupport == other.mArcRxSupport + && mSetAudioVolumeLevelSupport == other.mSetAudioVolumeLevelSupport; + } + + @Override + public int hashCode() { + return java.util.Objects.hash( + mRecordTvScreenSupport, + mSetOsdStringSupport, + mDeckControlSupport, + mSetAudioRateSupport, + mArcTxSupport, + mArcRxSupport, + mSetAudioVolumeLevelSupport + ); + } + + @NonNull + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Device features: "); + s.append("record_tv_screen: ") + .append(featureSupportStatusToString(mRecordTvScreenSupport)).append(" "); + s.append("set_osd_string: ") + .append(featureSupportStatusToString(mSetOsdStringSupport)).append(" "); + s.append("deck_control: ") + .append(featureSupportStatusToString(mDeckControlSupport)).append(" "); + s.append("set_audio_rate: ") + .append(featureSupportStatusToString(mSetAudioRateSupport)).append(" "); + s.append("arc_tx: ") + .append(featureSupportStatusToString(mArcTxSupport)).append(" "); + s.append("arc_rx: ") + .append(featureSupportStatusToString(mArcRxSupport)).append(" "); + s.append("set_audio_volume_level: ") + .append(featureSupportStatusToString(mSetAudioVolumeLevelSupport)).append(" "); + return s.toString(); + } + + @NonNull + private static String featureSupportStatusToString(@FeatureSupportStatus int status) { + switch (status) { + case FEATURE_SUPPORTED: + return "Y"; + case FEATURE_NOT_SUPPORTED: + return "N"; + case FEATURE_SUPPORT_UNKNOWN: + default: + return "?"; + } + } + + /** + * Builder for {@link DeviceFeatures} instances. + */ + public static final class Builder { + @FeatureSupportStatus private int mRecordTvScreenSupport; + @FeatureSupportStatus private int mOsdStringSupport; + @FeatureSupportStatus private int mDeckControlSupport; + @FeatureSupportStatus private int mSetAudioRateSupport; + @FeatureSupportStatus private int mArcTxSupport; + @FeatureSupportStatus private int mArcRxSupport; + @FeatureSupportStatus private int mSetAudioVolumeLevelSupport; + + private Builder(@FeatureSupportStatus int defaultFeatureSupportStatus) { + mRecordTvScreenSupport = defaultFeatureSupportStatus; + mOsdStringSupport = defaultFeatureSupportStatus; + mDeckControlSupport = defaultFeatureSupportStatus; + mSetAudioRateSupport = defaultFeatureSupportStatus; + mArcTxSupport = defaultFeatureSupportStatus; + mArcRxSupport = defaultFeatureSupportStatus; + mSetAudioVolumeLevelSupport = defaultFeatureSupportStatus; + } + + private Builder(DeviceFeatures info) { + mRecordTvScreenSupport = info.getRecordTvScreenSupport(); + mOsdStringSupport = info.getSetOsdStringSupport(); + mDeckControlSupport = info.getDeckControlSupport(); + mSetAudioRateSupport = info.getSetAudioRateSupport(); + mArcTxSupport = info.getArcTxSupport(); + mArcRxSupport = info.getArcRxSupport(); + mSetAudioVolumeLevelSupport = info.getSetAudioVolumeLevelSupport(); + } + + /** + * Creates a new {@link DeviceFeatures} object. + */ + public DeviceFeatures build() { + return new DeviceFeatures(this); + } + + /** + * Sets the value for {@link #getRecordTvScreenSupport()}. + */ + @NonNull + public Builder setRecordTvScreenSupport(@FeatureSupportStatus int recordTvScreenSupport) { + mRecordTvScreenSupport = recordTvScreenSupport; + return this; + } + + /** + * Sets the value for {@link #getSetOsdStringSupport()}. + */ + @NonNull + public Builder setSetOsdStringSupport(@FeatureSupportStatus int setOsdStringSupport) { + mOsdStringSupport = setOsdStringSupport; + return this; + } + + /** + * Sets the value for {@link #getDeckControlSupport()}. + */ + @NonNull + public Builder setDeckControlSupport(@FeatureSupportStatus int deckControlSupport) { + mDeckControlSupport = deckControlSupport; + return this; + } + + /** + * Sets the value for {@link #getSetAudioRateSupport()}. + */ + @NonNull + public Builder setSetAudioRateSupport(@FeatureSupportStatus int setAudioRateSupport) { + mSetAudioRateSupport = setAudioRateSupport; + return this; + } + + /** + * Sets the value for {@link #getArcTxSupport()}. + */ + @NonNull + public Builder setArcTxSupport(@FeatureSupportStatus int arcTxSupport) { + mArcTxSupport = arcTxSupport; + return this; + } + + /** + * Sets the value for {@link #getArcRxSupport()}. + */ + @NonNull + public Builder setArcRxSupport(@FeatureSupportStatus int arcRxSupport) { + mArcRxSupport = arcRxSupport; + return this; + } + + /** + * Sets the value for {@link #getSetAudioRateSupport()}. + */ + @NonNull + public Builder setSetAudioVolumeLevelSupport( + @FeatureSupportStatus int setAudioVolumeLevelSupport) { + mSetAudioVolumeLevelSupport = setAudioVolumeLevelSupport; + return this; + } + + /** + * Updates all fields with those in a 'new' instance of {@link DeviceFeatures}. + * All fields are replaced with those in the new instance, except when the field is + * {@link #FEATURE_SUPPORT_UNKNOWN} in the new instance. + */ + @NonNull + public Builder update(DeviceFeatures newDeviceFeatures) { + mRecordTvScreenSupport = updateFeatureSupportStatus(mRecordTvScreenSupport, + newDeviceFeatures.getRecordTvScreenSupport()); + mOsdStringSupport = updateFeatureSupportStatus(mOsdStringSupport, + newDeviceFeatures.getSetOsdStringSupport()); + mDeckControlSupport = updateFeatureSupportStatus(mDeckControlSupport, + newDeviceFeatures.getDeckControlSupport()); + mSetAudioRateSupport = updateFeatureSupportStatus(mSetAudioRateSupport, + newDeviceFeatures.getSetAudioRateSupport()); + mArcTxSupport = updateFeatureSupportStatus(mArcTxSupport, + newDeviceFeatures.getArcTxSupport()); + mArcRxSupport = updateFeatureSupportStatus(mArcRxSupport, + newDeviceFeatures.getArcRxSupport()); + mSetAudioVolumeLevelSupport = updateFeatureSupportStatus(mSetAudioVolumeLevelSupport, + newDeviceFeatures.getSetAudioVolumeLevelSupport()); + return this; + } + } +} diff --git a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java index c0b177d0efde..e3ea4e25ddd1 100644 --- a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java +++ b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java @@ -68,6 +68,9 @@ public class HdmiDeviceInfo implements Parcelable { */ public static final int ADDR_INTERNAL = 0; + /** Invalid or uninitialized logical address */ + public static final int ADDR_INVALID = -1; + /** * Physical address used to indicate the source comes from internal device. The physical address * of TV(0) is used. @@ -83,7 +86,15 @@ public class HdmiDeviceInfo implements Parcelable { /** Invalid device ID */ public static final int ID_INVALID = 0xFFFF; - /** Device info used to indicate an inactivated device. */ + /** Unknown vendor ID */ + public static final int VENDOR_ID_UNKNOWN = 0xFFFFFF; + + /** + * Instance that represents an inactive device. + * Can be passed to an input change listener to indicate that the active source + * yielded its status, allowing the listener to take an appropriate action such as + * switching to another input. + */ public static final HdmiDeviceInfo INACTIVE_DEVICE = new HdmiDeviceInfo(); private static final int HDMI_DEVICE_TYPE_CEC = 0; @@ -109,10 +120,11 @@ public class HdmiDeviceInfo implements Parcelable { private final int mLogicalAddress; private final int mDeviceType; @HdmiCecVersion - private final int mHdmiCecVersion; + private final int mCecVersion; private final int mVendorId; private final String mDisplayName; private final int mDevicePowerStatus; + private final DeviceFeatures mDeviceFeatures; // MHL only parameters. private final int mDeviceId; @@ -137,14 +149,22 @@ public class HdmiDeviceInfo implements Parcelable { int powerStatus = source.readInt(); String displayName = source.readString(); int cecVersion = source.readInt(); - return new HdmiDeviceInfo(logicalAddress, physicalAddress, portId, - deviceType, vendorId, displayName, powerStatus, cecVersion); + return cecDeviceBuilder() + .setLogicalAddress(logicalAddress) + .setPhysicalAddress(physicalAddress) + .setPortId(portId) + .setDeviceType(deviceType) + .setVendorId(vendorId) + .setDisplayName(displayName) + .setDevicePowerStatus(powerStatus) + .setCecVersion(cecVersion) + .build(); case HDMI_DEVICE_TYPE_MHL: int deviceId = source.readInt(); int adopterId = source.readInt(); - return new HdmiDeviceInfo(physicalAddress, portId, adopterId, deviceId); + return mhlDevice(physicalAddress, portId, adopterId, deviceId); case HDMI_DEVICE_TYPE_HARDWARE: - return new HdmiDeviceInfo(physicalAddress, portId); + return hardwarePort(physicalAddress, portId); case HDMI_DEVICE_TYPE_INACTIVE: return HdmiDeviceInfo.INACTIVE_DEVICE; default: @@ -159,97 +179,82 @@ public class HdmiDeviceInfo implements Parcelable { }; /** - * Constructor. Used to initialize the instance for CEC device. + * Constructor. Initializes the instance representing an inactive device. + * Can be passed to an input change listener to indicate that the active source + * yielded its status, allowing the listener to take an appropriate action such as + * switching to another input. * - * @param logicalAddress logical address of HDMI-CEC device - * @param physicalAddress physical address of HDMI-CEC device - * @param portId HDMI port ID (1 for HDMI1) - * @param deviceType type of device - * @param vendorId vendor id of device. Used for vendor specific command. - * @param displayName name of device - * @param powerStatus device power status - * @hide + * @deprecated Use {@link #INACTIVE_DEVICE} instead. */ - public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType, - int vendorId, String displayName, int powerStatus, int hdmiCecVersion) { - mHdmiDeviceType = HDMI_DEVICE_TYPE_CEC; - mPhysicalAddress = physicalAddress; - mPortId = portId; - - mId = idForCecDevice(logicalAddress); - mLogicalAddress = logicalAddress; - mDeviceType = deviceType; - mHdmiCecVersion = hdmiCecVersion; - mVendorId = vendorId; - mDevicePowerStatus = powerStatus; - mDisplayName = displayName; + @Deprecated + public HdmiDeviceInfo() { + mHdmiDeviceType = HDMI_DEVICE_TYPE_INACTIVE; + mPhysicalAddress = PATH_INVALID; + mId = ID_INVALID; + + mLogicalAddress = ADDR_INVALID; + mDeviceType = DEVICE_INACTIVE; + mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; + mPortId = PORT_INVALID; + mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; + mDisplayName = "Inactive"; + mVendorId = 0; + mDeviceFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN; mDeviceId = -1; mAdopterId = -1; } /** - * Constructor. Used to initialize the instance for CEC device. + * Converts an instance to a builder. * - * @param logicalAddress logical address of HDMI-CEC device - * @param physicalAddress physical address of HDMI-CEC device - * @param portId HDMI port ID (1 for HDMI1) - * @param deviceType type of device - * @param vendorId vendor id of device. Used for vendor specific command. - * @param displayName name of device - * @param powerStatus device power status * @hide */ - public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType, - int vendorId, String displayName, int powerStatus) { - this(logicalAddress, physicalAddress, portId, deviceType, - vendorId, displayName, powerStatus, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + public Builder toBuilder() { + return new Builder(this); } - /** - * Constructor. Used to initialize the instance for CEC device. - * - * @param logicalAddress logical address of HDMI-CEC device - * @param physicalAddress physical address of HDMI-CEC device - * @param portId HDMI port ID (1 for HDMI1) - * @param deviceType type of device - * @param vendorId vendor id of device. Used for vendor specific command. - * @param displayName name of device - * @hide - */ - public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType, - int vendorId, String displayName) { - this(logicalAddress, physicalAddress, portId, deviceType, - vendorId, displayName, HdmiControlManager.POWER_STATUS_UNKNOWN, - HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private HdmiDeviceInfo(Builder builder) { + this.mHdmiDeviceType = builder.mHdmiDeviceType; + this.mPhysicalAddress = builder.mPhysicalAddress; + this.mPortId = builder.mPortId; + this.mLogicalAddress = builder.mLogicalAddress; + this.mDeviceType = builder.mDeviceType; + this.mCecVersion = builder.mCecVersion; + this.mVendorId = builder.mVendorId; + this.mDisplayName = builder.mDisplayName; + this.mDevicePowerStatus = builder.mDevicePowerStatus; + this.mDeviceFeatures = builder.mDeviceFeatures; + this.mDeviceId = builder.mDeviceId; + this.mAdopterId = builder.mAdopterId; + + switch (mHdmiDeviceType) { + case HDMI_DEVICE_TYPE_MHL: + this.mId = idForMhlDevice(mPortId); + break; + case HDMI_DEVICE_TYPE_HARDWARE: + this.mId = idForHardware(mPortId); + break; + case HDMI_DEVICE_TYPE_CEC: + this.mId = idForCecDevice(mLogicalAddress); + break; + case HDMI_DEVICE_TYPE_INACTIVE: + default: + this.mId = ID_INVALID; + } } /** - * Constructor. Used to initialize the instance for device representing hardware port. + * Creates a Builder for an {@link HdmiDeviceInfo} representing a CEC device. * - * @param physicalAddress physical address of the port - * @param portId HDMI port ID (1 for HDMI1) * @hide */ - public HdmiDeviceInfo(int physicalAddress, int portId) { - mHdmiDeviceType = HDMI_DEVICE_TYPE_HARDWARE; - mPhysicalAddress = physicalAddress; - mPortId = portId; - - mId = idForHardware(portId); - mLogicalAddress = -1; - mDeviceType = DEVICE_RESERVED; - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; - mVendorId = 0; - mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; - mDisplayName = "HDMI" + portId; - - mDeviceId = -1; - mAdopterId = -1; + public static Builder cecDeviceBuilder() { + return new Builder(HDMI_DEVICE_TYPE_CEC); } /** - * Constructor. Used to initialize the instance for MHL device. + * Creates an {@link HdmiDeviceInfo} representing an MHL device. * * @param physicalAddress physical address of HDMI device * @param portId portId HDMI port ID (1 for HDMI1) @@ -257,44 +262,32 @@ public class HdmiDeviceInfo implements Parcelable { * @param deviceId device id of MHL * @hide */ - public HdmiDeviceInfo(int physicalAddress, int portId, int adopterId, int deviceId) { - mHdmiDeviceType = HDMI_DEVICE_TYPE_MHL; - mPhysicalAddress = physicalAddress; - mPortId = portId; - - mId = idForMhlDevice(portId); - mLogicalAddress = -1; - mDeviceType = DEVICE_RESERVED; - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; - mVendorId = 0; - mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; - mDisplayName = "Mobile"; - - mDeviceId = adopterId; - mAdopterId = deviceId; + public static HdmiDeviceInfo mhlDevice( + int physicalAddress, int portId, int adopterId, int deviceId) { + return new Builder(HDMI_DEVICE_TYPE_MHL) + .setPhysicalAddress(physicalAddress) + .setPortId(portId) + .setVendorId(0) + .setDisplayName("Mobile") + .setDeviceId(adopterId) + .setAdopterId(deviceId) + .build(); } /** - * Constructor. Used to initialize the instance representing an inactivated device. - * Can be passed input change listener to indicate the active source yielded - * its status, hence the listener should take an appropriate action such as - * switching to other input. + * Creates an {@link HdmiDeviceInfo} representing a hardware port. + * + * @param physicalAddress physical address of the port + * @param portId HDMI port ID (1 for HDMI1) + * @hide */ - public HdmiDeviceInfo() { - mHdmiDeviceType = HDMI_DEVICE_TYPE_INACTIVE; - mPhysicalAddress = PATH_INVALID; - mId = ID_INVALID; - - mLogicalAddress = -1; - mDeviceType = DEVICE_INACTIVE; - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; - mPortId = PORT_INVALID; - mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; - mDisplayName = "Inactive"; - mVendorId = 0; - - mDeviceId = -1; - mAdopterId = -1; + public static HdmiDeviceInfo hardwarePort(int physicalAddress, int portId) { + return new Builder(HDMI_DEVICE_TYPE_HARDWARE) + .setPhysicalAddress(physicalAddress) + .setPortId(portId) + .setVendorId(0) + .setDisplayName("HDMI" + portId) + .build(); } /** @@ -305,6 +298,15 @@ public class HdmiDeviceInfo implements Parcelable { } /** + * Returns the CEC features that this device supports. + * + * @hide + */ + public DeviceFeatures getDeviceFeatures() { + return mDeviceFeatures; + } + + /** * Returns the id to be used for CEC device. * * @param address logical address of CEC device @@ -372,7 +374,7 @@ public class HdmiDeviceInfo implements Parcelable { */ @HdmiCecVersion public int getCecVersion() { - return mHdmiCecVersion; + return mCecVersion; } /** @@ -485,7 +487,7 @@ public class HdmiDeviceInfo implements Parcelable { dest.writeInt(mVendorId); dest.writeInt(mDevicePowerStatus); dest.writeString(mDisplayName); - dest.writeInt(mHdmiCecVersion); + dest.writeInt(mCecVersion); break; case HDMI_DEVICE_TYPE_MHL: dest.writeInt(mDeviceId); @@ -508,7 +510,7 @@ public class HdmiDeviceInfo implements Parcelable { s.append("logical_address: ").append(String.format("0x%02X", mLogicalAddress)); s.append(" "); s.append("device_type: ").append(mDeviceType).append(" "); - s.append("cec_version: ").append(mHdmiCecVersion).append(" "); + s.append("cec_version: ").append(mCecVersion).append(" "); s.append("vendor_id: ").append(mVendorId).append(" "); s.append("display_name: ").append(mDisplayName).append(" "); s.append("power_status: ").append(mDevicePowerStatus).append(" "); @@ -531,6 +533,11 @@ public class HdmiDeviceInfo implements Parcelable { s.append("physical_address: ").append(String.format("0x%04X", mPhysicalAddress)); s.append(" "); s.append("port_id: ").append(mPortId); + + if (mHdmiDeviceType == HDMI_DEVICE_TYPE_CEC) { + s.append("\n ").append(mDeviceFeatures.toString()); + } + return s.toString(); } @@ -546,7 +553,7 @@ public class HdmiDeviceInfo implements Parcelable { && mPortId == other.mPortId && mLogicalAddress == other.mLogicalAddress && mDeviceType == other.mDeviceType - && mHdmiCecVersion == other.mHdmiCecVersion + && mCecVersion == other.mCecVersion && mVendorId == other.mVendorId && mDevicePowerStatus == other.mDevicePowerStatus && mDisplayName.equals(other.mDisplayName) @@ -562,11 +569,180 @@ public class HdmiDeviceInfo implements Parcelable { mPortId, mLogicalAddress, mDeviceType, - mHdmiCecVersion, + mCecVersion, mVendorId, mDevicePowerStatus, mDisplayName, mDeviceId, mAdopterId); } + + /** + * Builder for {@link HdmiDeviceInfo} instances. + * + * @hide + */ + public static final class Builder { + // Required parameters + private final int mHdmiDeviceType; + + // Common parameters + private int mPhysicalAddress = PATH_INVALID; + private int mPortId = PORT_INVALID; + + // CEC parameters + private int mLogicalAddress = ADDR_INVALID; + private int mDeviceType = DEVICE_RESERVED; + @HdmiCecVersion + private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; + private int mVendorId = VENDOR_ID_UNKNOWN; + private String mDisplayName = ""; + private int mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; + private DeviceFeatures mDeviceFeatures; + + // MHL parameters + private int mDeviceId = -1; + private int mAdopterId = -1; + + private Builder(int hdmiDeviceType) { + mHdmiDeviceType = hdmiDeviceType; + if (hdmiDeviceType == HDMI_DEVICE_TYPE_CEC) { + mDeviceFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN; + } else { + mDeviceFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED; + } + } + + private Builder(@NonNull HdmiDeviceInfo hdmiDeviceInfo) { + mHdmiDeviceType = hdmiDeviceInfo.mHdmiDeviceType; + mPhysicalAddress = hdmiDeviceInfo.mPhysicalAddress; + mPortId = hdmiDeviceInfo.mPortId; + mLogicalAddress = hdmiDeviceInfo.mLogicalAddress; + mDeviceType = hdmiDeviceInfo.mDeviceType; + mCecVersion = hdmiDeviceInfo.mCecVersion; + mVendorId = hdmiDeviceInfo.mVendorId; + mDisplayName = hdmiDeviceInfo.mDisplayName; + mDevicePowerStatus = hdmiDeviceInfo.mDevicePowerStatus; + mDeviceId = hdmiDeviceInfo.mDeviceId; + mAdopterId = hdmiDeviceInfo.mAdopterId; + mDeviceFeatures = hdmiDeviceInfo.mDeviceFeatures; + } + + /** + * Create a new {@link HdmiDeviceInfo} object. + */ + @NonNull + public HdmiDeviceInfo build() { + return new HdmiDeviceInfo(this); + } + + /** + * Sets the value for {@link #getPhysicalAddress()}. + */ + @NonNull + public Builder setPhysicalAddress(int physicalAddress) { + mPhysicalAddress = physicalAddress; + return this; + } + + /** + * Sets the value for {@link #getPortId()}. + */ + @NonNull + public Builder setPortId(int portId) { + mPortId = portId; + return this; + } + + /** + * Sets the value for {@link #getLogicalAddress()}. + */ + @NonNull + public Builder setLogicalAddress(int logicalAddress) { + mLogicalAddress = logicalAddress; + return this; + } + + /** + * Sets the value for {@link #getDeviceType()}. + */ + @NonNull + public Builder setDeviceType(int deviceType) { + mDeviceType = deviceType; + return this; + } + + /** + * Sets the value for {@link #getCecVersion()}. + */ + @NonNull + public Builder setCecVersion(int hdmiCecVersion) { + mCecVersion = hdmiCecVersion; + return this; + } + + /** + * Sets the value for {@link #getVendorId()}. + */ + @NonNull + public Builder setVendorId(int vendorId) { + mVendorId = vendorId; + return this; + } + + /** + * Sets the value for {@link #getDisplayName()}. + */ + @NonNull + public Builder setDisplayName(@NonNull String displayName) { + mDisplayName = displayName; + return this; + } + + /** + * Sets the value for {@link #getDevicePowerStatus()}. + */ + @NonNull + public Builder setDevicePowerStatus(int devicePowerStatus) { + mDevicePowerStatus = devicePowerStatus; + return this; + } + + /** + * Sets the value for {@link #getDeviceFeatures()}. + */ + @NonNull + public Builder setDeviceFeatures(DeviceFeatures deviceFeatures) { + this.mDeviceFeatures = deviceFeatures; + return this; + } + + /** + * Sets the value for {@link #getDeviceId()}. + */ + @NonNull + public Builder setDeviceId(int deviceId) { + mDeviceId = deviceId; + return this; + } + + /** + * Sets the value for {@link #getAdopterId()}. + */ + @NonNull + public Builder setAdopterId(int adopterId) { + mAdopterId = adopterId; + return this; + } + + /** + * Updates the value for {@link #getDeviceFeatures()} with a new set of device features. + * New information overrides the old, except when feature support was unknown. + */ + @NonNull + public Builder updateDeviceFeatures(DeviceFeatures deviceFeatures) { + mDeviceFeatures = mDeviceFeatures.toBuilder().update(deviceFeatures).build(); + return this; + } + } } diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java index 9468ca2590bb..54060cc0a784 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -118,13 +119,14 @@ public class ContextHubClient implements Closeable { * is newly generated (e.g. any regeneration of a callback client, or generation * of a non-equal PendingIntent client), the ID will not be the same. * - * @return The ID of this ContextHubClient. + * @return The ID of this ContextHubClient, in the range [0, 65535]. */ + @IntRange(from = 0, to = 65535) public int getId() { if (mId == null) { throw new IllegalStateException("ID was not set"); } - return mId; + return (0x0000FFFF & mId); } /** diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index e30594fb9da7..6f15588c0724 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -18,6 +18,7 @@ package android.inputmethodservice; import android.annotation.BinderThread; import android.annotation.MainThread; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -29,6 +30,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Log; import android.view.InputChannel; +import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; @@ -50,6 +52,7 @@ import com.android.internal.view.InlineSuggestionsRequestInfo; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -74,6 +77,8 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_HIDE_SOFT_INPUT = 70; private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80; private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90; + private static final int DO_CAN_START_STYLUS_HANDWRITING = 100; + private static final int DO_START_STYLUS_HANDWRITING = 110; final WeakReference<InputMethodServiceInternal> mTarget; final Context mContext; @@ -169,7 +174,8 @@ class IInputMethodWrapper extends IInputMethod.Stub SomeArgs args = (SomeArgs) msg.obj; try { inputMethod.initializeInternal((IBinder) args.arg1, - (IInputMethodPrivilegedOperations) args.arg2, msg.arg1); + (IInputMethodPrivilegedOperations) args.arg2, msg.arg1, + (boolean) args.arg3); } finally { args.recycle(); } @@ -229,13 +235,25 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_CHANGE_INPUTMETHOD_SUBTYPE: inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj); return; - case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: + case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: { final SomeArgs args = (SomeArgs) msg.obj; inputMethod.onCreateInlineSuggestionsRequest( (InlineSuggestionsRequestInfo) args.arg1, (IInlineSuggestionsRequestCallback) args.arg2); args.recycle(); return; + } + case DO_CAN_START_STYLUS_HANDWRITING: { + inputMethod.canStartStylusHandwriting(msg.arg1); + return; + } + case DO_START_STYLUS_HANDWRITING: { + final SomeArgs args = (SomeArgs) msg.obj; + inputMethod.startStylusHandwriting((InputChannel) args.arg1, + (List<MotionEvent>) args.arg2); + args.recycle(); + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); @@ -272,9 +290,10 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges) { + int configChanges, boolean stylusHwSupported) { mCaller.executeOrSendMessage( - mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, configChanges, token, privOps)); + mCaller.obtainMessageIOOO( + DO_INITIALIZE_INTERNAL, configChanges, token, privOps, stylusHwSupported)); } @BinderThread @@ -383,4 +402,21 @@ class IInputMethodWrapper extends IInputMethod.Stub mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE, subtype)); } + + @BinderThread + @Override + public void canStartStylusHandwriting(int requestId) + throws RemoteException { + mCaller.executeOrSendMessage( + mCaller.obtainMessageI(DO_CAN_START_STYLUS_HANDWRITING, requestId)); + } + + @BinderThread + @Override + public void startStylusHandwriting(@NonNull InputChannel channel, + @Nullable List<MotionEvent> stylusEvents) + throws RemoteException { + mCaller.executeOrSendMessage( + mCaller.obtainMessageOO(DO_START_STYLUS_HANDWRITING, channel, stylusEvents)); + } } diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java new file mode 100644 index 000000000000..e11d63562ce3 --- /dev/null +++ b/core/java/android/inputmethodservice/InkWindow.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice; + +import static android.view.WindowManager.LayoutParams; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.IBinder; +import android.util.Slog; +import android.view.View; +import android.view.WindowManager; + +import com.android.internal.policy.PhoneWindow; + +/** + * Window of type {@code LayoutParams.TYPE_INPUT_METHOD_DIALOG} for drawing + * Handwriting Ink on screen. + * @hide + */ +final class InkWindow extends PhoneWindow { + + private final WindowManager mWindowManager; + + public InkWindow(@NonNull Context context) { + super(context); + + setType(LayoutParams.TYPE_INPUT_METHOD); + final LayoutParams attrs = getAttributes(); + attrs.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + attrs.setFitInsetsTypes(0); + setAttributes(attrs); + // Ink window is not touchable with finger. + addFlags(FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_TOUCHABLE + | FLAG_NOT_FOCUSABLE); + setBackgroundDrawableResource(android.R.color.transparent); + setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + mWindowManager = context.getSystemService(WindowManager.class); + } + + /** + * Method to show InkWindow on screen. + * Emulates internal behavior similar to Dialog.show(). + */ + void show() { + if (getDecorView() == null) { + Slog.i(InputMethodService.TAG, "DecorView is not set for InkWindow. show() failed."); + return; + } + getDecorView().setVisibility(View.VISIBLE); + mWindowManager.addView(getDecorView(), getAttributes()); + } + + /** + * Method to hide InkWindow from screen. + * Emulates internal behavior similar to Dialog.hide(). + * @param remove set {@code true} to remove InkWindow surface completely. + */ + void hide(boolean remove) { + if (getDecorView() != null) { + getDecorView().setVisibility(remove ? View.GONE : View.INVISIBLE); + } + } + + void setToken(@NonNull IBinder token) { + WindowManager.LayoutParams lp = getAttributes(); + lp.token = token; + setAttributes(lp); + } +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 824cc19f8a60..09d50850788b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -54,6 +54,7 @@ import static android.view.WindowInsets.Type.statusBars; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.AnyThread; import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.IntDef; @@ -83,6 +84,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.ResultReceiver; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.Trace; import android.provider.Settings; import android.text.InputType; @@ -94,6 +96,7 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.proto.ProtoOutputStream; import android.view.Gravity; +import android.view.InputChannel; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -119,6 +122,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceProto; +import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import android.widget.FrameLayout; @@ -142,6 +146,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -302,6 +307,44 @@ public class InputMethodService extends AbstractInputMethodService { static final boolean DEBUG = false; /** + * Key for a boolean value that tells whether {@link InputMethodService} is responsible for + * rendering the back button and the IME switcher button or not when the gestural navigation is + * enabled. + * + * <p>This sysprop is just ignored when the gestural navigation mode is not enabled.</p> + * + * <p> + * To avoid complexity that is not necessary for production, you always need to reboot the + * device after modifying this flag as follows: + * <pre> + * $ adb root + * $ adb shell setprop persist.sys.ime.can_render_gestural_nav_buttons true + * $ adb reboot + * </pre> + * </p> + */ + private static final String PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS = + "persist.sys.ime.can_render_gestural_nav_buttons"; + + /** + * Returns whether {@link InputMethodService} is responsible for rendering the back button and + * the IME switcher button or not when the gestural navigation is enabled. + * + * <p>This method is supposed to be used with an assumption that the same value is returned in + * other processes. It is developers' responsibility for rebooting the device when the sysprop + * is modified.</p> + * + * @return {@code true} if {@link InputMethodService} is responsible for rendering the back + * button and the IME switcher button when the gestural navigation is enabled. + * + * @hide + */ + @AnyThread + public static boolean canImeRenderGesturalNavButtons() { + return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, false); + } + + /** * Allows the system to optimize the back button affordance based on the presence of software * keyboard. * @@ -520,9 +563,14 @@ public class InputMethodService extends AbstractInputMethodService { private boolean mAutomotiveHideNavBarForKeyboard; private boolean mIsAutomotive; + private boolean mHandwritingStarted; private Handler mHandler; private boolean mImeSurfaceScheduledForRemoval; private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker(); + private boolean mDestroyed; + + /** Stylus handwriting Ink window. */ + private InkWindow mInkWindow; /** * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput} @@ -598,11 +646,20 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public final void initializeInternal(@NonNull IBinder token, - IInputMethodPrivilegedOperations privilegedOperations, int configChanges) { + IInputMethodPrivilegedOperations privilegedOperations, int configChanges, + boolean stylusHwSupported) { + if (mDestroyed) { + Log.i(TAG, "The InputMethodService has already onDestroyed()." + + "Ignore the initialization."); + return; + } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal"); mConfigTracker.onInitialize(configChanges); mPrivOps.set(privilegedOperations); InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); + if (stylusHwSupported) { + mInkWindow = new InkWindow(mWindow.getContext()); + } attachToken(token); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -635,6 +692,9 @@ public class InputMethodService extends AbstractInputMethodService { attachToWindowToken(token); mToken = token; mWindow.setToken(token); + if (mInkWindow != null) { + mInkWindow.setToken(token); + } } /** @@ -818,6 +878,49 @@ public class InputMethodService extends AbstractInputMethodService { /** * {@inheritDoc} + * @hide + */ + @Override + public void canStartStylusHandwriting(int requestId) { + if (DEBUG) Log.v(TAG, "canStartStylusHandwriting()"); + if (mHandwritingStarted) { + Log.d(TAG, "There is an ongoing Handwriting session. ignoring."); + return; + } + if (!mInputStarted) { + Log.d(TAG, "Input should have started before starting Stylus handwriting."); + return; + } + if (onStartStylusHandwriting()) { + mPrivOps.onStylusHandwritingReady(requestId); + } else { + Log.i(TAG, "IME is not ready. Can't start Stylus Handwriting"); + } + } + + /** + * {@inheritDoc} + * @hide + */ + @MainThread + @Override + public void startStylusHandwriting( + @NonNull InputChannel channel, @Nullable List<MotionEvent> stylusEvents) { + if (DEBUG) Log.v(TAG, "startStylusHandwriting()"); + if (mHandwritingStarted) { + return; + } + + mHandwritingStarted = true; + mShowInputRequested = false; + + mInkWindow.show(); + // TODO: deliver previous @param stylusEvents + // TODO: create spy receiver for @param channel + } + + /** + * {@inheritDoc} */ @MainThread @Override @@ -1435,6 +1538,7 @@ public class InputMethodService extends AbstractInputMethodService { } @Override public void onDestroy() { + mDestroyed = true; super.onDestroy(); mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); @@ -2186,6 +2290,77 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Called when an app requests stylus handwriting + * {@link InputMethodManager#startStylusHandwriting(View)}. + * + * This will always be preceded by {@link #onStartInput(EditorInfo, boolean)} for the + * {@link EditorInfo} and {@link InputConnection} for which stylus handwriting is being + * requested. + * + * If the IME supports handwriting for the current input, it should return {@code true}, + * ensure its inking views are attached to the {@link #getStylusHandwritingWindow()}, and handle + * stylus input received on the ink window via {@link #getCurrentInputConnection()}. + * @return {@code true} if IME can honor the request, {@code false} if IME cannot at this time. + */ + public boolean onStartStylusHandwriting() { + // Intentionally empty + return false; + } + + /** + * Called when the current stylus handwriting session was finished (either by the system or + * via {@link #finishStylusHandwriting()}. + * + * When this is called, the ink window has been made invisible, and the IME no longer + * intercepts handwriting-related {@code MotionEvent}s. + */ + public void onFinishStylusHandwriting() { + // Intentionally empty + } + + /** + * Returns the stylus handwriting inking window. + * IMEs supporting stylus input are expected to attach their inking views to this + * window (e.g. with {@link Window#setContentView(View)} )). Handwriting-related + * {@link MotionEvent}s are dispatched to the attached view hierarchy. + * + * Note: This returns {@code null} if IME doesn't support stylus handwriting + * i.e. if {@link InputMethodInfo#supportsStylusHandwriting()} is false. + * This method should be called after {@link #onStartStylusHandwriting()}. + * @see #onStartStylusHandwriting() + */ + @Nullable + public final Window getStylusHandwritingWindow() { + return mInkWindow; + } + + /** + * Finish the current stylus handwriting session. + * + * This dismisses the {@link #getStylusHandwritingWindow ink window} and stops intercepting + * stylus {@code MotionEvent}s. + * + * Note for IME developers: Call this method at any time to finish current handwriting session. + * Generally, this should be invoked after a short timeout, giving the user enough time + * to start the next stylus stroke, if any. + * + * Handwriting session will be finished by framework on next {@link #onFinishInput()}. + */ + public final void finishStylusHandwriting() { + if (DEBUG) Log.v(TAG, "finishStylusHandwriting()"); + if (mInkWindow == null) { + return; + } + if (!mHandwritingStarted) { + return; + } + + mHandwritingStarted = false; + mInkWindow.hide(false /* remove */); + onFinishStylusHandwriting(); + } + + /** * The system has decided that it may be time to show your input method. * This is called due to a corresponding call to your * {@link InputMethod#showSoftInput InputMethod.showSoftInput()} @@ -2454,6 +2629,9 @@ public class InputMethodService extends AbstractInputMethodService { mInputStarted = false; mStartedInputConnection = null; mCurCompletions = null; + if (mInkWindow != null) { + finishStylusHandwriting(); + } } void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index f50aa991a67c..6284f56c8258 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -69,6 +69,8 @@ interface INetworkPolicyManager { int getMultipathPreference(in Network network); + SubscriptionPlan getSubscriptionPlan(in NetworkTemplate template); + void notifyStatsProviderWarningOrLimitReached(); SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage); void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage); String getSubscriptionPlansOwner(int subId); diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/core/java/android/net/InternalNetworkUpdateRequest.java index 6f093835fb08..f42c4b7c420d 100644 --- a/core/java/android/net/InternalNetworkUpdateRequest.java +++ b/core/java/android/net/InternalNetworkUpdateRequest.java @@ -17,7 +17,6 @@ package android.net; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -27,7 +26,7 @@ import java.util.Objects; public final class InternalNetworkUpdateRequest implements Parcelable { @NonNull private final StaticIpConfiguration mIpConfig; - @Nullable + @NonNull private final NetworkCapabilities mNetworkCapabilities; @NonNull @@ -37,20 +36,16 @@ public final class InternalNetworkUpdateRequest implements Parcelable { @NonNull public NetworkCapabilities getNetworkCapabilities() { - return mNetworkCapabilities == null - ? null : new NetworkCapabilities(mNetworkCapabilities); + return new NetworkCapabilities(mNetworkCapabilities); } /** @hide */ public InternalNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig, - @Nullable final NetworkCapabilities networkCapabilities) { + @NonNull final NetworkCapabilities networkCapabilities) { Objects.requireNonNull(ipConfig); + Objects.requireNonNull(networkCapabilities); mIpConfig = new StaticIpConfiguration(ipConfig); - if (null == networkCapabilities) { - mNetworkCapabilities = null; - } else { - mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); - } + mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); } private InternalNetworkUpdateRequest(@NonNull final Parcel source) { diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 7ebb646ba3eb..c936bfa05118 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -535,6 +535,46 @@ public class NetworkPolicyManager { } /** + * Get subscription plan for the given networkTemplate. + * + * @param template the networkTemplate to get the subscription plan for. + * @return the active {@link SubscriptionPlan} for the given template, or + * {@code null} if not found. + * @hide + */ + @Nullable + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public SubscriptionPlan getSubscriptionPlan(@NonNull NetworkTemplate template) { + try { + return mService.getSubscriptionPlan(template); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notifies that the specified {@link NetworkStatsProvider} has reached its quota + * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or + * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}. + * + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void notifyStatsProviderWarningOrLimitReached() { + try { + mService.notifyStatsProviderWarningOrLimitReached(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Resets network policy settings back to factory defaults. * * @hide diff --git a/core/java/android/net/PrivateDnsConnectivityChecker.java b/core/java/android/net/PrivateDnsConnectivityChecker.java index cfd458c65362..ac97b3616c3b 100644 --- a/core/java/android/net/PrivateDnsConnectivityChecker.java +++ b/core/java/android/net/PrivateDnsConnectivityChecker.java @@ -44,7 +44,7 @@ public class PrivateDnsConnectivityChecker { */ public static boolean canConnectToPrivateDnsServer(@NonNull String hostname) { final SocketFactory factory = SSLSocketFactory.getDefault(); - TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_APP); + TrafficStats.setThreadStatsTagApp(); try (SSLSocket socket = (SSLSocket) factory.createSocket()) { socket.setSoTimeout(CONNECTION_TIMEOUT_MS); diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index 5c2855307509..319382691925 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -96,6 +96,141 @@ public class VpnManager { */ public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; + /** + * Action sent in the intent when an error occurred. + * + * @hide + */ + public static final String ACTION_VPN_MANAGER_ERROR = "android.net.action.VPN_MANAGER_ERROR"; + + /** + * An IKE protocol error. Codes are the codes from IkeProtocolException, RFC 7296. + * + * @hide + */ + public static final String CATEGORY_ERROR_IKE = "android.net.category.ERROR_IKE"; + + /** + * User deactivated the VPN, either by turning it off or selecting a different VPN provider. + * The error code is always 0. + * + * @hide + */ + public static final String CATEGORY_ERROR_USER_DEACTIVATED = + "android.net.category.ERROR_USER_DEACTIVATED"; + + /** + * Network error. Error codes are ERROR_CODE_NETWORK_*. + * + * @hide + */ + public static final String CATEGORY_ERROR_NETWORK = "android.net.category.ERROR_NETWORK"; + + /** + * The key of the session that experienced this error, as returned by + * startProvisionedVpnProfileSession. + * + * @hide + */ + public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY"; + + /** + * Extra for the Network object that was the underlying network at the time of the failure, or + * null if none. + * + * @hide + */ + public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK"; + + /** + * The NetworkCapabilities of the underlying network. + * + * @hide + */ + public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES = + "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES"; + + /** + * The LinkProperties of the underlying network. + * + * @hide + */ + public static final String EXTRA_UNDERLYING_LINK_PROPERTIES = + "android.net.extra.UNDERLYING_LINK_PROPERTIES"; + + /** + * A long timestamp with SystemClock.elapsedRealtime base for when the event happened. + * + * @hide + */ + public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP"; + + /** + * Extra for the error type. This is ERROR_NOT_RECOVERABLE or ERROR_RECOVERABLE. + * + * @hide + */ + public static final String EXTRA_ERROR_TYPE = "android.net.extra.ERROR_TYPE"; + + /** + * Extra for the error code. The value will be 0 for CATEGORY_ERROR_USER_DEACTIVATED, one of + * ERROR_CODE_NETWORK_* for ERROR_CATEGORY_NETWORK or one of values defined in + * IkeProtocolException#ErrorType for CATEGORY_ERROR_IKE. + * + * @hide + */ + public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE"; + + /** + * This error is fatal, e.g. the VPN was disabled or configuration error. The stack will not + * retry connection. + * + * @hide + */ + public static final int ERROR_NOT_RECOVERABLE = 1; + + /** + * The stack experienced an error but will retry with exponential backoff, e.g. network timeout. + * + * @hide + */ + public static final int ERROR_RECOVERABLE = 2; + + /** + * An error code to indicate that there was an UnknownHostException. + * + * @hide + */ + public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0; + + /** + * An error code to indicate that there is a SocketTimeoutException. + * + * @hide + */ + public static final int ERROR_CODE_NETWORK_TIMEOUT = 1; + + /** + * An error code to indicate that the connection is refused. + * + * @hide + */ + public static final int ERROR_CODE_NETWORK_CONNECT = 2; + + /** + * An error code to indicate the connection was reset. (e.g. SocketException) + * + * @hide + */ + public static final int ERROR_CODE_NETWORK_CONNECTION_RESET = 3; + + /** + * An error code to indicate that there is an IOException. + * + * @hide + */ + public static final int ERROR_CODE_NETWORK_IO = 4; + /** @hide */ @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY, TYPE_VPN_OEM}) diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 2ced05693755..1ae1b050d32f 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -41,6 +41,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.NetworkUtilsInternal; import com.android.internal.net.VpnConfig; @@ -50,6 +51,7 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.Socket; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -471,6 +473,13 @@ public class VpnService extends Service { } } + private static void checkNonPrefixBytes(@NonNull InetAddress address, int prefixLength) { + final IpPrefix prefix = new IpPrefix(address, prefixLength); + if (!prefix.getAddress().equals(address)) { + throw new IllegalArgumentException("Bad address"); + } + } + /** * Helper class to create a VPN interface. This class should be always * used within the scope of the outer {@link VpnService}. @@ -481,9 +490,9 @@ public class VpnService extends Service { private final VpnConfig mConfig = new VpnConfig(); @UnsupportedAppUsage - private final List<LinkAddress> mAddresses = new ArrayList<LinkAddress>(); + private final List<LinkAddress> mAddresses = new ArrayList<>(); @UnsupportedAppUsage - private final List<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); + private final List<RouteInfo> mRoutes = new ArrayList<>(); public Builder() { mConfig.user = VpnService.this.getClass().getName(); @@ -555,7 +564,6 @@ public class VpnService extends Service { throw new IllegalArgumentException("Bad address"); } mAddresses.add(new LinkAddress(address, prefixLength)); - mConfig.updateAllowedFamilies(address); return this; } @@ -579,28 +587,68 @@ public class VpnService extends Service { * Add a network route to the VPN interface. Both IPv4 and IPv6 * routes are supported. * + * If a route with the same destination is already present, its type will be updated. + * + * @throws IllegalArgumentException if the route is invalid. + */ + @NonNull + private Builder addRoute(@NonNull IpPrefix prefix, int type) { + check(prefix.getAddress(), prefix.getPrefixLength()); + + final RouteInfo newRoute = new RouteInfo(prefix, /* gateway */ + null, /* interface */ null, type); + + final int index = findRouteIndexByDestination(newRoute); + + if (index == -1) { + mRoutes.add(newRoute); + } else { + mRoutes.set(index, newRoute); + } + + return this; + } + + /** + * Add a network route to the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * * Adding a route implicitly allows traffic from that address family * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily * + * Calling this method overrides previous calls to {@link #excludeRoute} for the same + * destination. + * + * If multiple routes match the packet destination, route with the longest prefix takes + * precedence. + * * @throws IllegalArgumentException if the route is invalid. */ @NonNull public Builder addRoute(@NonNull InetAddress address, int prefixLength) { - check(address, prefixLength); + checkNonPrefixBytes(address, prefixLength); - int offset = prefixLength / 8; - byte[] bytes = address.getAddress(); - if (offset < bytes.length) { - for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) { - if (bytes[offset] != 0) { - throw new IllegalArgumentException("Bad address"); - } - } - } - mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null, null, - RouteInfo.RTN_UNICAST)); - mConfig.updateAllowedFamilies(address); - return this; + return addRoute(new IpPrefix(address, prefixLength), RouteInfo.RTN_UNICAST); + } + + /** + * Add a network route to the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * Calling this method overrides previous calls to {@link #excludeRoute} for the same + * destination. + * + * If multiple routes match the packet destination, route with the longest prefix takes + * precedence. + * + * @throws IllegalArgumentException if the route is invalid. + */ + @NonNull + public Builder addRoute(@NonNull IpPrefix prefix) { + return addRoute(prefix, RouteInfo.RTN_UNICAST); } /** @@ -611,6 +659,12 @@ public class VpnService extends Service { * Adding a route implicitly allows traffic from that address family * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily * + * Calling this method overrides previous calls to {@link #excludeRoute} for the same + * destination. + * + * If multiple routes match the packet destination, route with the longest prefix takes + * precedence. + * * @throws IllegalArgumentException if the route is invalid. * @see #addRoute(InetAddress, int) */ @@ -620,6 +674,23 @@ public class VpnService extends Service { } /** + * Exclude a network route from the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * + * Calling this method overrides previous calls to {@link #addRoute} for the same + * destination. + * + * If multiple routes match the packet destination, route with the longest prefix takes + * precedence. + * + * @throws IllegalArgumentException if the route is invalid. + */ + @NonNull + public Builder excludeRoute(@NonNull IpPrefix prefix) { + return addRoute(prefix, RouteInfo.RTN_THROW); + } + + /** * Add a DNS server to the VPN connection. Both IPv4 and IPv6 * addresses are supported. If none is set, the DNS servers of * the default network will be used. @@ -900,5 +971,23 @@ public class VpnService extends Service { throw new IllegalStateException(e); } } + + private int findRouteIndexByDestination(RouteInfo route) { + for (int i = 0; i < mRoutes.size(); i++) { + if (mRoutes.get(i).getDestination().equals(route.getDestination())) { + return i; + } + } + return -1; + } + + /** + * Method for testing, to observe mRoutes while builder is being used. + * @hide + */ + @VisibleForTesting + public List<RouteInfo> routes() { + return Collections.unmodifiableList(mRoutes); + } } } diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java index febd9b406111..3f7521a12ae9 100644 --- a/core/java/android/net/annotations/PolicyDirection.java +++ b/core/java/android/net/annotations/PolicyDirection.java @@ -24,10 +24,6 @@ import java.lang.annotation.RetentionPolicy; /** * IPsec traffic direction. - * - * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class - * to allow others to statically include it. - * * @hide */ @IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT}) diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java index 1ac3f0a6d7d1..69e63133eb03 100644 --- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java @@ -15,6 +15,9 @@ */ package android.net.vcn; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString; + import static com.android.internal.annotations.VisibleForTesting.Visibility; import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER; import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER; @@ -23,8 +26,12 @@ import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZ import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.net.NetworkCapabilities; +import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria; import android.os.PersistableBundle; import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.ArraySet; @@ -37,32 +44,45 @@ import java.util.Collections; import java.util.Objects; import java.util.Set; -// TODO: Add documents -/** @hide */ +/** + * This class represents a configuration for a network template class of underlying cellular + * networks. + * + * <p>See {@link VcnUnderlyingNetworkTemplate} + */ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate { private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds"; @NonNull private final Set<String> mAllowedNetworkPlmnIds; private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds"; @NonNull private final Set<Integer> mAllowedSpecificCarrierIds; - private static final String ALLOW_ROAMING_KEY = "mAllowRoaming"; - private final boolean mAllowRoaming; + private static final String ROAMING_MATCH_KEY = "mRoamingMatchCriteria"; + private final int mRoamingMatchCriteria; - private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic"; - private final boolean mRequireOpportunistic; + private static final String OPPORTUNISTIC_MATCH_KEY = "mOpportunisticMatchCriteria"; + private final int mOpportunisticMatchCriteria; private VcnCellUnderlyingNetworkTemplate( - int networkQuality, - boolean allowMetered, + int meteredMatchCriteria, + int minEntryUpstreamBandwidthKbps, + int minExitUpstreamBandwidthKbps, + int minEntryDownstreamBandwidthKbps, + int minExitDownstreamBandwidthKbps, Set<String> allowedNetworkPlmnIds, Set<Integer> allowedSpecificCarrierIds, - boolean allowRoaming, - boolean requireOpportunistic) { - super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, allowMetered); + int roamingMatchCriteria, + int opportunisticMatchCriteria) { + super( + NETWORK_PRIORITY_TYPE_CELL, + meteredMatchCriteria, + minEntryUpstreamBandwidthKbps, + minExitUpstreamBandwidthKbps, + minEntryDownstreamBandwidthKbps, + minExitDownstreamBandwidthKbps); mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds); mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds); - mAllowRoaming = allowRoaming; - mRequireOpportunistic = requireOpportunistic; + mRoamingMatchCriteria = roamingMatchCriteria; + mOpportunisticMatchCriteria = opportunisticMatchCriteria; validate(); } @@ -72,15 +92,17 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork protected void validate() { super.validate(); validatePlmnIds(mAllowedNetworkPlmnIds); - Objects.requireNonNull(mAllowedSpecificCarrierIds, "allowedCarrierIds is null"); + Objects.requireNonNull(mAllowedSpecificCarrierIds, "matchingCarrierIds is null"); + validateMatchCriteria(mRoamingMatchCriteria, "mRoamingMatchCriteria"); + validateMatchCriteria(mOpportunisticMatchCriteria, "mOpportunisticMatchCriteria"); } - private static void validatePlmnIds(Set<String> allowedNetworkPlmnIds) { - Objects.requireNonNull(allowedNetworkPlmnIds, "allowedNetworkPlmnIds is null"); + private static void validatePlmnIds(Set<String> matchingOperatorPlmnIds) { + Objects.requireNonNull(matchingOperatorPlmnIds, "matchingOperatorPlmnIds is null"); // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal // digits. - for (String id : allowedNetworkPlmnIds) { + for (String id : matchingOperatorPlmnIds) { if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) { continue; } else { @@ -96,8 +118,16 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork @NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); - final int networkQuality = in.getInt(NETWORK_QUALITY_KEY); - final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY); + final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY); + + final int minEntryUpstreamBandwidthKbps = + in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS); + final int minExitUpstreamBandwidthKbps = + in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS); + final int minEntryDownstreamBandwidthKbps = + in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS); + final int minExitDownstreamBandwidthKbps = + in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS); final PersistableBundle plmnIdsBundle = in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY); @@ -114,16 +144,19 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork PersistableBundleUtils.toList( specificCarrierIdsBundle, INTEGER_DESERIALIZER)); - final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY); - final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY); + final int roamingMatchCriteria = in.getInt(ROAMING_MATCH_KEY); + final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY); return new VcnCellUnderlyingNetworkTemplate( - networkQuality, - allowMetered, + meteredMatchCriteria, + minEntryUpstreamBandwidthKbps, + minExitUpstreamBandwidthKbps, + minEntryDownstreamBandwidthKbps, + minExitDownstreamBandwidthKbps, allowedNetworkPlmnIds, allowedSpecificCarrierIds, - allowRoaming, - requireOpportunistic); + roamingMatchCriteria, + opportunisticMatchCriteria); } /** @hide */ @@ -143,35 +176,51 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER); result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle); - result.putBoolean(ALLOW_ROAMING_KEY, mAllowRoaming); - result.putBoolean(REQUIRE_OPPORTUNISTIC_KEY, mRequireOpportunistic); + result.putInt(ROAMING_MATCH_KEY, mRoamingMatchCriteria); + result.putInt(OPPORTUNISTIC_MATCH_KEY, mOpportunisticMatchCriteria); return result; } - /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */ + /** + * Retrieve the matching operator PLMN IDs, or an empty set if any PLMN ID is acceptable. + * + * @see Builder#setOperatorPlmnIds(Set) + */ @NonNull - public Set<String> getAllowedOperatorPlmnIds() { + public Set<String> getOperatorPlmnIds() { return Collections.unmodifiableSet(mAllowedNetworkPlmnIds); } /** - * Retrieve the allowed specific carrier IDs, or an empty set if any specific carrier ID is - * acceptable. + * Retrieve the matching sim specific carrier IDs, or an empty set if any sim specific carrier + * ID is acceptable. + * + * @see Builder#setSimSpecificCarrierIds(Set) */ @NonNull - public Set<Integer> getAllowedSpecificCarrierIds() { + public Set<Integer> getSimSpecificCarrierIds() { return Collections.unmodifiableSet(mAllowedSpecificCarrierIds); } - /** Return if roaming is allowed. */ - public boolean allowRoaming() { - return mAllowRoaming; + /** + * Return the matching criteria for roaming networks. + * + * @see Builder#setRoaming(int) + */ + @MatchCriteria + public int getRoaming() { + return mRoamingMatchCriteria; } - /** Return if requiring an opportunistic network. */ - public boolean requireOpportunistic() { - return mRequireOpportunistic; + /** + * Return the matching criteria for opportunistic cellular subscriptions. + * + * @see Builder#setOpportunistic(int) + */ + @MatchCriteria + public int getOpportunistic() { + return mOpportunisticMatchCriteria; } @Override @@ -180,8 +229,8 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork super.hashCode(), mAllowedNetworkPlmnIds, mAllowedSpecificCarrierIds, - mAllowRoaming, - mRequireOpportunistic); + mRoamingMatchCriteria, + mOpportunisticMatchCriteria); } @Override @@ -197,8 +246,8 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other; return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds) && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds) - && mAllowRoaming == rhs.mAllowRoaming - && mRequireOpportunistic == rhs.mRequireOpportunistic; + && mRoamingMatchCriteria == rhs.mRoamingMatchCriteria + && mOpportunisticMatchCriteria == rhs.mOpportunisticMatchCriteria; } /** @hide */ @@ -206,77 +255,199 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork void dumpTransportSpecificFields(IndentingPrintWriter pw) { pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString()); pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString()); - pw.println("mAllowRoaming: " + mAllowRoaming); - pw.println("mRequireOpportunistic: " + mRequireOpportunistic); + pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria)); + pw.println( + "mOpportunisticMatchCriteria: " + + getMatchCriteriaString(mOpportunisticMatchCriteria)); } - /** This class is used to incrementally build WifiNetworkPriority objects. */ - public static final class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> { + /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */ + public static final class Builder { + private int mMeteredMatchCriteria = MATCH_ANY; + @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>(); @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>(); - private boolean mAllowRoaming = false; - private boolean mRequireOpportunistic = false; + private int mRoamingMatchCriteria = MATCH_ANY; + private int mOpportunisticMatchCriteria = MATCH_ANY; + + private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS; + private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS; + private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS; + private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS; /** Construct a Builder object. */ public Builder() {} /** - * Set allowed operator PLMN IDs. + * Set the matching criteria for metered networks. + * + * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one + * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will + * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED). + * + * @param matchCriteria the matching criteria for metered networks. Defaults to {@link + * #MATCH_ANY}. + * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED + */ + // The matching getter is defined in the super class. Please see {@link + // VcnUnderlyingNetworkTemplate#getMetered()} + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setMetered(@MatchCriteria int matchCriteria) { + validateMatchCriteria(matchCriteria, "setMetered"); + + mMeteredMatchCriteria = matchCriteria; + return this; + } + + /** + * Set operator PLMN IDs with which a network can match this template. * * <p>This is used to distinguish cases where roaming agreements may dictate a different * priority from a partner's networks. * - * @param allowedNetworkPlmnIds the allowed operator PLMN IDs in String. Defaults to an - * empty set, allowing ANY PLMN ID. A valid PLMN is a concatenation of MNC and MCC, and - * thus consists of 5 or 6 decimal digits. See {@link SubscriptionInfo#getMccString()} - * and {@link SubscriptionInfo#getMncString()}. + * @param operatorPlmnIds the matching operator PLMN IDs in String. Network with one of the + * matching PLMN IDs can match this template. If the set is empty, any PLMN ID will + * match. The default is an empty set. A valid PLMN is a concatenation of MNC and MCC, + * and thus consists of 5 or 6 decimal digits. + * @see SubscriptionInfo#getMccString() + * @see SubscriptionInfo#getMncString() */ @NonNull - public Builder setAllowedOperatorPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) { - validatePlmnIds(allowedNetworkPlmnIds); + public Builder setOperatorPlmnIds(@NonNull Set<String> operatorPlmnIds) { + validatePlmnIds(operatorPlmnIds); mAllowedNetworkPlmnIds.clear(); - mAllowedNetworkPlmnIds.addAll(allowedNetworkPlmnIds); + mAllowedNetworkPlmnIds.addAll(operatorPlmnIds); return this; } /** - * Set allowed specific carrier IDs. + * Set sim specific carrier IDs with which a network can match this template. * - * @param allowedSpecificCarrierIds the allowed specific carrier IDs. Defaults to an empty - * set, allowing ANY carrier ID. See {@link TelephonyManager#getSimSpecificCarrierId()}. + * @param simSpecificCarrierIds the matching sim specific carrier IDs. Network with one of + * the sim specific carrier IDs can match this template. If the set is empty, any + * carrier ID will match. The default is an empty set. + * @see TelephonyManager#getSimSpecificCarrierId() */ @NonNull - public Builder setAllowedSpecificCarrierIds( - @NonNull Set<Integer> allowedSpecificCarrierIds) { - Objects.requireNonNull(allowedSpecificCarrierIds, "allowedCarrierIds is null"); + public Builder setSimSpecificCarrierIds(@NonNull Set<Integer> simSpecificCarrierIds) { + Objects.requireNonNull(simSpecificCarrierIds, "simSpecificCarrierIds is null"); + mAllowedSpecificCarrierIds.clear(); - mAllowedSpecificCarrierIds.addAll(allowedSpecificCarrierIds); + mAllowedSpecificCarrierIds.addAll(simSpecificCarrierIds); return this; } /** - * Set if roaming is allowed. + * Set the matching criteria for roaming networks. + * + * <p>A template where setRoaming(MATCH_REQUIRED) will only match roaming networks (one + * without NET_CAPABILITY_NOT_ROAMING). A template where setRoaming(MATCH_FORBIDDEN) will + * only match a network that is not roaming (one with NET_CAPABILITY_NOT_ROAMING). * - * @param allowRoaming the flag to indicate if roaming is allowed. Defaults to {@code - * false}. + * @param matchCriteria the matching criteria for roaming networks. Defaults to {@link + * #MATCH_ANY}. + * @see NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING */ @NonNull - public Builder setAllowRoaming(boolean allowRoaming) { - mAllowRoaming = allowRoaming; + public Builder setRoaming(@MatchCriteria int matchCriteria) { + validateMatchCriteria(matchCriteria, "setRoaming"); + + mRoamingMatchCriteria = matchCriteria; return this; } /** - * Set if requiring an opportunistic network. + * Set the matching criteria for opportunistic cellular subscriptions. * - * @param requireOpportunistic the flag to indicate if caller requires an opportunistic - * network. Defaults to {@code false}. + * @param matchCriteria the matching criteria for opportunistic cellular subscriptions. + * Defaults to {@link #MATCH_ANY}. + * @see SubscriptionManager#setOpportunistic(boolean, int) */ @NonNull - public Builder setRequireOpportunistic(boolean requireOpportunistic) { - mRequireOpportunistic = requireOpportunistic; + public Builder setOpportunistic(@MatchCriteria int matchCriteria) { + validateMatchCriteria(matchCriteria, "setOpportunistic"); + + mOpportunisticMatchCriteria = matchCriteria; + return this; + } + + /** + * Set the minimum upstream bandwidths that this template will match. + * + * <p>This template will not match a network that does not provide at least the bandwidth + * passed as the entry bandwidth, except in the case that the network is selected as the VCN + * Gateway Connection's underlying network, where it will continue to match until the + * bandwidth drops under the exit bandwidth. + * + * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the + * invalid case where a network fulfills the entry criteria, but at the same time fails the + * exit criteria. + * + * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in + * {@link NetworkCapabilities}. The provided estimates will be used without modification. + * + * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks + * that ARE NOT the already-selected underlying network, or {@code 0} to disable this + * requirement. Disabled by default. + * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network + * that IS the already-selected underlying network, or {@code 0} to disable this + * requirement. Disabled by default. + * @return this {@link Builder} instance, for chaining + */ + @NonNull + // The getter for the two integers are separated, and in the superclass. Please see {@link + // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link + // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()} + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setMinUpstreamBandwidthKbps( + int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) { + validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps); + + mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps; + mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps; + + return this; + } + + /** + * Set the minimum upstream bandwidths that this template will match. + * + * <p>This template will not match a network that does not provide at least the bandwidth + * passed as the entry bandwidth, except in the case that the network is selected as the VCN + * Gateway Connection's underlying network, where it will continue to match until the + * bandwidth drops under the exit bandwidth. + * + * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the + * invalid case where a network fulfills the entry criteria, but at the same time fails the + * exit criteria. + * + * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in + * {@link NetworkCapabilities}. The provided estimates will be used without modification. + * + * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for + * networks that ARE NOT the already-selected underlying network, or {@code 0} to + * disable this requirement. Disabled by default. + * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a + * network that IS the already-selected underlying network, or {@code 0} to disable this + * requirement. Disabled by default. + * @return this {@link Builder} instance, for chaining + */ + @NonNull + // The getter for the two integers are separated, and in the superclass. Please see {@link + // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link + // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()} + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setMinDownstreamBandwidthKbps( + int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) { + validateMinBandwidthKbps( + minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps); + + mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps; + mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps; + return this; } @@ -284,18 +455,15 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork @NonNull public VcnCellUnderlyingNetworkTemplate build() { return new VcnCellUnderlyingNetworkTemplate( - mNetworkQuality, - mAllowMetered, + mMeteredMatchCriteria, + mMinEntryUpstreamBandwidthKbps, + mMinExitUpstreamBandwidthKbps, + mMinEntryDownstreamBandwidthKbps, + mMinExitDownstreamBandwidthKbps, mAllowedNetworkPlmnIds, mAllowedSpecificCarrierIds, - mAllowRoaming, - mRequireOpportunistic); - } - - /** @hide */ - @Override - Builder self() { - return this; + mRoamingMatchCriteria, + mOpportunisticMatchCriteria); } } } diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index d07c24a6529c..a6830b708c31 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -16,7 +16,7 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; -import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static com.android.internal.annotations.VisibleForTesting.Visibility; @@ -42,7 +42,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.SortedSet; @@ -162,30 +162,21 @@ public final class VcnGatewayConnectionConfig { /** @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) - public static final LinkedHashSet<VcnUnderlyingNetworkTemplate> - DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>(); + public static final List<VcnUnderlyingNetworkTemplate> DEFAULT_UNDERLYING_NETWORK_TEMPLATES = + new ArrayList<>(); static { - DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( + DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add( new VcnCellUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setAllowRoaming(true /* allowRoaming */) - .setRequireOpportunistic(true /* requireOpportunistic */) + .setOpportunistic(MATCH_REQUIRED) .build()); - DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( + DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add( new VcnWifiUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) .build()); - DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( + DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add( new VcnCellUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setAllowRoaming(true /* allowRoaming */) - .setRequireOpportunistic(false /* requireOpportunistic */) .build()); } @@ -200,9 +191,9 @@ public final class VcnGatewayConnectionConfig { /** @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) - public static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities"; + public static final String UNDERLYING_NETWORK_TEMPLATES_KEY = "mUnderlyingNetworkTemplates"; - @NonNull private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities; + @NonNull private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates; private static final String MAX_MTU_KEY = "mMaxMtu"; private final int mMaxMtu; @@ -215,7 +206,7 @@ public final class VcnGatewayConnectionConfig { @NonNull String gatewayConnectionName, @NonNull IkeTunnelConnectionParams tunnelConnectionParams, @NonNull Set<Integer> exposedCapabilities, - @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, + @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull long[] retryIntervalsMs, @IntRange(from = MIN_MTU_V6) int maxMtu) { mGatewayConnectionName = gatewayConnectionName; @@ -224,9 +215,9 @@ public final class VcnGatewayConnectionConfig { mRetryIntervalsMs = retryIntervalsMs; mMaxMtu = maxMtu; - mUnderlyingNetworkPriorities = new LinkedHashSet<>(underlyingNetworkPriorities); - if (mUnderlyingNetworkPriorities.isEmpty()) { - mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); + mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates); + if (mUnderlyingNetworkTemplates.isEmpty()) { + mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES); } validate(); @@ -250,22 +241,19 @@ public final class VcnGatewayConnectionConfig { mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList( exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER)); - final PersistableBundle networkPrioritiesBundle = - in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY); + final PersistableBundle networkTemplatesBundle = + in.getPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY); - if (networkPrioritiesBundle == null) { - // UNDERLYING_NETWORK_PRIORITIES_KEY was added in Android T. Thus + if (networkTemplatesBundle == null) { + // UNDERLYING_NETWORK_TEMPLATES_KEY was added in Android T. Thus // VcnGatewayConnectionConfig created on old platforms will not have this data and will // be assigned with the default value - mUnderlyingNetworkPriorities = - new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); - + mUnderlyingNetworkTemplates = new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES); } else { - mUnderlyingNetworkPriorities = - new LinkedHashSet<>( - PersistableBundleUtils.toList( - networkPrioritiesBundle, - VcnUnderlyingNetworkTemplate::fromPersistableBundle)); + mUnderlyingNetworkTemplates = + PersistableBundleUtils.toList( + networkTemplatesBundle, + VcnUnderlyingNetworkTemplate::fromPersistableBundle); } mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY); @@ -285,7 +273,7 @@ public final class VcnGatewayConnectionConfig { checkValidCapability(cap); } - Objects.requireNonNull(mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null"); + validateNetworkTemplateList(mUnderlyingNetworkTemplates); Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null"); validateRetryInterval(mRetryIntervalsMs); @@ -314,6 +302,19 @@ public final class VcnGatewayConnectionConfig { } } + private static void validateNetworkTemplateList( + List<VcnUnderlyingNetworkTemplate> networkPriorityRules) { + Objects.requireNonNull(networkPriorityRules, "networkPriorityRules is null"); + + Set<VcnUnderlyingNetworkTemplate> existingRules = new ArraySet<>(); + for (VcnUnderlyingNetworkTemplate rule : networkPriorityRules) { + Objects.requireNonNull(rule, "Found null value VcnUnderlyingNetworkTemplate"); + if (!existingRules.add(rule)) { + throw new IllegalArgumentException("Found duplicate VcnUnderlyingNetworkTemplate"); + } + } + } + /** * Returns the configured Gateway Connection name. * @@ -368,15 +369,13 @@ public final class VcnGatewayConnectionConfig { } /** - * Retrieve the configured VcnUnderlyingNetworkTemplate list, or a default list if it is not - * configured. + * Retrieve the VcnUnderlyingNetworkTemplate list, or a default list if it is not configured. * - * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkTemplate>) - * @hide + * @see Builder#setVcnUnderlyingNetworkPriorities(List) */ @NonNull - public LinkedHashSet<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() { - return new LinkedHashSet<>(mUnderlyingNetworkPriorities); + public List<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() { + return new ArrayList<>(mUnderlyingNetworkTemplates); } /** @@ -415,15 +414,15 @@ public final class VcnGatewayConnectionConfig { PersistableBundleUtils.fromList( new ArrayList<>(mExposedCapabilities), PersistableBundleUtils.INTEGER_SERIALIZER); - final PersistableBundle networkPrioritiesBundle = + final PersistableBundle networkTemplatesBundle = PersistableBundleUtils.fromList( - new ArrayList<>(mUnderlyingNetworkPriorities), + mUnderlyingNetworkTemplates, VcnUnderlyingNetworkTemplate::toPersistableBundle); result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName); result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle); result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle); - result.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, networkPrioritiesBundle); + result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle); result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs); result.putInt(MAX_MTU_KEY, mMaxMtu); @@ -436,7 +435,7 @@ public final class VcnGatewayConnectionConfig { mGatewayConnectionName, mTunnelConnectionParams, mExposedCapabilities, - mUnderlyingNetworkPriorities, + mUnderlyingNetworkTemplates, Arrays.hashCode(mRetryIntervalsMs), mMaxMtu); } @@ -451,7 +450,7 @@ public final class VcnGatewayConnectionConfig { return mGatewayConnectionName.equals(rhs.mGatewayConnectionName) && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams) && mExposedCapabilities.equals(rhs.mExposedCapabilities) - && mUnderlyingNetworkPriorities.equals(rhs.mUnderlyingNetworkPriorities) + && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates) && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs) && mMaxMtu == rhs.mMaxMtu; } @@ -465,8 +464,8 @@ public final class VcnGatewayConnectionConfig { @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet(); @NonNull - private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities = - new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); + private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates = + new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES); @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS; private int mMaxMtu = DEFAULT_MAX_MTU; @@ -539,27 +538,37 @@ public final class VcnGatewayConnectionConfig { } /** - * Set the VcnUnderlyingNetworkTemplate list. + * Set the list of templates to match underlying networks against, in high-to-low priority + * order. + * + * <p>To select the VCN underlying network, the VCN connection will go through all the + * network candidates and return a network matching the highest priority rule. + * + * <p>If multiple networks match the same rule, the VCN will prefer an already-selected + * network as opposed to a new/unselected network. However, if both are new/unselected + * networks, a network will be chosen arbitrarily amongst the networks matching the highest + * priority rule. * - * @param underlyingNetworkPriorities a list of unique VcnUnderlyingNetworkPriorities that - * are ordered from most to least preferred, or an empty list to use the default - * prioritization. The default network prioritization is Opportunistic cellular, Carrier - * WiFi and Macro cellular - * @return + * <p>If all networks fail to match the rules provided, an underlying network will still be + * selected (at random if necessary). + * + * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are + * ordered from most to least preferred, or an empty list to use the default + * prioritization. The default network prioritization order is Opportunistic cellular, + * Carrier WiFi and then Macro cellular. + * @return this {@link Builder} instance, for chaining */ - /** @hide */ @NonNull public Builder setVcnUnderlyingNetworkPriorities( - @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities) { - Objects.requireNonNull( - mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null"); + @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) { + validateNetworkTemplateList(underlyingNetworkTemplates); - mUnderlyingNetworkPriorities.clear(); + mUnderlyingNetworkTemplates.clear(); - if (underlyingNetworkPriorities.isEmpty()) { - mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); + if (underlyingNetworkTemplates.isEmpty()) { + mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES); } else { - mUnderlyingNetworkPriorities.addAll(underlyingNetworkPriorities); + mUnderlyingNetworkTemplates.addAll(underlyingNetworkTemplates); } return this; @@ -629,7 +638,7 @@ public final class VcnGatewayConnectionConfig { mGatewayConnectionName, mTunnelConnectionParams, mExposedCapabilities, - mUnderlyingNetworkPriorities, + mUnderlyingNetworkTemplates, mRetryIntervalsMs, mMaxMtu); } diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java index d306d5cb6826..3a9ca3edded7 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java @@ -15,6 +15,8 @@ */ package android.net.vcn; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; + import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.IntDef; @@ -31,59 +33,126 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; -// TODO: Add documents -/** @hide */ +/** + * This class represents a template containing set of underlying network requirements for doing + * route selection. + * + * <p>Apps provisioning a VCN can configure the underlying network priority for each Gateway + * Connection by setting a list (in priority order, most to least preferred) of the appropriate + * subclasses in the VcnGatewayConnectionConfig. See {@link + * VcnGatewayConnectionConfig.Builder#setVcnUnderlyingNetworkPriorities} + */ public abstract class VcnUnderlyingNetworkTemplate { /** @hide */ - protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1; + static final int NETWORK_PRIORITY_TYPE_WIFI = 1; /** @hide */ - protected static final int NETWORK_PRIORITY_TYPE_CELL = 2; + static final int NETWORK_PRIORITY_TYPE_CELL = 2; - /** Denotes that any network quality is acceptable */ - public static final int NETWORK_QUALITY_ANY = 0; - /** Denotes that network quality needs to be OK */ - public static final int NETWORK_QUALITY_OK = 100000; + /** + * Used to configure the matching criteria of a network characteristic. This may include network + * capabilities, or cellular subscription information. Denotes that networks with or without the + * characteristic are both acceptable to match this template. + */ + public static final int MATCH_ANY = 0; - private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>(); + /** + * Used to configure the matching criteria of a network characteristic. This may include network + * capabilities, or cellular subscription information. Denotes that a network MUST have the + * capability in order to match this template. + */ + public static final int MATCH_REQUIRED = 1; - static { - NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_ANY, "NETWORK_QUALITY_ANY"); - NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_OK, "NETWORK_QUALITY_OK"); - } + /** + * Used to configure the matching criteria of a network characteristic. This may include network + * capabilities, or cellular subscription information. Denotes that a network MUST NOT have the + * capability in order to match this template. + */ + public static final int MATCH_FORBIDDEN = 2; /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY}) - public @interface NetworkQuality {} + @IntDef({MATCH_ANY, MATCH_REQUIRED, MATCH_FORBIDDEN}) + public @interface MatchCriteria {} + + private static final SparseArray<String> MATCH_CRITERIA_TO_STRING_MAP = new SparseArray<>(); + + static { + MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_ANY, "MATCH_ANY"); + MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_REQUIRED, "MATCH_REQUIRED"); + MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_FORBIDDEN, "MATCH_FORBIDDEN"); + } private static final String NETWORK_PRIORITY_TYPE_KEY = "mNetworkPriorityType"; private final int mNetworkPriorityType; /** @hide */ - protected static final String NETWORK_QUALITY_KEY = "mNetworkQuality"; - private final int mNetworkQuality; + static final String METERED_MATCH_KEY = "mMeteredMatchCriteria"; + + private final int mMeteredMatchCriteria; + + /** @hide */ + public static final int DEFAULT_MIN_BANDWIDTH_KBPS = 0; + + /** @hide */ + static final String MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY = "mMinEntryUpstreamBandwidthKbps"; + + private final int mMinEntryUpstreamBandwidthKbps; + + /** @hide */ + static final String MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY = "mMinExitUpstreamBandwidthKbps"; + + private final int mMinExitUpstreamBandwidthKbps; /** @hide */ - protected static final String ALLOW_METERED_KEY = "mAllowMetered"; - private final boolean mAllowMetered; + static final String MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY = + "mMinEntryDownstreamBandwidthKbps"; + + private final int mMinEntryDownstreamBandwidthKbps; /** @hide */ - protected VcnUnderlyingNetworkTemplate( - int networkPriorityType, int networkQuality, boolean allowMetered) { + static final String MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY = "mMinExitDownstreamBandwidthKbps"; + + private final int mMinExitDownstreamBandwidthKbps; + + /** @hide */ + VcnUnderlyingNetworkTemplate( + int networkPriorityType, + int meteredMatchCriteria, + int minEntryUpstreamBandwidthKbps, + int minExitUpstreamBandwidthKbps, + int minEntryDownstreamBandwidthKbps, + int minExitDownstreamBandwidthKbps) { mNetworkPriorityType = networkPriorityType; - mNetworkQuality = networkQuality; - mAllowMetered = allowMetered; + mMeteredMatchCriteria = meteredMatchCriteria; + mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps; + mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps; + mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps; + mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps; } - private static void validateNetworkQuality(int networkQuality) { + /** @hide */ + static void validateMatchCriteria(int matchCriteria, String matchingCapability) { Preconditions.checkArgument( - networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK, - "Invalid networkQuality:" + networkQuality); + MATCH_CRITERIA_TO_STRING_MAP.contains(matchCriteria), + "Invalid matching criteria: " + matchCriteria + " for " + matchingCapability); + } + + /** @hide */ + static void validateMinBandwidthKbps(int minEntryBandwidth, int minExitBandwidth) { + Preconditions.checkArgument( + minEntryBandwidth >= 0, "Invalid minEntryBandwidth, must be >= 0"); + Preconditions.checkArgument( + minExitBandwidth >= 0, "Invalid minExitBandwidth, must be >= 0"); + Preconditions.checkArgument( + minEntryBandwidth >= minExitBandwidth, + "Minimum entry bandwidth must be >= exit bandwidth"); } /** @hide */ protected void validate() { - validateNetworkQuality(mNetworkQuality); + validateMatchCriteria(mMeteredMatchCriteria, "mMeteredMatchCriteria"); + validateMinBandwidthKbps(mMinEntryUpstreamBandwidthKbps, mMinExitUpstreamBandwidthKbps); + validateMinBandwidthKbps(mMinEntryDownstreamBandwidthKbps, mMinExitDownstreamBandwidthKbps); } /** @hide */ @@ -111,15 +180,24 @@ public abstract class VcnUnderlyingNetworkTemplate { final PersistableBundle result = new PersistableBundle(); result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType); - result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality); - result.putBoolean(ALLOW_METERED_KEY, mAllowMetered); + result.putInt(METERED_MATCH_KEY, mMeteredMatchCriteria); + result.putInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, mMinEntryUpstreamBandwidthKbps); + result.putInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, mMinExitUpstreamBandwidthKbps); + result.putInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, mMinEntryDownstreamBandwidthKbps); + result.putInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, mMinExitDownstreamBandwidthKbps); return result; } @Override public int hashCode() { - return Objects.hash(mNetworkPriorityType, mNetworkQuality, mAllowMetered); + return Objects.hash( + mNetworkPriorityType, + mMeteredMatchCriteria, + mMinEntryUpstreamBandwidthKbps, + mMinExitUpstreamBandwidthKbps, + mMinEntryDownstreamBandwidthKbps, + mMinExitDownstreamBandwidthKbps); } @Override @@ -130,8 +208,21 @@ public abstract class VcnUnderlyingNetworkTemplate { final VcnUnderlyingNetworkTemplate rhs = (VcnUnderlyingNetworkTemplate) other; return mNetworkPriorityType == rhs.mNetworkPriorityType - && mNetworkQuality == rhs.mNetworkQuality - && mAllowMetered == rhs.mAllowMetered; + && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria + && mMinEntryUpstreamBandwidthKbps == rhs.mMinEntryUpstreamBandwidthKbps + && mMinExitUpstreamBandwidthKbps == rhs.mMinExitUpstreamBandwidthKbps + && mMinEntryDownstreamBandwidthKbps == rhs.mMinEntryDownstreamBandwidthKbps + && mMinExitDownstreamBandwidthKbps == rhs.mMinExitDownstreamBandwidthKbps; + } + + /** @hide */ + static String getNameString(SparseArray<String> toStringMap, int key) { + return toStringMap.get(key, "Invalid value " + key); + } + + /** @hide */ + static String getMatchCriteriaString(int matchCriteria) { + return getNameString(MATCH_CRITERIA_TO_STRING_MAP, matchCriteria); } /** @hide */ @@ -146,67 +237,63 @@ public abstract class VcnUnderlyingNetworkTemplate { pw.println(this.getClass().getSimpleName() + ":"); pw.increaseIndent(); - pw.println( - "mNetworkQuality: " - + NETWORK_QUALITY_TO_STRING_MAP.get( - mNetworkQuality, "Invalid value " + mNetworkQuality)); - pw.println("mAllowMetered: " + mAllowMetered); + pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria)); + pw.println("mMinEntryUpstreamBandwidthKbps: " + mMinEntryUpstreamBandwidthKbps); + pw.println("mMinExitUpstreamBandwidthKbps: " + mMinExitUpstreamBandwidthKbps); + pw.println("mMinEntryDownstreamBandwidthKbps: " + mMinEntryDownstreamBandwidthKbps); + pw.println("mMinExitDownstreamBandwidthKbps: " + mMinExitDownstreamBandwidthKbps); dumpTransportSpecificFields(pw); pw.decreaseIndent(); } - /** Retrieve the required network quality. */ - @NetworkQuality - public int getNetworkQuality() { - return mNetworkQuality; + /** + * Return the matching criteria for metered networks. + * + * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMetered(int) + * @see VcnCellUnderlyingNetworkTemplate.Builder#setMetered(int) + */ + public int getMetered() { + return mMeteredMatchCriteria; } - /** Return if a metered network is allowed. */ - public boolean allowMetered() { - return mAllowMetered; + /** + * Returns the minimum entry upstream bandwidth allowed by this template. + * + * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int) + * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int) + */ + public int getMinEntryUpstreamBandwidthKbps() { + return mMinEntryUpstreamBandwidthKbps; } /** - * This class is used to incrementally build VcnUnderlyingNetworkTemplate objects. + * Returns the minimum exit upstream bandwidth allowed by this template. * - * @param <T> The subclass to be built. + * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int) + * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinUpstreamBandwidthKbps(int, int) */ - public abstract static class Builder<T extends Builder<T>> { - /** @hide */ - protected int mNetworkQuality = NETWORK_QUALITY_ANY; - /** @hide */ - protected boolean mAllowMetered = false; - - /** @hide */ - protected Builder() {} - - /** - * Set the required network quality. - * - * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY - */ - @NonNull - public T setNetworkQuality(@NetworkQuality int networkQuality) { - validateNetworkQuality(networkQuality); - - mNetworkQuality = networkQuality; - return self(); - } + public int getMinExitUpstreamBandwidthKbps() { + return mMinExitUpstreamBandwidthKbps; + } - /** - * Set if a metered network is allowed. - * - * @param allowMetered the flag to indicate if a metered network is allowed, defaults to - * {@code false} - */ - @NonNull - public T setAllowMetered(boolean allowMetered) { - mAllowMetered = allowMetered; - return self(); - } + /** + * Returns the minimum entry downstream bandwidth allowed by this template. + * + * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int) + * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int) + */ + public int getMinEntryDownstreamBandwidthKbps() { + return mMinEntryDownstreamBandwidthKbps; + } - /** @hide */ - abstract T self(); + /** + * Returns the minimum exit downstream bandwidth allowed by this template. + * + * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int) + * @see VcnCellUnderlyingNetworkTemplate.Builder#setMinDownstreamBandwidthKbps(int, int) + */ + public int getMinExitDownstreamBandwidthKbps() { + return mMinExitDownstreamBandwidthKbps; } } diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java index 6bbb2bfecda4..23a07abdf0cb 100644 --- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java @@ -16,41 +16,99 @@ package android.net.vcn; import static com.android.internal.annotations.VisibleForTesting.Visibility; +import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; +import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.net.NetworkCapabilities; import android.os.PersistableBundle; +import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.vcn.util.PersistableBundleUtils; +import java.util.ArrayList; +import java.util.Collections; import java.util.Objects; +import java.util.Set; -// TODO: Add documents -/** @hide */ +/** + * This class represents a configuration for a network template class of underlying Carrier WiFi + * networks. + * + * <p>See {@link VcnUnderlyingNetworkTemplate} + */ public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate { - private static final String SSID_KEY = "mSsid"; - @Nullable private final String mSsid; + private static final String SSIDS_KEY = "mSsids"; + @Nullable private final Set<String> mSsids; private VcnWifiUnderlyingNetworkTemplate( - int networkQuality, boolean allowMetered, String ssid) { - super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered); - mSsid = ssid; + int meteredMatchCriteria, + int minEntryUpstreamBandwidthKbps, + int minExitUpstreamBandwidthKbps, + int minEntryDownstreamBandwidthKbps, + int minExitDownstreamBandwidthKbps, + Set<String> ssids) { + super( + NETWORK_PRIORITY_TYPE_WIFI, + meteredMatchCriteria, + minEntryUpstreamBandwidthKbps, + minExitUpstreamBandwidthKbps, + minEntryDownstreamBandwidthKbps, + minExitDownstreamBandwidthKbps); + mSsids = new ArraySet<>(ssids); validate(); } /** @hide */ + @Override + protected void validate() { + super.validate(); + validateSsids(mSsids); + } + + private static void validateSsids(Set<String> ssids) { + Objects.requireNonNull(ssids, "ssids is null"); + + for (String ssid : ssids) { + Objects.requireNonNull(ssid, "found null value ssid"); + } + } + + /** @hide */ @NonNull @VisibleForTesting(visibility = Visibility.PROTECTED) public static VcnWifiUnderlyingNetworkTemplate fromPersistableBundle( @NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); - final int networkQuality = in.getInt(NETWORK_QUALITY_KEY); - final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY); - final String ssid = in.getString(SSID_KEY); - return new VcnWifiUnderlyingNetworkTemplate(networkQuality, allowMetered, ssid); + final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY); + + final int minEntryUpstreamBandwidthKbps = + in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS); + final int minExitUpstreamBandwidthKbps = + in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS); + final int minEntryDownstreamBandwidthKbps = + in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS); + final int minExitDownstreamBandwidthKbps = + in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS); + + final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY); + Objects.requireNonNull(ssidsBundle, "ssidsBundle is null"); + final Set<String> ssids = + new ArraySet<String>( + PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER)); + return new VcnWifiUnderlyingNetworkTemplate( + meteredMatchCriteria, + minEntryUpstreamBandwidthKbps, + minExitUpstreamBandwidthKbps, + minEntryDownstreamBandwidthKbps, + minExitDownstreamBandwidthKbps, + ssids); } /** @hide */ @@ -59,13 +117,17 @@ public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetwork @VisibleForTesting(visibility = Visibility.PROTECTED) public PersistableBundle toPersistableBundle() { final PersistableBundle result = super.toPersistableBundle(); - result.putString(SSID_KEY, mSsid); + + final PersistableBundle ssidsBundle = + PersistableBundleUtils.fromList(new ArrayList<>(mSsids), STRING_SERIALIZER); + result.putPersistableBundle(SSIDS_KEY, ssidsBundle); + return result; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), mSsid); + return Objects.hash(super.hashCode(), mSsids); } @Override @@ -79,49 +141,162 @@ public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetwork } final VcnWifiUnderlyingNetworkTemplate rhs = (VcnWifiUnderlyingNetworkTemplate) other; - return mSsid.equals(rhs.mSsid); + return mSsids.equals(rhs.mSsids); } /** @hide */ @Override void dumpTransportSpecificFields(IndentingPrintWriter pw) { - pw.println("mSsid: " + mSsid); + pw.println("mSsids: " + mSsids); } - /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */ - @Nullable - public String getSsid() { - return mSsid; + /** + * Retrieve the matching SSIDs, or an empty set if any SSID is acceptable. + * + * @see Builder#setSsids(Set) + */ + @NonNull + public Set<String> getSsids() { + return Collections.unmodifiableSet(mSsids); } /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */ - public static class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> { - @Nullable private String mSsid; + public static final class Builder { + private int mMeteredMatchCriteria = MATCH_ANY; + @NonNull private final Set<String> mSsids = new ArraySet<>(); + + private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS; + private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS; + private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS; + private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS; /** Construct a Builder object. */ public Builder() {} /** - * Set the required SSID. + * Set the matching criteria for metered networks. + * + * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one + * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will + * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED). * - * @param ssid the required SSID, or {@code null} if any SSID is acceptable. + * @param matchCriteria the matching criteria for metered networks. Defaults to {@link + * #MATCH_ANY}. + * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED */ + // The matching getter is defined in the super class. Please see {@link + // VcnUnderlyingNetworkTemplate#getMetered()} + @SuppressLint("MissingGetterMatchingBuilder") @NonNull - public Builder setSsid(@Nullable String ssid) { - mSsid = ssid; + public Builder setMetered(@MatchCriteria int matchCriteria) { + validateMatchCriteria(matchCriteria, "setMetered"); + + mMeteredMatchCriteria = matchCriteria; return this; } - /** Build the VcnWifiUnderlyingNetworkTemplate. */ + /** + * Set the SSIDs with which a network can match this priority rule. + * + * @param ssids the matching SSIDs. Network with one of the matching SSIDs can match this + * priority rule. If the set is empty, any SSID will match. The default is an empty set. + */ @NonNull - public VcnWifiUnderlyingNetworkTemplate build() { - return new VcnWifiUnderlyingNetworkTemplate(mNetworkQuality, mAllowMetered, mSsid); + public Builder setSsids(@NonNull Set<String> ssids) { + validateSsids(ssids); + + mSsids.clear(); + mSsids.addAll(ssids); + return this; } - /** @hide */ - @Override - Builder self() { + /** + * Set the minimum upstream bandwidths that this template will match. + * + * <p>This template will not match a network that does not provide at least the bandwidth + * passed as the entry bandwidth, except in the case that the network is selected as the VCN + * Gateway Connection's underlying network, where it will continue to match until the + * bandwidth drops under the exit bandwidth. + * + * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the + * invalid case where a network fulfills the entry criteria, but at the same time fails the + * exit criteria. + * + * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in + * {@link NetworkCapabilities}. The provided estimates will be used without modification. + * + * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks + * that ARE NOT the already-selected underlying network, or {@code 0} to disable this + * requirement. Disabled by default. + * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network + * that IS the already-selected underlying network, or {@code 0} to disable this + * requirement. Disabled by default. + * @return this {@link Builder} instance, for chaining + */ + @NonNull + // The getter for the two integers are separated, and in the superclass. Please see {@link + // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link + // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()} + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setMinUpstreamBandwidthKbps( + int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) { + validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps); + + mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps; + mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps; + return this; } + + /** + * Set the minimum upstream bandwidths that this template will match. + * + * <p>This template will not match a network that does not provide at least the bandwidth + * passed as the entry bandwidth, except in the case that the network is selected as the VCN + * Gateway Connection's underlying network, where it will continue to match until the + * bandwidth drops under the exit bandwidth. + * + * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the + * invalid case where a network fulfills the entry criteria, but at the same time fails the + * exit criteria. + * + * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in + * {@link NetworkCapabilities}. The provided estimates will be used without modification. + * + * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for + * networks that ARE NOT the already-selected underlying network, or {@code 0} to + * disable this requirement. Disabled by default. + * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a + * network that IS the already-selected underlying network, or {@code 0} to disable this + * requirement. Disabled by default. + * @return this {@link Builder} instance, for chaining + */ + @NonNull + // The getter for the two integers are separated, and in the superclass. Please see {@link + // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link + // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()} + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setMinDownstreamBandwidthKbps( + int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) { + validateMinBandwidthKbps( + minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps); + + mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps; + mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps; + + return this; + } + + /** Build the VcnWifiUnderlyingNetworkTemplate. */ + @NonNull + public VcnWifiUnderlyingNetworkTemplate build() { + return new VcnWifiUnderlyingNetworkTemplate( + mMeteredMatchCriteria, + mMinEntryUpstreamBandwidthKbps, + mMinExitUpstreamBandwidthKbps, + mMinEntryDownstreamBandwidthKbps, + mMinExitDownstreamBandwidthKbps, + mSsids); + } } } diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index c6071959f438..47a272c2337f 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -191,6 +191,8 @@ public abstract class BatteryConsumer { private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = { POWER_COMPONENT_CPU, POWER_COMPONENT_MOBILE_RADIO, + POWER_COMPONENT_WIFI, + POWER_COMPONENT_BLUETOOTH, }; static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 520730fa7352..4666c5c12189 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -461,6 +461,12 @@ public abstract class BatteryStats implements Parcelable { public abstract long getCountLocked(int which); /** + * Returns the count accumulated by this Counter for the specified process state. + * If the counter does not support per-procstate tracking, returns 0. + */ + public abstract long getCountForProcessState(@BatteryConsumer.ProcessState int procState); + + /** * Temporary for debugging. */ public abstract void logState(Printer pw, String prefix); @@ -1031,6 +1037,16 @@ public abstract class BatteryStats implements Parcelable { public abstract long getBluetoothMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the uid's bluetooth usage + * when in the specified process state. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getBluetoothMeasuredBatteryConsumptionUC( + @BatteryConsumer.ProcessState int processState); + + /** * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from * on device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. @@ -1096,6 +1112,17 @@ public abstract class BatteryStats implements Parcelable { public abstract long getWifiMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the uid's wifi usage when in the + * specified process state. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getWifiMeasuredBatteryConsumptionUC( + @BatteryConsumer.ProcessState int processState); + + + /** * Returns the battery consumption (in microcoulombs) used by this uid for each * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 429450ce8e5e..80cf2f87f1d6 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -311,6 +311,7 @@ public class Binder implements IBinder { private final long mObject; private IInterface mOwner; + @Nullable private String mDescriptor; private volatile String[] mTransactionTraceNames = null; private volatile String mSimpleDescriptor = null; @@ -930,8 +931,8 @@ public class Binder implements IBinder { transactionNames[i] = buf.toString(); buf.setLength(0); } - mTransactionTraceNames = transactionNames; mSimpleDescriptor = descriptor; + mTransactionTraceNames = transactionNames; } final int index = transactionCode - FIRST_CALL_TRANSACTION; if (index < 0 || index >= mTransactionTraceNames.length) { @@ -940,13 +941,19 @@ public class Binder implements IBinder { return mTransactionTraceNames[index]; } - private String getSimpleDescriptor() { - final int dot = mDescriptor.lastIndexOf("."); + private @NonNull String getSimpleDescriptor() { + String descriptor = mDescriptor; + if (descriptor == null) { + // Just "Binder" to avoid null checks in transaction name tracing. + return "Binder"; + } + + final int dot = descriptor.lastIndexOf("."); if (dot > 0) { // Strip the package name - return mDescriptor.substring(dot + 1); + return descriptor.substring(dot + 1); } - return mDescriptor; + return descriptor; } /** diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 3f4216406b54..fe86874da3da 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -90,7 +90,7 @@ interface IUserManager { Bundle getApplicationRestrictionsForUser(in String packageName, int userId); void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); - int removeUserOrSetEphemeral(int userId, boolean evenWhenDisallowed); + int removeUserWhenPossible(int userId, boolean overrideDevicePolicy); boolean markGuestForDeletion(int userId); UserInfo findCurrentGuestUser(); boolean isQuietModeEnabled(int userId); diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java index cfa823cffe86..b74bb333deae 100644 --- a/core/java/android/os/LocaleList.java +++ b/core/java/android/os/LocaleList.java @@ -20,6 +20,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.icu.util.ULocale; @@ -312,18 +313,30 @@ public final class LocaleList implements Parcelable { return isPseudoLocale(locale != null ? locale.toLocale() : null); } - @IntRange(from=0, to=1) - private static int matchScore(Locale supported, Locale desired) { + /** + * Determine whether two locales are considered a match, even if they are not exactly equal. + * They are considered as a match when both of their languages and scripts + * (explicit or inferred) are identical. This means that a user would be able to understand + * the content written in the supported locale even if they say they prefer the desired locale. + * + * E.g. [zh-HK] matches [zh-Hant]; [en-US] matches [en-CA] + * + * @param supported The supported {@link Locale} to be compared. + * @param desired The desired {@link Locale} to be compared. + * @return True if they match, false otherwise. + */ + public static boolean matchesLanguageAndScript(@SuppressLint("UseIcu") @NonNull + Locale supported, @SuppressLint("UseIcu") @NonNull Locale desired) { if (supported.equals(desired)) { - return 1; // return early so we don't do unnecessary computation + return true; // return early so we don't do unnecessary computation } if (!supported.getLanguage().equals(desired.getLanguage())) { - return 0; + return false; } if (isPseudoLocale(supported) || isPseudoLocale(desired)) { // The locales are not the same, but the languages are the same, and one of the locales // is a pseudo-locale. So this is not a match. - return 0; + return false; } final String supportedScr = getLikelyScript(supported); if (supportedScr.isEmpty()) { @@ -331,20 +344,17 @@ public final class LocaleList implements Parcelable { // if the locales match. So we fall back to old behavior of matching, which considered // locales with different regions different. final String supportedRegion = supported.getCountry(); - return (supportedRegion.isEmpty() || - supportedRegion.equals(desired.getCountry())) - ? 1 : 0; + return supportedRegion.isEmpty() || supportedRegion.equals(desired.getCountry()); } final String desiredScr = getLikelyScript(desired); // There is no match if the two locales use different scripts. This will most imporantly // take care of traditional vs simplified Chinese. - return supportedScr.equals(desiredScr) ? 1 : 0; + return supportedScr.equals(desiredScr); } private int findFirstMatchIndex(Locale supportedLocale) { for (int idx = 0; idx < mList.length; idx++) { - final int score = matchScore(supportedLocale, mList[idx]); - if (score > 0) { + if (matchesLanguageAndScript(supportedLocale, mList[idx])) { return idx; } } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 742a542efdd1..a63f68a343d2 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -132,6 +132,7 @@ public class Process { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @TestApi + @SystemApi(client = MODULE_LIBRARIES) public static final int NFC_UID = 1027; /** diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index edf628004728..190f5f127f3d 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4750,7 +4750,7 @@ public class UserManager { public int removeUserWhenPossible(@NonNull UserHandle user, boolean overrideDevicePolicy) { try { - return mService.removeUserOrSetEphemeral(user.getIdentifier(), overrideDevicePolicy); + return mService.removeUserWhenPossible(user.getIdentifier(), overrideDevicePolicy); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -4777,7 +4777,7 @@ public class UserManager { public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId, boolean evenWhenDisallowed) { try { - return mService.removeUserOrSetEphemeral(userId, evenWhenDisallowed); + return mService.removeUserWhenPossible(userId, evenWhenDisallowed); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 8834725cc3e9..8bc219b7dc57 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -136,6 +136,7 @@ public final class VibrationAttributes implements Parcelable { */ @IntDef(prefix = { "FLAG_" }, flag = true, value = { FLAG_BYPASS_INTERRUPTION_POLICY, + FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF }) @Retention(RetentionPolicy.SOURCE) public @interface Flag{} @@ -146,10 +147,22 @@ public final class VibrationAttributes implements Parcelable { public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 0x1; /** + * Flag requesting vibration effect to be played even when user settings are disabling it. + * + * <p>Flag introduced to represent + * {@link android.view.HapticFeedbackConstants#FLAG_IGNORE_GLOBAL_SETTING} and + * {@link AudioAttributes#FLAG_BYPASS_MUTE}. + * + * @hide + */ + public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 0x2; + + /** * All flags supported by vibrator service, update it when adding new flag. * @hide */ - public static final int FLAG_ALL_SUPPORTED = FLAG_BYPASS_INTERRUPTION_POLICY; + public static final int FLAG_ALL_SUPPORTED = + FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; /** Creates a new {@link VibrationAttributes} instance with given usage. */ public static @NonNull VibrationAttributes createForUsage(int usage) { @@ -397,6 +410,11 @@ public final class VibrationAttributes implements Parcelable { if ((audio.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) { mFlags |= FLAG_BYPASS_INTERRUPTION_POLICY; } + if ((audio.getAllFlags() & AudioAttributes.FLAG_BYPASS_MUTE) != 0) { + // Muted audio stream translates to vibration usage having the value + // Vibrator.VIBRATION_INTENSITY_OFF set in the user setting. + mFlags |= FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; + } } /** diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 0e32a78411dd..5814bac06f59 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -56,4 +56,6 @@ oneway interface IPermissionController { in AndroidFuture<String> callback); void getUnusedAppCount( in AndroidFuture callback); + void selfRevokePermissions(in String packageName, in List<String> permissions, + in AndroidFuture callback); } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 90b5e518ef1b..8e5581b1b80e 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -76,6 +76,8 @@ interface IPermissionManager { List<SplitPermissionInfoParcelable> getSplitPermissions(); + void selfRevokePermissions(String packageName, in List<String> permissions); + void startOneTimePermissionSession(String packageName, int userId, long timeout, int importanceToResetTimer, int importanceToKeepSessionAlive); diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index a0788e70b247..47cd10765da0 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -817,4 +817,40 @@ public final class PermissionControllerManager { } }); } + + /** + * Triggers the revocation of one or more permissions for a package, under the following + * conditions: + * <ul> + * <li>The package {@code packageName} must be under the same UID as the calling process + * (typically, the target package is the calling package). + * <li>Each permission in {@code permissions} must be granted to the package + * {@code packageName}. + * <li>Each permission in {@code permissions} must be a runtime permission. + * </ul> + * <p> + * For every permission in {@code permissions}, the entire permission group it belongs to will + * be revoked. This revocation happens asynchronously and kills all processes running in the + * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * + * @param packageName The name of the package for which the permissions will be revoked. + * @param permissions List of permissions to be revoked. + * + * @see Context#selfRevokePermissions(Collection) + * + * @hide + */ + public void selfRevokePermissions(@NonNull String packageName, + @NonNull List<String> permissions) { + mRemoteService.postAsync(service -> { + AndroidFuture<Void> future = new AndroidFuture<>(); + service.selfRevokePermissions(packageName, permissions, future); + return future; + }).whenComplete((result, err) -> { + if (err != null) { + Log.e(TAG, "Failed to self revoke " + String.join(",", permissions) + + " for package " + packageName, err); + } + }); + } } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index c9793032c7eb..dcbab62530b1 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -324,6 +324,27 @@ public abstract class PermissionControllerService extends Service { @NonNull Consumer<String> callback) { throw new AbstractMethodError("Must be overridden in implementing class"); } + + /** + * Triggers the revocation of one or more permissions for a package. This should only be called + * at the request of {@code packageName}. + * <p> + * For every permission in {@code permissions}, the entire permission group it belongs to will + * be revoked. This revocation happens asynchronously and kills all processes running in the + * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * + * @param packageName The name of the package for which the permissions will be revoked. + * @param permissions List of permissions to be revoked. + * @param callback Callback waiting for operation to be complete. + * + * @see PermissionManager#selfRevokePermissions(java.util.Collection) + */ + @BinderThread + public void onSelfRevokePermissions(@NonNull String packageName, + @NonNull List<String> permissions, @NonNull Runnable callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + /** * Get a user-readable sentence, describing the set of privileges that are to be granted to a * companion app managing a device of the given profile. @@ -646,6 +667,20 @@ public abstract class PermissionControllerService extends Service { callback.completeExceptionally(t); } } + + @Override + public void selfRevokePermissions(@NonNull String packageName, + @NonNull List<String> permissions, @NonNull AndroidFuture callback) { + try { + enforceSomePermissionsGrantedToCaller( + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); + Objects.requireNonNull(callback); + onSelfRevokePermissions(packageName, permissions, + () -> callback.complete(null)); + } catch (Throwable t) { + callback.completeExceptionally(t); + } + } }; } } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 4f2287606e05..13941dc5ef82 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -68,6 +68,7 @@ import com.android.internal.annotations.Immutable; import com.android.internal.util.CollectionUtils; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -561,6 +562,19 @@ public final class PermissionManager { } /** + * @see Context#selfRevokePermissions(Collection) + * @hide + */ + public void selfRevokePermissions(@NonNull Collection<String> permissions) { + try { + mPermissionManager.selfRevokePermissions(mContext.getPackageName(), + new ArrayList<String>(permissions)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Gets the state flags associated with a permission. * * @param packageName the package name for which to get the flags diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 72e28630da40..e568370f1728 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5120,7 +5120,12 @@ public final class Settings { * It was about AudioManager's setting and thus affected all the applications which * relied on the setting, while this is purely about the vibration setting for incoming * calls. + * + * @deprecated Replaced by using {@link android.os.VibrationAttributes#USAGE_RINGTONE} on + * vibrations for incoming calls. User settings are applied automatically by the service and + * should not be applied by individual apps. */ + @Deprecated @Readable public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; @@ -5181,7 +5186,12 @@ public final class Settings { /** * Whether haptic feedback (Vibrate on tap) is enabled. The value is * boolean (1 or 0). + * + * @deprecated Replaced by using {@link android.os.VibrationAttributes#USAGE_TOUCH} on + * vibrations. User settings are applied automatically by the service and should not be + * applied by individual apps. */ + @Deprecated @Readable public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; @@ -7158,6 +7168,12 @@ public final class Settings { public static final String LOCATION_COARSE_ACCURACY_M = "locationCoarseAccuracy"; /** + * Whether or not to show display system location accesses. + * @hide + */ + public static final String LOCATION_SHOW_SYSTEM_OPS = "locationShowSystemOps"; + + /** * A flag containing settings used for biometric weak * @hide */ @@ -8974,6 +8990,15 @@ public final class Settings { public static final String UI_NIGHT_MODE = "ui_night_mode"; /** + * The current night mode custom type that has been selected by the user. Owned + * and controlled by UiModeManagerService. Constants are as per UiModeManager. + * @hide + */ + @Readable + @SuppressLint("NoSettingsProvider") + public static final String UI_NIGHT_MODE_CUSTOM_TYPE = "ui_night_mode_custom_type"; + + /** * The current night mode that has been overridden to turn on by the system. Owned * and controlled by UiModeManagerService. Constants are as per * UiModeManager. @@ -10525,7 +10550,7 @@ public final class Settings { DEVICE_STATE_ROTATION_LOCK_UNLOCKED, }) @Retention(RetentionPolicy.SOURCE) - @interface DeviceStateRotationLockSetting { + public @interface DeviceStateRotationLockSetting { } /** diff --git a/core/java/android/service/games/CreateGameSessionResult.aidl b/core/java/android/service/games/CreateGameSessionResult.aidl new file mode 100644 index 000000000000..b7c5e1602891 --- /dev/null +++ b/core/java/android/service/games/CreateGameSessionResult.aidl @@ -0,0 +1,23 @@ +/* + * 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 android.service.games; + + +/** + * @hide + */ +parcelable CreateGameSessionResult; diff --git a/core/java/android/service/games/CreateGameSessionResult.java b/core/java/android/service/games/CreateGameSessionResult.java new file mode 100644 index 000000000000..8448b0f433b2 --- /dev/null +++ b/core/java/android/service/games/CreateGameSessionResult.java @@ -0,0 +1,84 @@ +/* + * 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 android.service.games; + + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControlViewHost; + +/** + * Internal result object that contains the successful creation of a game session. + * + * @see IGameSessionService#create(CreateGameSessionRequest, GameSessionViewHostConfiguration, + * com.android.internal.infra.AndroidFuture) + * @hide + */ +@Hide +public final class CreateGameSessionResult implements Parcelable { + + @NonNull + public static final Parcelable.Creator<CreateGameSessionResult> CREATOR = + new Parcelable.Creator<CreateGameSessionResult>() { + @Override + public CreateGameSessionResult createFromParcel(Parcel source) { + return new CreateGameSessionResult( + IGameSession.Stub.asInterface(source.readStrongBinder()), + source.readParcelable( + SurfaceControlViewHost.SurfacePackage.class.getClassLoader(), + SurfaceControlViewHost.SurfacePackage.class)); + } + + @Override + public CreateGameSessionResult[] newArray(int size) { + return new CreateGameSessionResult[0]; + } + }; + + private final IGameSession mGameSession; + private final SurfaceControlViewHost.SurfacePackage mSurfacePackage; + + public CreateGameSessionResult( + @NonNull IGameSession gameSession, + @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { + mGameSession = gameSession; + mSurfacePackage = surfacePackage; + } + + @NonNull + public IGameSession getGameSession() { + return mGameSession; + } + + @NonNull + public SurfaceControlViewHost.SurfacePackage getSurfacePackage() { + return mSurfacePackage; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mGameSession.asBinder()); + dest.writeParcelable(mSurfacePackage, flags); + } +} diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java index b79c0553460f..870a7e3f2646 100644 --- a/core/java/android/service/games/GameService.java +++ b/core/java/android/service/games/GameService.java @@ -16,6 +16,8 @@ package android.service.games; +import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; @@ -38,10 +40,29 @@ import java.util.Objects; * when a game session should begin. It is always kept running by the system. * Because of this it should be kept as lightweight as possible. * - * Heavy weight operations (such as showing UI) should be implemented in the + * <p> Instead of requiring permissions for sensitive actions (e.g., starting a new game session), + * this class is provided with an {@link IGameServiceController} instance which exposes the + * sensitive functionality. This controller is provided by the system server when calling the + * {@link IGameService#connected(IGameServiceController)} method exposed by this class. The system + * server does so only when creating the bound game service. + * + * <p>Heavyweight operations (such as showing UI) should be implemented in the * associated {@link GameSessionService} when a game session is taking place. Its * implementation should run in a separate process from the {@link GameService}. * + * <p>To extend this class, you must declare the service in your manifest file with + * the {@link android.Manifest.permission#BIND_GAME_SERVICE} permission + * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example: + * <pre> + * <service android:name=".GameService" + * android:label="@string/service_name" + * android:permission="android.permission.BIND_GAME_SERVICE"> + * <intent-filter> + * <action android:name="android.service.games.GameService" /> + * </intent-filter> + * </service> + * </pre> + * * @hide */ @SystemApi @@ -66,12 +87,13 @@ public class GameService extends Service { */ public static final String SERVICE_META_DATA = "android.game_service"; + private IGameServiceController mGameServiceController; private IGameManagerService mGameManagerService; private final IGameService mInterface = new IGameService.Stub() { @Override - public void connected() { + public void connected(IGameServiceController gameServiceController) { Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( - GameService::doOnConnected, GameService.this)); + GameService::doOnConnected, GameService.this, gameServiceController)); } @Override @@ -79,6 +101,12 @@ public class GameService extends Service { Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( GameService::onDisconnected, GameService.this)); } + + @Override + public void gameStarted(GameStartedEvent gameStartedEvent) { + Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( + GameService::onGameStarted, GameService.this, gameStartedEvent)); + } }; private final IBinder.DeathRecipient mGameManagerServiceDeathRecipient = () -> { @@ -90,7 +118,7 @@ public class GameService extends Service { @Override @Nullable - public IBinder onBind(@Nullable Intent intent) { + public final IBinder onBind(@Nullable Intent intent) { if (ACTION_GAME_SERVICE.equals(intent.getAction())) { return mInterface.asBinder(); } @@ -98,7 +126,7 @@ public class GameService extends Service { return null; } - private void doOnConnected() { + private void doOnConnected(@NonNull IGameServiceController gameServiceController) { mGameManagerService = IGameManagerService.Stub.asInterface( ServiceManager.getService(Context.GAME_SERVICE)); @@ -109,6 +137,7 @@ public class GameService extends Service { Log.w(TAG, "Unable to link to death with system service"); } + mGameServiceController = gameServiceController; onConnected(); } @@ -125,4 +154,34 @@ public class GameService extends Service { * The service should clean up any resources that it holds at this point. */ public void onDisconnected() {} + + /** + * Called when a game task is started. It is the responsibility of the service to determine what + * action to take (e.g., request that a game session be created). + * + * @param gameStartedEvent Contains information about the game being started. + */ + public void onGameStarted(@NonNull GameStartedEvent gameStartedEvent) {} + + /** + * Call to create a new game session be created for a game. This method may be called + * by a game service following {@link #onGameStarted}, using the task ID provided by the + * provided {@link GameStartedEvent} (using {@link GameStartedEvent#getTaskId}). + * + * If a game session already exists for the game task, this call will be ignored and the + * existing session will continue. + * + * @param taskId The taskId of the game. + */ + public final void createGameSession(@IntRange(from = 0) int taskId) { + if (mGameServiceController == null) { + throw new IllegalStateException("Can not call before connected()"); + } + + try { + mGameServiceController.createGameSession(taskId); + } catch (RemoteException e) { + Log.e(TAG, "Request for game session failed", e); + } + } } diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java index 0ff08c08932b..1a5331f10525 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -16,8 +16,17 @@ package android.service.games; +import android.annotation.Hide; +import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Handler; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; import com.android.internal.util.function.pooled.PooledLambda; @@ -41,12 +50,29 @@ public abstract class GameSession { } }; + private GameSessionRootView mGameSessionRootView; + private SurfaceControlViewHost mSurfaceControlViewHost; + + @Hide + void attach( + @NonNull Context context, + @NonNull SurfaceControlViewHost surfaceControlViewHost, + int widthPx, + int heightPx) { + mSurfaceControlViewHost = surfaceControlViewHost; + mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost); + surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx); + } + + @Hide void doCreate() { onCreate(); } + @Hide void doDestroy() { onDestroy(); + mSurfaceControlViewHost.release(); } /** @@ -54,12 +80,57 @@ public abstract class GameSession { * * This should be used perform any setup required now that the game session is created. */ - public void onCreate() {} + public void onCreate() { + } /** * Finalizer called when the game session is ending. * * This should be used to perform any cleanup before the game session is destroyed. */ - public void onDestroy() {} + public void onDestroy() { + } + + + /** + * Sets the task overlay content to an explicit view. This view is placed directly into the game + * session's task overlay view hierarchy. It can itself be a complex view hierarchy. The size + * the task overlay view will always match the dimensions of the associated task's window. The + * {@code View} may not be cleared once set, but may be replaced by invoking + * {@link #setTaskOverlayView(View, ViewGroup.LayoutParams)} again. + * + * @param view The desired content to display. + * @param layoutParams Layout parameters for the view. + */ + public void setTaskOverlayView( + @NonNull View view, + @NonNull ViewGroup.LayoutParams layoutParams) { + mGameSessionRootView.removeAllViews(); + mGameSessionRootView.addView(view, layoutParams); + } + + /** + * Root view of the {@link SurfaceControlViewHost} associated with the {@link GameSession} + * instance. It is responsible for observing changes in the size of the window and resizing + * itself to match. + */ + private static final class GameSessionRootView extends FrameLayout { + private final SurfaceControlViewHost mSurfaceControlViewHost; + + GameSessionRootView(@NonNull Context context, + SurfaceControlViewHost surfaceControlViewHost) { + super(context); + mSurfaceControlViewHost = surfaceControlViewHost; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // TODO(b/204504596): Investigate skipping the relayout in cases where the size has + // not changed. + Rect bounds = newConfig.windowConfiguration.getBounds(); + mSurfaceControlViewHost.relayout(bounds.width(), bounds.height()); + } + } } diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java index a2c88709b62d..195a0f233307 100644 --- a/core/java/android/service/games/GameSessionService.java +++ b/core/java/android/service/games/GameSessionService.java @@ -22,8 +22,12 @@ import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; +import android.hardware.display.DisplayManager; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.view.Display; +import android.view.SurfaceControlViewHost; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; @@ -62,18 +66,29 @@ public abstract class GameSessionService extends Service { private final IGameSessionService mInterface = new IGameSessionService.Stub() { @Override - public void create(CreateGameSessionRequest createGameSessionRequest, + public void create( + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, AndroidFuture gameSessionFuture) { Handler.getMain().post(PooledLambda.obtainRunnable( GameSessionService::doCreate, GameSessionService.this, createGameSessionRequest, + gameSessionViewHostConfiguration, gameSessionFuture)); } }; + private DisplayManager mDisplayManager; + + @Override + public void onCreate() { + super.onCreate(); + mDisplayManager = this.getSystemService(DisplayManager.class); + } + @Override @Nullable - public IBinder onBind(@Nullable Intent intent) { + public final IBinder onBind(@Nullable Intent intent) { if (intent == null) { return null; } @@ -85,12 +100,36 @@ public abstract class GameSessionService extends Service { return mInterface.asBinder(); } - private void doCreate(CreateGameSessionRequest createGameSessionRequest, - AndroidFuture<IBinder> gameSessionFuture) { + private void doCreate( + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture) { GameSession gameSession = onNewSession(createGameSessionRequest); Objects.requireNonNull(gameSession); - gameSessionFuture.complete(gameSession.mInterface.asBinder()); + Display display = mDisplayManager.getDisplay(gameSessionViewHostConfiguration.mDisplayId); + if (display == null) { + createGameSessionResultFuture.completeExceptionally( + new IllegalStateException("No display found for id: " + + gameSessionViewHostConfiguration.mDisplayId)); + return; + } + + IBinder hostToken = new Binder(); + SurfaceControlViewHost surfaceControlViewHost = + new SurfaceControlViewHost(this, display, hostToken); + + gameSession.attach(this, + surfaceControlViewHost, + gameSessionViewHostConfiguration.mWidthPx, + gameSessionViewHostConfiguration.mHeightPx); + + CreateGameSessionResult createGameSessionResult = + new CreateGameSessionResult(gameSession.mInterface, + surfaceControlViewHost.getSurfacePackage()); + + createGameSessionResultFuture.complete(createGameSessionResult); + gameSession.doCreate(); } diff --git a/core/java/android/service/games/GameSessionViewHostConfiguration.aidl b/core/java/android/service/games/GameSessionViewHostConfiguration.aidl new file mode 100644 index 000000000000..b900b9d09b07 --- /dev/null +++ b/core/java/android/service/games/GameSessionViewHostConfiguration.aidl @@ -0,0 +1,22 @@ +/* + * 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 android.service.games; + +/** + * @hide + */ +parcelable GameSessionViewHostConfiguration; diff --git a/core/java/android/service/games/GameSessionViewHostConfiguration.java b/core/java/android/service/games/GameSessionViewHostConfiguration.java new file mode 100644 index 000000000000..53db0dfae8b2 --- /dev/null +++ b/core/java/android/service/games/GameSessionViewHostConfiguration.java @@ -0,0 +1,96 @@ +/* + * 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 android.service.games; + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Represents the configuration of the {@link android.view.SurfaceControlViewHost} used to render + * the overlay for a game session. + * + * @hide + */ +@Hide +public final class GameSessionViewHostConfiguration implements Parcelable { + + @NonNull + public static final Creator<GameSessionViewHostConfiguration> CREATOR = + new Creator<GameSessionViewHostConfiguration>() { + @Override + public GameSessionViewHostConfiguration createFromParcel(Parcel source) { + return new GameSessionViewHostConfiguration( + source.readInt(), + source.readInt(), + source.readInt()); + } + + @Override + public GameSessionViewHostConfiguration[] newArray(int size) { + return new GameSessionViewHostConfiguration[0]; + } + }; + + final int mDisplayId; + final int mWidthPx; + final int mHeightPx; + + public GameSessionViewHostConfiguration(int displayId, int widthPx, int heightPx) { + this.mDisplayId = displayId; + this.mWidthPx = widthPx; + this.mHeightPx = heightPx; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDisplayId); + dest.writeInt(mWidthPx); + dest.writeInt(mHeightPx); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GameSessionViewHostConfiguration)) return false; + GameSessionViewHostConfiguration that = (GameSessionViewHostConfiguration) o; + return mDisplayId == that.mDisplayId && mWidthPx == that.mWidthPx + && mHeightPx == that.mHeightPx; + } + + @Override + public int hashCode() { + return Objects.hash(mDisplayId, mWidthPx, mHeightPx); + } + + @Override + public String toString() { + return "GameSessionViewHostConfiguration{" + + "mDisplayId=" + mDisplayId + + ", mWidthPx=" + mWidthPx + + ", mHeightPx=" + mHeightPx + + '}'; + } +} diff --git a/core/java/android/service/games/GameStartedEvent.aidl b/core/java/android/service/games/GameStartedEvent.aidl new file mode 100644 index 000000000000..8a5a4a16abab --- /dev/null +++ b/core/java/android/service/games/GameStartedEvent.aidl @@ -0,0 +1,23 @@ +/* + * 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 android.service.games; + + +/** + * @hide + */ +parcelable GameStartedEvent;
\ No newline at end of file diff --git a/core/java/android/service/games/GameStartedEvent.java b/core/java/android/service/games/GameStartedEvent.java new file mode 100644 index 000000000000..bf292606fbf6 --- /dev/null +++ b/core/java/android/service/games/GameStartedEvent.java @@ -0,0 +1,119 @@ +/* + * 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 android.service.games; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Event object provided when a game task is started. + * + * This is provided to the Game Service via + * {@link GameService#onGameStarted(GameStartedEvent)}. It includes the game's taskId + * (see {@link #getTaskId}) that the game's package name (see {@link #getPackageName}). + * + * @hide + */ +@SystemApi +public final class GameStartedEvent implements Parcelable { + + @NonNull + public static final Parcelable.Creator<GameStartedEvent> CREATOR = + new Parcelable.Creator<GameStartedEvent>() { + @Override + public GameStartedEvent createFromParcel(Parcel source) { + return new GameStartedEvent( + source.readInt(), + source.readString()); + } + + @Override + public GameStartedEvent[] newArray(int size) { + return new GameStartedEvent[0]; + } + }; + + private final int mTaskId; + private final String mPackageName; + + public GameStartedEvent(@IntRange(from = 0) int taskId, @NonNull String packageName) { + this.mTaskId = taskId; + this.mPackageName = packageName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mTaskId); + dest.writeString(mPackageName); + } + + /** + * Unique identifier for the task associated with the game. + */ + @IntRange(from = 0) + public int getTaskId() { + return mTaskId; + } + + /** + * The package name for the game. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + @Override + public String toString() { + return "GameStartedEvent{" + + "mTaskId=" + + mTaskId + + ", mPackageName='" + + mPackageName + + "\'}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameStartedEvent)) { + return false; + } + + GameStartedEvent that = (GameStartedEvent) o; + return mTaskId == that.mTaskId + && Objects.equals(mPackageName, that.mPackageName); + } + + @Override + public int hashCode() { + return Objects.hash(mTaskId, mPackageName); + } +} diff --git a/core/java/android/service/games/IGameService.aidl b/core/java/android/service/games/IGameService.aidl index 8a0d6365977b..38c8416117e0 100644 --- a/core/java/android/service/games/IGameService.aidl +++ b/core/java/android/service/games/IGameService.aidl @@ -16,10 +16,14 @@ package android.service.games; +import android.service.games.GameStartedEvent; +import android.service.games.IGameServiceController; + /** * @hide */ oneway interface IGameService { - void connected(); + void connected(in IGameServiceController gameServiceController); void disconnected(); + void gameStarted(in GameStartedEvent gameStartedEvent); } diff --git a/core/java/android/service/games/IGameServiceController.aidl b/core/java/android/service/games/IGameServiceController.aidl new file mode 100644 index 000000000000..886f519b6605 --- /dev/null +++ b/core/java/android/service/games/IGameServiceController.aidl @@ -0,0 +1,24 @@ +/* + * 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 android.service.games; + +/** + * @hide + */ +oneway interface IGameServiceController { + void createGameSession(int taskId); +}
\ No newline at end of file diff --git a/core/java/android/service/games/IGameSessionService.aidl b/core/java/android/service/games/IGameSessionService.aidl index 2a53ea7f8e4a..dcbcbc16a374 100644 --- a/core/java/android/service/games/IGameSessionService.aidl +++ b/core/java/android/service/games/IGameSessionService.aidl @@ -18,6 +18,7 @@ package android.service.games; import android.service.games.IGameSession; import android.service.games.CreateGameSessionRequest; +import android.service.games.GameSessionViewHostConfiguration; import com.android.internal.infra.AndroidFuture; @@ -28,5 +29,6 @@ import com.android.internal.infra.AndroidFuture; oneway interface IGameSessionService { void create( in CreateGameSessionRequest createGameSessionRequest, - in AndroidFuture /* T=IBinder for IGameSession */ gameSessionFuture); + in GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + in AndroidFuture /* T=CreateGameSessionResult */ createGameSessionResultFuture); } diff --git a/core/java/android/service/games/OWNERS b/core/java/android/service/games/OWNERS new file mode 100644 index 000000000000..81d94e090d7f --- /dev/null +++ b/core/java/android/service/games/OWNERS @@ -0,0 +1 @@ +include /GAME_MANAGER_OWNERS diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl index 21661db0606a..ec3b8575ed36 100644 --- a/core/java/android/service/trust/ITrustAgentService.aidl +++ b/core/java/android/service/trust/ITrustAgentService.aidl @@ -25,6 +25,7 @@ import android.service.trust.ITrustAgentServiceCallback; */ interface ITrustAgentService { oneway void onUnlockAttempt(boolean successful); + oneway void onUserRequestedUnlock(); oneway void onUnlockLockout(int timeoutMs); oneway void onTrustTimeout(); oneway void onDeviceLocked(); diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index 61277e285b86..fba61cfd801e 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -114,15 +114,47 @@ public class TrustAgentService extends Service { */ public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 1 << 1; + /** + * Flag for {@link #grantTrust(CharSequence, long, int)} indicating the platform should + * automatically remove trust after some conditions are met (detailed below) with the option for + * the agent to renew the trust again later. + * + * <p>After this is called, the agent will grant trust until the platform thinks an active user + * is no longer using that trust. For example, if the user dismisses keyguard, the platform will + * remove trust (this does not automatically lock the device). + * + * <p>When the platform internally removes the agent's trust in this manner, an agent can + * re-grant it (via a call to grantTrust) without the user having to unlock the device through + * another method (e.g. PIN). This renewable state only persists for a limited time. + * + * TODO(b/213631675): Remove @hide + * @hide + */ + public static final int FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE = 1 << 2; + + /** + * Flag for {@link #grantTrust(CharSequence, long, int)} indicating that the message should + * be displayed to the user. + * + * Without this flag, the message passed to {@code grantTrust} is only used for debugging + * purposes. With the flag, it may be displayed to the user as the reason why the device is + * unlocked. + * + * TODO(b/213911325): Remove @hide + * @hide + */ + public static final int FLAG_GRANT_TRUST_DISPLAY_MESSAGE = 1 << 3; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "FLAG_GRANT_TRUST_" }, value = { FLAG_GRANT_TRUST_INITIATED_BY_USER, FLAG_GRANT_TRUST_DISMISS_KEYGUARD, + FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE, + FLAG_GRANT_TRUST_DISPLAY_MESSAGE, }) public @interface GrantTrustFlags {} - /** * Int enum indicating that escrow token is active. * See {@link #onEscrowTokenStateReceived(long, int)} @@ -154,6 +186,7 @@ public class TrustAgentService extends Service { private static final int MSG_ESCROW_TOKEN_ADDED = 7; private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8; private static final int MSG_ESCROW_TOKEN_REMOVED = 9; + private static final int MSG_USER_REQUESTED_UNLOCK = 10; private static final String EXTRA_TOKEN = "token"; private static final String EXTRA_TOKEN_HANDLE = "token_handle"; @@ -187,6 +220,9 @@ public class TrustAgentService extends Service { case MSG_UNLOCK_ATTEMPT: onUnlockAttempt(msg.arg1 != 0); break; + case MSG_USER_REQUESTED_UNLOCK: + onUserRequestedUnlock(); + break; case MSG_UNLOCK_LOCKOUT: onDeviceUnlockLockout(msg.arg1); break; @@ -265,6 +301,22 @@ public class TrustAgentService extends Service { } /** + * Called when the user has interacted with the locked device such that they likely want it + * to be unlocked. This approximates the timing when, for example, the platform would check for + * face authentication to unlock the device. + * + * To attempt to unlock the device, the agent needs to call + * {@link #grantTrust(CharSequence, long, int)}. + * + * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE + * + * TODO(b/213631672): Add CTS tests + * @hide + */ + public void onUserRequestedUnlock() { + } + + /** * Called when the timeout provided by the agent expires. Note that this may be called earlier * than requested by the agent if the trust timeout is adjusted by the system or * {@link DevicePolicyManager}. The agent is expected to re-evaluate the trust state and only @@ -564,6 +616,22 @@ public class TrustAgentService extends Service { } /** + * Locks the user. + * + * This revokes any trust granted by this agent and shows keyguard for the user if it is not + * currently shown for them. Other users are not affected. Note that this is in contrast to + * {@link #revokeTrust()} which does not show keyguard if it is not already shown. + * + * If the user has no auth method specified, then keyguard will still be shown but can be + * dismissed normally. + * + * TODO(b/213631675): Implement & make public + * @hide + */ + public final void lockUser() { + } + + /** * Request showing a transient error message on the keyguard. * The message will be visible on the lock screen or always on display if possible but can be * overridden by other keyguard events of higher priority - eg. fingerprint auth error. @@ -601,6 +669,11 @@ public class TrustAgentService extends Service { } @Override + public void onUserRequestedUnlock() { + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK).sendToTarget(); + } + + @Override public void onUnlockLockout(int timeoutMs) { mHandler.obtainMessage(MSG_UNLOCK_LOCKOUT, timeoutMs, 0).sendToTarget(); } diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index 81af6a220444..93d4def2180e 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -46,4 +46,5 @@ interface IWallpaperEngine { oneway void removeLocalColorsAreas(in List<RectF> regions); oneway void addLocalColorsAreas(in List<RectF> regions); SurfaceControl mirrorSurfaceControl(); + oneway void applyDimming(float dimAmount); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 73ffd66486d2..bff75a62b590 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -26,6 +26,7 @@ import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import android.animation.ValueAnimator; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -159,6 +160,7 @@ public abstract class WallpaperService extends Service { private static final int MSG_ZOOM = 10100; private static final int MSG_SCALE_PREVIEW = 10110; private static final int MSG_REPORT_SHOWN = 10150; + private static final int MSG_UPDATE_DIMMING = 10200; private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY); @@ -167,6 +169,8 @@ public abstract class WallpaperService extends Service { private static final boolean ENABLE_WALLPAPER_DIMMING = SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true); + private static final long DIMMING_ANIMATION_DURATION_MS = 300L; + private final ArrayList<Engine> mActiveEngines = new ArrayList<Engine>(); @@ -221,6 +225,9 @@ public abstract class WallpaperService extends Service { boolean mOffsetsChanged; boolean mFixedSizeAllowed; boolean mShouldDim; + // Whether the wallpaper should be dimmed by default (when no additional dimming is applied) + // based on its color hints + boolean mShouldDimByDefault; int mWidth; int mHeight; int mFormat; @@ -272,6 +279,8 @@ public abstract class WallpaperService extends Service { private Context mDisplayContext; private int mDisplayState; private float mWallpaperDimAmount = 0.05f; + private float mPreviousWallpaperDimAmount = mWallpaperDimAmount; + private float mDefaultDimAmount = mWallpaperDimAmount; SurfaceControl mSurfaceControl = new SurfaceControl(); SurfaceControl mBbqSurfaceControl; @@ -861,15 +870,34 @@ public abstract class WallpaperService extends Service { return; } int colorHints = colors.getColorHints(); - boolean shouldDim = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 + mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 && (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0); - if (shouldDim != mShouldDim) { - mShouldDim = shouldDim; + + // If default dimming value changes and no additional dimming is applied + if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) { + mShouldDim = mShouldDimByDefault; updateSurfaceDimming(); updateSurface(false, false, true); } } + /** + * Update the dim amount of the wallpaper by updating the surface. + * + * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper. + */ + private void updateWallpaperDimming(float dimAmount) { + mPreviousWallpaperDimAmount = mWallpaperDimAmount; + + // Custom dim amount cannot be less than the default dim amount. + mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount); + // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim + // based on its default wallpaper color hints. + mShouldDim = dimAmount != 0f || mShouldDimByDefault; + updateSurfaceDimming(); + updateSurface(false, false, true); + } + private void updateSurfaceDimming() { if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) { return; @@ -878,9 +906,21 @@ public abstract class WallpaperService extends Service { // preview mode. if (!isPreview() && mShouldDim) { Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount); - new SurfaceControl.Transaction() - .setAlpha(mBbqSurfaceControl, 1 - mWallpaperDimAmount) - .apply(); + SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction(); + + // Animate dimming to gradually change the wallpaper alpha from the previous + // dim amount to the new amount only if the dim amount changed. + ValueAnimator animator = ValueAnimator.ofFloat( + mPreviousWallpaperDimAmount, mWallpaperDimAmount); + animator.setDuration(mPreviousWallpaperDimAmount == mWallpaperDimAmount + ? 0 : DIMMING_ANIMATION_DURATION_MS); + animator.addUpdateListener((ValueAnimator va) -> { + final float dimValue = (float) va.getAnimatedValue(); + surfaceControl + .setAlpha(mBbqSurfaceControl, 1 - dimValue) + .apply(); + }); + animator.start(); } else { Log.v(TAG, "Setting wallpaper dimming: " + 0); new SurfaceControl.Transaction() @@ -1332,8 +1372,10 @@ public abstract class WallpaperService extends Service { // Use window context of TYPE_WALLPAPER so client can access UI resources correctly. mDisplayContext = createDisplayContext(mDisplay) .createWindowContext(TYPE_WALLPAPER, null /* options */); - mWallpaperDimAmount = mDisplayContext.getResources().getFloat( + mDefaultDimAmount = mDisplayContext.getResources().getFloat( com.android.internal.R.dimen.config_wallpaperDimAmount); + mWallpaperDimAmount = mDefaultDimAmount; + mPreviousWallpaperDimAmount = mWallpaperDimAmount; mDisplayState = mDisplay.getState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); @@ -1647,7 +1689,7 @@ public abstract class WallpaperService extends Service { Log.e(TAG, "Error creating page local color bitmap", e); continue; } - WallpaperColors color = WallpaperColors.fromBitmap(target); + WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount); target.recycle(); WallpaperColors currentColor = page.getColors(area); @@ -2175,6 +2217,12 @@ public abstract class WallpaperService extends Service { mDetached.set(true); } + public void applyDimming(float dimAmount) throws RemoteException { + Message msg = mCaller.obtainMessageI(MSG_UPDATE_DIMMING, + Float.floatToIntBits(dimAmount)); + mCaller.sendMessage(msg); + } + public void scalePreview(Rect position) { Message msg = mCaller.obtainMessageO(MSG_SCALE_PREVIEW, position); mCaller.sendMessage(msg); @@ -2245,6 +2293,9 @@ public abstract class WallpaperService extends Service { case MSG_ZOOM: mEngine.setZoom(Float.intBitsToFloat(message.arg1)); break; + case MSG_UPDATE_DIMMING: + mEngine.updateWallpaperDimming(Float.intBitsToFloat(message.arg1)); + break; case MSG_SCALE_PREVIEW: mEngine.scalePreview((Rect) message.obj); break; diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl index cc349c8d030d..ad3ad7ad1e05 100644 --- a/core/java/android/speech/IRecognitionService.aidl +++ b/core/java/android/speech/IRecognitionService.aidl @@ -20,6 +20,7 @@ import android.os.Bundle; import android.content.AttributionSource; import android.content.Intent; import android.speech.IRecognitionListener; +import android.speech.IRecognitionSupportCallback; /** * A Service interface to speech recognition. Call startListening when @@ -60,4 +61,18 @@ oneway interface IRecognitionService { * @param listener to receive callbacks, note that this must be non-null */ void cancel(in IRecognitionListener listener, boolean isShutdown); + + /** + * Checks whether this RecognitionService could {@link #startListening} successfully on the + * given recognizerIntent. For more information see {@link #startListening} and + * {@link RecognizerIntent}. + */ + void checkRecognitionSupport(in Intent recognizerIntent, in IRecognitionSupportCallback listener); + + /** + * Requests RecognitionService to download the support for the given recognizerIntent. For more + * information see {@link #checkRecognitionSupport}, {@link #startListening} and + * {@link RecognizerIntent}. + */ + void triggerModelDownload(in Intent recognizerIntent); } diff --git a/core/java/android/speech/IRecognitionSupportCallback.aidl b/core/java/android/speech/IRecognitionSupportCallback.aidl new file mode 100644 index 000000000000..f5a54739af25 --- /dev/null +++ b/core/java/android/speech/IRecognitionSupportCallback.aidl @@ -0,0 +1,37 @@ +/* + * 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 android.speech; + +import android.os.Bundle; +import android.speech.RecognitionSupport; + +/** + * Callback for speech recognition support checks, used with RecognitionService. + * This provides the {@link RecognitionSupport} for a given recognition request, callers can use + * it to check whether RecognitionService can fulfill a given recognition request. + * {@hide} + */ +oneway interface IRecognitionSupportCallback { + void onSupportResult(in RecognitionSupport recognitionSupport); + + /** + * A network or recognition error occurred. + * + * @param error code is defined in {@link SpeechRecognizer} + */ + void onError(in int error); +} diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index 5e647a4531bc..5dbbc045077e 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -37,6 +37,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.util.Pair; import com.android.internal.util.function.pooled.PooledLambda; @@ -90,6 +91,10 @@ public abstract class RecognitionService extends Service { private static final int MSG_RESET = 4; + private static final int MSG_CHECK_RECOGNITION_SUPPORT = 5; + + private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6; + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -107,6 +112,15 @@ public abstract class RecognitionService extends Service { case MSG_RESET: dispatchClearCallback(); break; + case MSG_CHECK_RECOGNITION_SUPPORT: + Pair<Intent, IRecognitionSupportCallback> intentAndListener = + (Pair<Intent, IRecognitionSupportCallback>) msg.obj; + dispatchCheckRecognitionSupport( + intentAndListener.first, intentAndListener.second); + break; + case MSG_TRIGGER_MODEL_DOWNLOAD: + dispatchTriggerModelDownload((Intent) msg.obj); + break; } } }; @@ -179,6 +193,15 @@ public abstract class RecognitionService extends Service { mStartedDataDelivery = false; } + private void dispatchCheckRecognitionSupport( + Intent intent, IRecognitionSupportCallback callback) { + RecognitionService.this.onCheckRecognitionSupport(intent, new SupportCallback(callback)); + } + + private void dispatchTriggerModelDownload(Intent intent) { + RecognitionService.this.triggerModelDownload(intent); + } + private class StartListeningArgs { public final Intent mIntent; @@ -238,6 +261,34 @@ public abstract class RecognitionService extends Service { */ protected abstract void onStopListening(Callback listener); + /** + * Queries the service on whether it would support a {@link #onStartListening(Intent, Callback)} + * for the same {@code recognizerIntent}. + * + * <p>The service will notify the caller about the level of support or error via + * {@link SupportCallback}. + * + * <p>If the service does not offer the support check it will notify the caller with + * {@link SpeechRecognizer#ERROR_CANNOT_CHECK_SUPPORT}. + */ + public void onCheckRecognitionSupport( + @NonNull Intent recognizerIntent, + @NonNull SupportCallback supportCallback) { + if (DBG) { + Log.i(TAG, String.format("#onSupports [%s]", recognizerIntent)); + } + supportCallback.onError(SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT); + } + + /** + * Requests the download of the recognizer support for {@code recognizerIntent}. + */ + public void triggerModelDownload(@NonNull Intent recognizerIntent) { + if (DBG) { + Log.i(TAG, String.format("#downloadModel [%s]", recognizerIntent)); + } + } + @Override @SuppressLint("MissingNullability") public Context createContext(@NonNull ContextParams contextParams) { @@ -410,7 +461,45 @@ public abstract class RecognitionService extends Service { } } - /** Binder of the recognition service */ + /** + * This class receives callbacks from the speech recognition service and forwards them to the + * user. An instance of this class is passed to the + * {@link RecognitionService#onCheckRecognitionSupport(Intent, SupportCallback)} method. Recognizers may call + * these methods on any thread. + */ + public static class SupportCallback { + + private final IRecognitionSupportCallback mCallback; + + private SupportCallback(IRecognitionSupportCallback callback) { + this.mCallback = callback; + } + + /** The service should call this method to notify the caller about the level of support. */ + public void onSupportResult(@NonNull RecognitionSupport recognitionSupport) { + try { + mCallback.onSupportResult(recognitionSupport); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * The service should call this method when an error occurred and can't satisfy the support + * request. + * + * @param errorCode code is defined in {@link SpeechRecognizer} + */ + public void onError(@SpeechRecognizer.RecognitionError int errorCode) { + try { + mCallback.onError(errorCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + +/** Binder of the recognition service */ private static final class RecognitionServiceBinder extends IRecognitionService.Stub { private final WeakReference<RecognitionService> mServiceRef; @@ -452,6 +541,27 @@ public abstract class RecognitionService extends Service { } } + @Override + public void checkRecognitionSupport( + Intent recognizerIntent, IRecognitionSupportCallback callback) { + final RecognitionService service = mServiceRef.get(); + if (service != null) { + service.mHandler.sendMessage( + Message.obtain(service.mHandler, MSG_CHECK_RECOGNITION_SUPPORT, + Pair.create(recognizerIntent, callback))); + } + } + + @Override + public void triggerModelDownload(Intent recognizerIntent) { + final RecognitionService service = mServiceRef.get(); + if (service != null) { + service.mHandler.sendMessage( + Message.obtain( + service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent)); + } + } + public void clearReference() { mServiceRef.clear(); } diff --git a/core/java/android/speech/RecognitionSupport.aidl b/core/java/android/speech/RecognitionSupport.aidl new file mode 100644 index 000000000000..20e52a8f04f5 --- /dev/null +++ b/core/java/android/speech/RecognitionSupport.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.speech; + +parcelable RecognitionSupport; diff --git a/core/java/android/speech/RecognitionSupport.java b/core/java/android/speech/RecognitionSupport.java new file mode 100644 index 000000000000..3a86d0b30639 --- /dev/null +++ b/core/java/android/speech/RecognitionSupport.java @@ -0,0 +1,328 @@ +/* + * 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 android.speech; + +import android.annotation.NonNull; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.List; + +/** Encodes the level of support for a given speech recognition request */ +@DataClass( + genConstructor = false, + genBuilder = true, + genEqualsHashCode = true, + genHiddenConstDefs = true, + genParcelable = true, + genToString = true +) +public final class RecognitionSupport implements Parcelable { + + /** Support for this request is ready for use on this device for the returned languages. */ + @NonNull + private List<String> mInstalledLanguages = null; + + /** Support for this request is scheduled for download for the returned languages. */ + @NonNull private List<String> mPendingLanguages = null; + + /** These languages are supported but need to be downloaded before use. */ + @NonNull + private List<String> mSupportedLanguages = null; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/speech/RecognitionSupport.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ RecognitionSupport( + @NonNull List<String> installedLanguages, + @NonNull List<String> pendingLanguages, + @NonNull List<String> supportedLanguages) { + this.mInstalledLanguages = installedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mInstalledLanguages); + this.mPendingLanguages = pendingLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPendingLanguages); + this.mSupportedLanguages = supportedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSupportedLanguages); + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Support for this request is ready for use on this device for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull List<String> getInstalledLanguages() { + return mInstalledLanguages; + } + + /** + * Support for this request is scheduled for download for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull List<String> getPendingLanguages() { + return mPendingLanguages; + } + + /** + * These languages are supported but need to be downloaded before use. + */ + @DataClass.Generated.Member + public @NonNull List<String> getSupportedLanguages() { + return mSupportedLanguages; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "RecognitionSupport { " + + "installedLanguages = " + mInstalledLanguages + ", " + + "pendingLanguages = " + mPendingLanguages + ", " + + "supportedLanguages = " + mSupportedLanguages + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(RecognitionSupport other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + RecognitionSupport that = (RecognitionSupport) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mInstalledLanguages, that.mInstalledLanguages) + && java.util.Objects.equals(mPendingLanguages, that.mPendingLanguages) + && java.util.Objects.equals(mSupportedLanguages, that.mSupportedLanguages); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mInstalledLanguages); + _hash = 31 * _hash + java.util.Objects.hashCode(mPendingLanguages); + _hash = 31 * _hash + java.util.Objects.hashCode(mSupportedLanguages); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeStringList(mInstalledLanguages); + dest.writeStringList(mPendingLanguages); + dest.writeStringList(mSupportedLanguages); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ RecognitionSupport(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + List<String> installedLanguages = new java.util.ArrayList<>(); + in.readStringList(installedLanguages); + List<String> pendingLanguages = new java.util.ArrayList<>(); + in.readStringList(pendingLanguages); + List<String> supportedLanguages = new java.util.ArrayList<>(); + in.readStringList(supportedLanguages); + + this.mInstalledLanguages = installedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mInstalledLanguages); + this.mPendingLanguages = pendingLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPendingLanguages); + this.mSupportedLanguages = supportedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSupportedLanguages); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<RecognitionSupport> CREATOR + = new Parcelable.Creator<RecognitionSupport>() { + @Override + public RecognitionSupport[] newArray(int size) { + return new RecognitionSupport[size]; + } + + @Override + public RecognitionSupport createFromParcel(@NonNull android.os.Parcel in) { + return new RecognitionSupport(in); + } + }; + + /** + * A builder for {@link RecognitionSupport} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @NonNull List<String> mInstalledLanguages; + private @NonNull List<String> mPendingLanguages; + private @NonNull List<String> mSupportedLanguages; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Support for this request is ready for use on this device for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull Builder setInstalledLanguages(@NonNull List<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mInstalledLanguages = value; + return this; + } + + /** @see #setInstalledLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addInstalledLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mInstalledLanguages == null) setInstalledLanguages(new java.util.ArrayList<>()); + mInstalledLanguages.add(value); + return this; + } + + /** + * Support for this request is scheduled for download for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull Builder setPendingLanguages(@NonNull List<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mPendingLanguages = value; + return this; + } + + /** @see #setPendingLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addPendingLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mPendingLanguages == null) setPendingLanguages(new java.util.ArrayList<>()); + mPendingLanguages.add(value); + return this; + } + + /** + * These languages are supported but need to be downloaded before use. + */ + @DataClass.Generated.Member + public @NonNull Builder setSupportedLanguages(@NonNull List<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mSupportedLanguages = value; + return this; + } + + /** @see #setSupportedLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addSupportedLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mSupportedLanguages == null) setSupportedLanguages(new java.util.ArrayList<>()); + mSupportedLanguages.add(value); + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull RecognitionSupport build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mInstalledLanguages = null; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mPendingLanguages = null; + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mSupportedLanguages = null; + } + RecognitionSupport o = new RecognitionSupport( + mInstalledLanguages, + mPendingLanguages, + mSupportedLanguages); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x8) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1639158640137L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/speech/RecognitionSupport.java", + inputSignatures = "private @android.annotation.NonNull java.util.List<java.lang.String> mInstalledLanguages\nprivate @android.annotation.NonNull java.util.List<java.lang.String> mPendingLanguages\nprivate @android.annotation.NonNull java.util.List<java.lang.String> mSupportedLanguages\nclass RecognitionSupport extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/speech/RecognitionSupportCallback.java b/core/java/android/speech/RecognitionSupportCallback.java new file mode 100644 index 000000000000..9278e719ac81 --- /dev/null +++ b/core/java/android/speech/RecognitionSupportCallback.java @@ -0,0 +1,32 @@ +/* + * 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 android.speech; + +import android.annotation.NonNull; + +/** + * Used for receiving notifications from the SpeechRecognizer about the device support status for + * the given recognition request. + */ +public interface RecognitionSupportCallback { + + /** Notifies the caller about the support for the given request. */ + void onSupportResult(@NonNull RecognitionSupport recognitionSupport); + + /** Notifies the caller about an error during the recognition support request */ + void onError(@SpeechRecognizer.RecognitionError int error); +} diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 3cdd8b8d8436..71c1e882a1f6 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -38,6 +38,7 @@ import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.util.Slog; import com.android.internal.R; @@ -46,6 +47,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; @@ -112,7 +114,8 @@ public class SpeechRecognizer { ERROR_TOO_MANY_REQUESTS, ERROR_SERVER_DISCONNECTED, ERROR_LANGUAGE_NOT_SUPPORTED, - ERROR_LANGUAGE_UNAVAILABLE + ERROR_LANGUAGE_UNAVAILABLE, + ERROR_CANNOT_CHECK_SUPPORT, }) public @interface RecognitionError {} @@ -155,19 +158,24 @@ public class SpeechRecognizer { /** Requested language is supported, but not available currently (e.g. not downloaded yet). */ public static final int ERROR_LANGUAGE_UNAVAILABLE = 13; + /** The service does not allow to check for support. */ + public static final int ERROR_CANNOT_CHECK_SUPPORT = 14; + /** action codes */ private static final int MSG_START = 1; private static final int MSG_STOP = 2; private static final int MSG_CANCEL = 3; private static final int MSG_CHANGE_LISTENER = 4; private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5; + private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6; + private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7; /** The actual RecognitionService endpoint */ private IRecognitionService mService; /** Context with which the manager was created */ private final Context mContext; - + /** Component to direct service intent to */ private final ComponentName mServiceComponent; @@ -197,6 +205,15 @@ public class SpeechRecognizer { case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT: handleSetTemporaryComponent((ComponentName) msg.obj); break; + case MSG_CHECK_RECOGNITION_SUPPORT: + Pair<Intent, RecognitionSupportCallback> intentAndListener = + (Pair<Intent, RecognitionSupportCallback>) msg.obj; + handleCheckRecognitionSupport( + intentAndListener.first, intentAndListener.second); + break; + case MSG_TRIGGER_MODEL_DOWNLOAD: + handleTriggerModelDownload((Intent) msg.obj); + break; } } }; @@ -208,7 +225,7 @@ public class SpeechRecognizer { private final Queue<Message> mPendingTasks = new LinkedBlockingQueue<>(); /** The Listener that will receive all the callbacks */ - private final InternalListener mListener = new InternalListener(); + private final InternalRecognitionListener mListener = new InternalRecognitionListener(); private final IBinder mClientToken = new Binder(); @@ -465,6 +482,38 @@ public class SpeechRecognizer { } /** + * Checks whether {@code recognizerIntent} is supported by + * {@link SpeechRecognizer#startListening(Intent)}. + * + * @param recognizerIntent contains parameters for the recognition to be performed. The intent + * may also contain optional extras. See {@link RecognizerIntent} for the list of + * supported extras, any unlisted extra might be ignored. + * @param supportListener the listener on which to receive the support query results. + */ + public void checkRecognitionSupport( + @NonNull Intent recognizerIntent, + @NonNull RecognitionSupportCallback supportListener) { + Objects.requireNonNull(recognizerIntent, "intent must not be null"); + Objects.requireNonNull(supportListener, "listener must not be null"); + + putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT, + Pair.create(recognizerIntent, supportListener))); + } + + /** + * Attempts to download the support for the given {@code recognizerIntent}. This might trigger + * user interaction to approve the download. Callers can verify the status of the request via + * {@link #checkRecognitionSupport(Intent, RecognitionSupportCallback)}. + * + * @param recognizerIntent contains parameters for the recognition to be performed. The intent + * may also contain optional extras, see {@link RecognizerIntent}. + */ + public void triggerModelDownload(@NonNull Intent recognizerIntent) { + Objects.requireNonNull(recognizerIntent, "intent must not be null"); + putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD)); + } + + /** * Sets a temporary component to power on-device speech recognizer. * * <p>This is only expected to be called in tests, system would reject calls from client apps. @@ -503,7 +552,7 @@ public class SpeechRecognizer { } try { mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource()); - if (DBG) Log.d(TAG, "service start listening command succeded"); + if (DBG) Log.d(TAG, "service start listening command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "startListening() failed", e); mListener.onError(ERROR_CLIENT); @@ -517,7 +566,7 @@ public class SpeechRecognizer { } try { mService.stopListening(mListener); - if (DBG) Log.d(TAG, "service stop listening command succeded"); + if (DBG) Log.d(TAG, "service stop listening command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "stopListening() failed", e); mListener.onError(ERROR_CLIENT); @@ -531,7 +580,7 @@ public class SpeechRecognizer { } try { mService.cancel(mListener, /*isShutdown*/ false); - if (DBG) Log.d(TAG, "service cancel command succeded"); + if (DBG) Log.d(TAG, "service cancel command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "cancel() failed", e); mListener.onError(ERROR_CLIENT); @@ -554,6 +603,35 @@ public class SpeechRecognizer { } } + private void handleCheckRecognitionSupport( + Intent recognizerIntent, RecognitionSupportCallback recognitionSupportCallback) { + if (!maybeInitializeManagerService()) { + return; + } + try { + mService.checkRecognitionSupport( + recognizerIntent, + new InternalSupportCallback(recognitionSupportCallback)); + if (DBG) Log.d(TAG, "service support command succeeded"); + } catch (final RemoteException e) { + Log.e(TAG, "checkRecognitionSupport() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + + private void handleTriggerModelDownload(Intent recognizerIntent) { + if (!maybeInitializeManagerService()) { + return; + } + try { + mService.triggerModelDownload(recognizerIntent); + if (DBG) Log.d(TAG, "service download support command succeeded"); + } catch (final RemoteException e) { + Log.e(TAG, "downloadModel() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + private boolean checkOpenConnection() { if (mService != null) { return true; @@ -626,7 +704,7 @@ public class SpeechRecognizer { } } - private boolean maybeInitializeManagerService() { + private synchronized boolean maybeInitializeManagerService() { if (mManagerService != null) { return true; } @@ -678,7 +756,7 @@ public class SpeechRecognizer { * Internal wrapper of IRecognitionListener which will propagate the results to * RecognitionListener */ - private static class InternalListener extends IRecognitionListener.Stub { + private static class InternalRecognitionListener extends IRecognitionListener.Stub { private RecognitionListener mInternalListener; private static final int MSG_BEGINNING_OF_SPEECH = 1; @@ -766,4 +844,42 @@ public class SpeechRecognizer { .sendToTarget(); } } + + private static class InternalSupportCallback extends IRecognitionSupportCallback.Stub { + private final RecognitionSupportCallback mCallback; + + private static final int MSG_SUPPORT_RESULT = 1; + private static final int MSG_ERROR = 2; + + private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (mCallback == null) { + return; + } + switch (msg.what) { + case MSG_SUPPORT_RESULT: + mCallback.onSupportResult((RecognitionSupport) msg.obj); + break; + case MSG_ERROR: + mCallback.onError((Integer) msg.obj); + break; + } + } + }; + + private InternalSupportCallback(RecognitionSupportCallback callback) { + this.mCallback = callback; + } + + @Override + public void onSupportResult(RecognitionSupport recognitionSupport) throws RemoteException { + Message.obtain(mInternalHandler, MSG_SUPPORT_RESULT, recognitionSupport).sendToTarget(); + } + + @Override + public void onError(int errorCode) throws RemoteException { + Message.obtain(mInternalHandler, MSG_ERROR, errorCode).sendToTarget(); + } + } } diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 9eaaa91532d0..542de3fad8b0 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -131,6 +131,7 @@ public class TelephonyRegistryManager { mContext.getAttributionTag(), callback); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -152,6 +153,7 @@ public class TelephonyRegistryManager { mSubscriptionChangedListenerMap.remove(listener); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -194,6 +196,7 @@ public class TelephonyRegistryManager { mContext.getAttributionTag(), callback); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -216,6 +219,7 @@ public class TelephonyRegistryManager { mOpportunisticSubscriptionChangedListenerMap.remove(listener); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -304,6 +308,7 @@ public class TelephonyRegistryManager { sRegistry.notifyCarrierNetworkChange(active); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -329,6 +334,7 @@ public class TelephonyRegistryManager { sRegistry.notifyCarrierNetworkChangeWithSubId(subscriptionId, active); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -347,6 +353,7 @@ public class TelephonyRegistryManager { sRegistry.notifyCallState(slotIndex, subId, state, incomingNumber); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -364,6 +371,7 @@ public class TelephonyRegistryManager { sRegistry.notifyCallStateForAllSubs(state, incomingNumber); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -376,6 +384,7 @@ public class TelephonyRegistryManager { sRegistry.notifySubscriptionInfoChanged(); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -388,6 +397,7 @@ public class TelephonyRegistryManager { sRegistry.notifyOpportunisticSubscriptionInfoChanged(); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -404,6 +414,7 @@ public class TelephonyRegistryManager { sRegistry.notifyServiceStateForPhoneId(slotIndex, subId, state); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -421,6 +432,7 @@ public class TelephonyRegistryManager { sRegistry.notifySignalStrengthForPhoneId(slotIndex, subId, signalStrength); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -439,6 +451,7 @@ public class TelephonyRegistryManager { sRegistry.notifyMessageWaitingChangedForPhoneId(slotIndex, subId, msgWaitingInd); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -454,6 +467,7 @@ public class TelephonyRegistryManager { sRegistry.notifyCallForwardingChangedForSubscriber(subId, callForwardInd); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -469,6 +483,7 @@ public class TelephonyRegistryManager { sRegistry.notifyDataActivityForSubscriber(subId, dataActivityType); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -490,6 +505,7 @@ public class TelephonyRegistryManager { slotIndex, subId, preciseState); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -508,6 +524,7 @@ public class TelephonyRegistryManager { sRegistry.notifyCallQualityChanged(callQuality, slotIndex, subId, networkType); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -523,6 +540,7 @@ public class TelephonyRegistryManager { sRegistry.notifyEmergencyNumberList(slotIndex, subId); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -538,6 +556,7 @@ public class TelephonyRegistryManager { sRegistry.notifyOutgoingEmergencyCall(phoneId, subId, emergencyNumber); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -553,6 +572,7 @@ public class TelephonyRegistryManager { sRegistry.notifyOutgoingEmergencySms(phoneId, subId, emergencyNumber); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -570,6 +590,7 @@ public class TelephonyRegistryManager { sRegistry.notifyRadioPowerStateChanged(slotIndex, subId, radioPowerState); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -583,6 +604,7 @@ public class TelephonyRegistryManager { sRegistry.notifyPhoneCapabilityChanged(phoneCapability); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -615,6 +637,7 @@ public class TelephonyRegistryManager { SIM_ACTIVATION_TYPE_DATA, activationState); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -634,6 +657,7 @@ public class TelephonyRegistryManager { SIM_ACTIVATION_TYPE_VOICE, activationState); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -651,6 +675,7 @@ public class TelephonyRegistryManager { sRegistry.notifyUserMobileDataStateChangedForPhoneId(slotIndex, subId, state); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -669,6 +694,7 @@ public class TelephonyRegistryManager { sRegistry.notifyDisplayInfoChanged(slotIndex, subscriptionId, telephonyDisplayInfo); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -683,6 +709,7 @@ public class TelephonyRegistryManager { sRegistry.notifyImsDisconnectCause(subId, imsReasonInfo); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -698,6 +725,7 @@ public class TelephonyRegistryManager { sRegistry.notifySrvccStateChanged(subId, state); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -721,6 +749,7 @@ public class TelephonyRegistryManager { foregroundCallPreciseState, backgroundCallPreciseState); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -741,6 +770,7 @@ public class TelephonyRegistryManager { sRegistry.notifyDisconnectCause(slotIndex, subId, cause, preciseCause); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -755,6 +785,7 @@ public class TelephonyRegistryManager { sRegistry.notifyCellLocationForSubscriber(subId, cellLocation); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -769,7 +800,7 @@ public class TelephonyRegistryManager { try { sRegistry.notifyCellInfoForSubscriber(subId, cellInfo); } catch (RemoteException ex) { - + throw ex.rethrowFromSystemServer(); } } @@ -781,7 +812,7 @@ public class TelephonyRegistryManager { try { sRegistry.notifyActiveDataSubIdChanged(activeDataSubId); } catch (RemoteException ex) { - + throw ex.rethrowFromSystemServer(); } } @@ -814,6 +845,7 @@ public class TelephonyRegistryManager { sRegistry.notifyRegistrationFailed(slotIndex, subId, cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode); } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -830,6 +862,7 @@ public class TelephonyRegistryManager { sRegistry.notifyBarringInfoChanged(slotIndex, subId, barringInfo); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -846,6 +879,7 @@ public class TelephonyRegistryManager { sRegistry.notifyPhysicalChannelConfigForSubscriber(slotIndex, subId, configs); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -862,6 +896,7 @@ public class TelephonyRegistryManager { sRegistry.notifyDataEnabled(slotIndex, subId, enabled, reason); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } @@ -880,6 +915,7 @@ public class TelephonyRegistryManager { allowedNetworkType); } catch (RemoteException ex) { // system process is dead + throw ex.rethrowFromSystemServer(); } } @@ -895,6 +931,7 @@ public class TelephonyRegistryManager { sRegistry.notifyLinkCapacityEstimateChanged(slotIndex, subId, linkCapacityEstimateList); } catch (RemoteException ex) { // system server crash + throw ex.rethrowFromSystemServer(); } } diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 3ee1a9000188..fee23f4b0be7 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -16,6 +16,9 @@ package android.text; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; @@ -85,6 +88,41 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback } /** + * Utility function to construct a BoringLayout instance. + * + * The spacing multiplier and additional amount spacing are not used by BoringLayout. + * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will + * return 0.0. + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested width + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerWidth} is used instead + * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts. + * False for keeping the first font's line height. If some glyphs + * requires larger vertical spaces, by passing true to this + * argument, the layout increase the line height to fit all glyphs. + */ + public static @NonNull BoringLayout make( + @NonNull CharSequence source, @NonNull TextPaint paint, + @IntRange(from = 0) int outerWidth, + @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, + boolean includePad, @NonNull TextUtils.TruncateAt ellipsize, + @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) { + return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad, + ellipsize, ellipsizedWidth, useFallbackLineSpacing); + } + + /** * Returns a BoringLayout for the specified text, potentially reusing * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. @@ -109,7 +147,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback mEllipsizedStart = 0; mEllipsizedCount = 0; - init(source, paint, align, metrics, includePad, true); + init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); return this; } @@ -118,12 +156,14 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. * + * The spacing multiplier and additional amount spacing are not used by BoringLayout. + * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will + * return 0.0. + * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text - * @param spacingMult this value is no longer used by BoringLayout - * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is @@ -132,15 +172,21 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * requested width * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is - * not used, {@code outerwidth} is used instead + * not used, {@code outerWidth} is used instead + * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts. + * False for keeping the first font's line height. If some glyphs + * requires larger vertical spaces, by passing true to this + * argument, the layout increase the line height to fit all glyphs. */ - public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, - Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, - boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source, + @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, + @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, + @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, + boolean useFallbackLineSpacing) { boolean trust; if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { - replaceWith(source, paint, outerWidth, align, spacingMult, spacingAdd); + replaceWith(source, paint, outerWidth, align, 1f, 0f); mEllipsizedWidth = outerWidth; mEllipsizedStart = 0; @@ -148,17 +194,46 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback trust = true; } else { replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this), - paint, outerWidth, align, spacingMult, spacingAdd); + paint, outerWidth, align, 1f, 0f); mEllipsizedWidth = ellipsizedWidth; trust = false; } - init(getText(), paint, align, metrics, includePad, trust); + init(getText(), paint, align, metrics, includePad, trust, + useFallbackLineSpacing); return this; } /** + * Returns a BoringLayout for the specified text, potentially reusing + * this one if it is already suitable. The caller must make sure that + * no one is still using this Layout. + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param spacingMult this value is no longer used by BoringLayout + * @param spacingAdd this value is no longer used by BoringLayout + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested width + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerWidth} is used instead + */ + public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, + Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, + boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + return replaceOrMake(source, paint, outerWidth, align, metrics, + includePad, ellipsize, ellipsizedWidth, false /* useFallbackLineSpacing */); + } + + /** * @param source the text to render * @param paint the default paint for the layout * @param outerwidth the wrapping width for the text @@ -178,7 +253,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback mEllipsizedStart = 0; mEllipsizedCount = 0; - init(source, paint, align, metrics, includePad, true); + init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); } /** @@ -194,14 +269,46 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the - * requested {@code outerwidth} + * requested {@code outerWidth} * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is - * not used, {@code outerwidth} is used instead + * not used, {@code outerWidth} is used instead */ public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + this(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad, + ellipsize, ellipsizedWidth, false /* fallbackLineSpacing */); + } + + /** + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param spacingMult this value is no longer used by BoringLayout + * @param spacingAdd this value is no longer used by BoringLayout + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested {@code outerWidth} + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerWidth} is used instead + * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts. + * False for keeping the first font's line height. If some glyphs + * requires larger vertical spaces, by passing true to this + * argument, the layout increase the line height to fit all glyphs. + */ + public BoringLayout( + @NonNull CharSequence source, @NonNull TextPaint paint, + @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult, + float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad, + @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, + boolean useFallbackLineSpacing) { /* * It is silly to have to call super() and then replaceWith(), * but we can't use "this" for the callback until the call to @@ -224,11 +331,12 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback trust = false; } - init(getText(), paint, align, metrics, includePad, trust); + init(getText(), paint, align, metrics, includePad, trust, useFallbackLineSpacing); } /* package */ void init(CharSequence source, TextPaint paint, Alignment align, - BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth) { + BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth, + boolean useFallbackLineSpacing) { int spacing; if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) { @@ -260,7 +368,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback TextLine line = TextLine.obtain(); line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null, - mEllipsizedStart, mEllipsizedStart + mEllipsizedCount); + mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing); mMax = (int) Math.ceil(line.metrics(null)); TextLine.recycle(line); } @@ -336,6 +444,27 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback @UnsupportedAppUsage public static Metrics isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics) { + return isBoring(text, paint, textDir, false /* useFallbackLineSpacing */, metrics); + } + + /** + * Returns null if not boring; the width, ascent, and descent in the + * provided Metrics object (or a new one if the provided one was null) + * if boring. + * + * @param text a text to be calculated text layout. + * @param paint a paint object used for styling. + * @param textDir a text direction. + * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts. + * False for keeping the first font's line height. If some glyphs + * requires larger vertical spaces, by passing true to this + * argument, the layout increase the line height to fit all glyphs. + * @param metrics the out metrics. + * @return metrics on success. null if text cannot be rendered by BoringLayout. + */ + public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing, + @Nullable Metrics metrics) { final int textLength = text.length(); if (hasAnyInterestingChars(text, textLength)) { return null; // There are some interesting characters. Not boring. @@ -362,7 +491,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null, 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */, - 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */); + 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */, + useFallbackLineSpacing); fm.width = (int) Math.ceil(line.metrics(fm)); TextLine.recycle(line); @@ -450,6 +580,11 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback return mEllipsizedWidth; } + @Override + public boolean isFallbackLineSpacingEnabled() { + return mUseFallbackLineSpacing; + } + // Override draw so it will be faster. @Override public void draw(Canvas c, Path highlight, Paint highlightpaint, @@ -471,6 +606,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback private String mDirect; private Paint mPaint; + private boolean mUseFallbackLineSpacing; /* package */ int mBottom, mDesc; // for Direct private int mTopPadding, mBottomPadding; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index da3e9b6d509c..95adb7765f1e 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -591,7 +591,8 @@ public abstract class Layout { } else { tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops, getEllipsisStart(lineNum), - getEllipsisStart(lineNum) + getEllipsisCount(lineNum)); + getEllipsisStart(lineNum) + getEllipsisCount(lineNum), + isFallbackLineSpacingEnabled()); if (justify) { tl.justify(right - left - indentWidth); } @@ -960,6 +961,15 @@ public abstract class Layout { } /** + * Return true if the fallback line space is enabled in this Layout. + * + * @return true if the fallback line space is enabled. Otherwise returns false. + */ + public boolean isFallbackLineSpacingEnabled() { + return false; + } + + /** * Returns true if the character at offset and the preceding character * are at different run levels (and thus there's a split caret). * @param offset the offset @@ -1231,7 +1241,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); float wid = tl.measure(offset - start, trailing, null); TextLine.recycle(tl); @@ -1271,7 +1282,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); boolean[] trailings = primaryIsTrailingPreviousAllLineOffsets(line); if (!primary) { for (int offset = 0; offset < trailings.length; ++offset) { @@ -1456,7 +1468,8 @@ public abstract class Layout { paint.setStartHyphenEdit(getStartHyphenEdit(line)); paint.setEndHyphenEdit(getEndHyphenEdit(line)); tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { tl.justify(getJustifyWidth(line)); } @@ -1486,7 +1499,8 @@ public abstract class Layout { paint.setStartHyphenEdit(getStartHyphenEdit(line)); paint.setEndHyphenEdit(getEndHyphenEdit(line)); tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { tl.justify(getJustifyWidth(line)); } @@ -1572,7 +1586,8 @@ public abstract class Layout { // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here. tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs, false, null, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); final HorizontalMeasurementProvider horizontal = new HorizontalMeasurementProvider(line, primary); @@ -1828,7 +1843,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); // XXX: we don't care about tabs tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft); TextLine.recycle(tl); return caret; @@ -2202,7 +2218,8 @@ public abstract class Layout { } } tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops, - 0 /* ellipsisStart */, 0 /* ellipsisEnd */); + 0 /* ellipsisStart */, 0 /* ellipsisEnd */, + false /* use fallback line spacing. unused */); return margin + Math.abs(tl.metrics(null)); } finally { TextLine.recycle(tl); diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index 6a3c6182b96d..748d55123b9a 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.text.LineBreakConfig; import android.graphics.text.MeasuredText; import android.text.AutoGrowArray.ByteArray; import android.text.AutoGrowArray.FloatArray; @@ -124,7 +125,7 @@ public class MeasuredParagraph { // The native MeasuredParagraph. private @Nullable MeasuredText mMeasuredText; - // Following two objects are for avoiding object allocation. + // Following three objects are for avoiding object allocation. private @NonNull TextPaint mCachedPaint = new TextPaint(); private @Nullable Paint.FontMetricsInt mCachedFm; @@ -350,7 +351,8 @@ public class MeasuredParagraph { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. mt.applyMetricsAffectingSpan( - paint, null /* spans */, start, end, null /* native builder ptr */); + paint, null /* lineBreakConfig */, null /* spans */, start, end, + null /* native builder ptr */); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. int spanEnd; @@ -360,7 +362,8 @@ public class MeasuredParagraph { MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); mt.applyMetricsAffectingSpan( - paint, spans, spanStart, spanEnd, null /* native builder ptr */); + paint, null /* line break config */, spans, spanStart, spanEnd, + null /* native builder ptr */); } } return mt; @@ -373,6 +376,7 @@ public class MeasuredParagraph { * result to recycle and returns recycle. * * @param paint the paint to be used for rendering the text. + * @param lineBreakConfig the line break configuration for text wrapping. * @param text the character sequence to be measured * @param start the inclusive start offset of the target region in the text * @param end the exclusive end offset of the target region in the text @@ -386,6 +390,7 @@ public class MeasuredParagraph { */ public static @NonNull MeasuredParagraph buildForStaticLayout( @NonNull TextPaint paint, + @Nullable LineBreakConfig lineBreakConfig, @NonNull CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @@ -411,7 +416,8 @@ public class MeasuredParagraph { } else { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, builder); + mt.applyMetricsAffectingSpan(paint, lineBreakConfig, null /* spans */, start, end, + builder); mt.mSpanEndCache.append(end); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply @@ -424,7 +430,9 @@ public class MeasuredParagraph { MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, builder); + // TODO: Update line break config with spans. + mt.applyMetricsAffectingSpan(paint, lineBreakConfig, spans, spanStart, spanEnd, + builder); mt.mSpanEndCache.append(spanEnd); } } @@ -500,12 +508,13 @@ public class MeasuredParagraph { private void applyReplacementRun(@NonNull ReplacementSpan replacement, @IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer + @NonNull TextPaint paint, @Nullable MeasuredText.Builder builder) { // Use original text. Shouldn't matter. // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for // backward compatibility? or Should we initialize them for getFontMetricsInt? final float width = replacement.getSize( - mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); + paint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); if (builder == null) { // Assigns all width to the first character. This is the same behavior as minikin. mWidths.set(start, width); @@ -514,22 +523,24 @@ public class MeasuredParagraph { } mWholeWidth += width; } else { - builder.appendReplacementRun(mCachedPaint, end - start, width); + builder.appendReplacementRun(paint, end - start, width); } } private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer + @NonNull TextPaint paint, + @Nullable LineBreakConfig config, @Nullable MeasuredText.Builder builder) { if (mLtrWithoutBidi) { // If the whole text is LTR direction, just apply whole region. if (builder == null) { - mWholeWidth += mCachedPaint.getTextRunAdvances( + mWholeWidth += paint.getTextRunAdvances( mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, mWidths.getRawArray(), start); } else { - builder.appendStyleRun(mCachedPaint, end - start, false /* isRtl */); + builder.appendStyleRun(paint, config, end - start, false /* isRtl */); } } else { // If there is multiple bidi levels, split into individual bidi level and apply style. @@ -541,11 +552,11 @@ public class MeasuredParagraph { final boolean isRtl = (level & 0x1) != 0; if (builder == null) { final int levelLength = levelEnd - levelStart; - mWholeWidth += mCachedPaint.getTextRunAdvances( + mWholeWidth += paint.getTextRunAdvances( mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, isRtl, mWidths.getRawArray(), levelStart); } else { - builder.appendStyleRun(mCachedPaint, levelEnd - levelStart, isRtl); + builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl); } if (levelEnd == end) { break; @@ -559,6 +570,7 @@ public class MeasuredParagraph { private void applyMetricsAffectingSpan( @NonNull TextPaint paint, + @Nullable LineBreakConfig lineBreakConfig, @Nullable MetricAffectingSpan[] spans, @IntRange(from = 0) int start, // inclusive, in original text buffer @IntRange(from = 0) int end, // exclusive, in original text buffer @@ -595,9 +607,11 @@ public class MeasuredParagraph { } if (replacement != null) { - applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, builder); + applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, mCachedPaint, + builder); } else { - applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, builder); + applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, mCachedPaint, + lineBreakConfig, builder); } if (needFontMetrics) { diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index 152570ffd1c7..ce63376a6d63 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -22,6 +22,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; +import android.graphics.text.LineBreakConfig; import android.graphics.text.MeasuredText; import android.text.style.MetricAffectingSpan; @@ -96,6 +97,9 @@ public class PrecomputedText implements Spannable { // The hyphenation frequency for this measured text. private final @Layout.HyphenationFrequency int mHyphenationFrequency; + // The line break configuration for calculating text wrapping. + private final @Nullable LineBreakConfig mLineBreakConfig; + /** * A builder for creating {@link Params}. */ @@ -113,6 +117,9 @@ public class PrecomputedText implements Spannable { private @Layout.HyphenationFrequency int mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL; + // The line break configuration for calculating text wrapping. + private @Nullable LineBreakConfig mLineBreakConfig; + /** * Builder constructor. * @@ -130,6 +137,7 @@ public class PrecomputedText implements Spannable { mTextDir = params.mTextDir; mBreakStrategy = params.mBreakStrategy; mHyphenationFrequency = params.mHyphenationFrequency; + mLineBreakConfig = params.mLineBreakConfig; } /** @@ -177,24 +185,41 @@ public class PrecomputedText implements Spannable { } /** + * Set the line break config for the text wrapping. + * + * @param lineBreakConfig the newly line break configuration. + * @return this builder, useful for chaining. + * @see StaticLayout.Builder#setLineBreakConfig + */ + public @NonNull Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { + mLineBreakConfig = lineBreakConfig; + return this; + } + + /** * Build the {@link Params}. * * @return the layout parameter */ public @NonNull Params build() { - return new Params(mPaint, mTextDir, mBreakStrategy, mHyphenationFrequency); + return new Params(mPaint, mLineBreakConfig, mTextDir, mBreakStrategy, + mHyphenationFrequency); } } // This is public hidden for internal use. // For the external developers, use Builder instead. /** @hide */ - public Params(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, - @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { + public Params(@NonNull TextPaint paint, + @Nullable LineBreakConfig lineBreakConfig, + @NonNull TextDirectionHeuristic textDir, + @Layout.BreakStrategy int strategy, + @Layout.HyphenationFrequency int frequency) { mPaint = paint; mTextDir = textDir; mBreakStrategy = strategy; mHyphenationFrequency = frequency; + mLineBreakConfig = lineBreakConfig; } /** @@ -233,6 +258,15 @@ public class PrecomputedText implements Spannable { return mHyphenationFrequency; } + /** + * Return the line break configuration for this text. + * + * @return the current line break configuration, null if no line break configuration is set. + */ + public @Nullable LineBreakConfig getLineBreakConfig() { + return mLineBreakConfig; + } + /** @hide */ @IntDef(value = { UNUSABLE, NEED_RECOMPUTE, USABLE }) @Retention(RetentionPolicy.SOURCE) @@ -262,8 +296,9 @@ public class PrecomputedText implements Spannable { /** @hide */ public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy, - @Layout.HyphenationFrequency int frequency) { + @Layout.HyphenationFrequency int frequency, @Nullable LineBreakConfig lbConfig) { if (mBreakStrategy == strategy && mHyphenationFrequency == frequency + && isLineBreakEquals(mLineBreakConfig, lbConfig) && mPaint.equalsForTextMeasurement(paint)) { return mTextDir == textDir ? USABLE : NEED_RECOMPUTE; } else { @@ -272,6 +307,29 @@ public class PrecomputedText implements Spannable { } /** + * Check the two LineBreakConfig instances are equal. + * This method assumes they are equal if one parameter is null and the other parameter has + * a LineBreakStyle value of LineBreakConfig.LINE_BREAK_STYLE_NONE. + * + * @param o1 the first LineBreakConfig instance. + * @param o2 the second LineBreakConfig instance. + * @return true if the two LineBreakConfig instances are equal. + */ + private boolean isLineBreakEquals(LineBreakConfig o1, LineBreakConfig o2) { + if (Objects.equals(o1, o2)) { + return true; + } + if (o1 == null && (o2 != null + && o2.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) { + return true; + } else if (o2 == null && (o1 != null + && o1.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) { + return true; + } + return false; + } + + /** * Check if the same text layout. * * @return true if this and the given param result in the same text layout @@ -286,21 +344,25 @@ public class PrecomputedText implements Spannable { } Params param = (Params) o; return checkResultUsable(param.mPaint, param.mTextDir, param.mBreakStrategy, - param.mHyphenationFrequency) == Params.USABLE; + param.mHyphenationFrequency, param.mLineBreakConfig) == Params.USABLE; } @Override public int hashCode() { // TODO: implement MinikinPaint::hashCode and use it to keep consistency with equals. + int lineBreakStyle = (mLineBreakConfig != null) + ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; return Objects.hash(mPaint.getTextSize(), mPaint.getTextScaleX(), mPaint.getTextSkewX(), mPaint.getLetterSpacing(), mPaint.getWordSpacing(), mPaint.getFlags(), mPaint.getTextLocales(), mPaint.getTypeface(), mPaint.getFontVariationSettings(), mPaint.isElegantTextHeight(), mTextDir, - mBreakStrategy, mHyphenationFrequency); + mBreakStrategy, mHyphenationFrequency, lineBreakStyle); } @Override public String toString() { + int lineBreakStyle = (mLineBreakConfig != null) + ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; return "{" + "textSize=" + mPaint.getTextSize() + ", textScaleX=" + mPaint.getTextScaleX() @@ -313,6 +375,7 @@ public class PrecomputedText implements Spannable { + ", textDir=" + mTextDir + ", breakStrategy=" + mBreakStrategy + ", hyphenationFrequency=" + mHyphenationFrequency + + ", lineBreakStyle=" + lineBreakStyle + "}"; } }; @@ -369,7 +432,8 @@ public class PrecomputedText implements Spannable { final PrecomputedText.Params hintParams = hintPct.getParams(); final @Params.CheckResultUsableResult int checkResult = hintParams.checkResultUsable(params.mPaint, params.mTextDir, - params.mBreakStrategy, params.mHyphenationFrequency); + params.mBreakStrategy, params.mHyphenationFrequency, + params.mLineBreakConfig); switch (checkResult) { case Params.USABLE: return hintPct; @@ -418,9 +482,9 @@ public class PrecomputedText implements Spannable { final int paraStart = pct.getParagraphStart(i); final int paraEnd = pct.getParagraphEnd(i); result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( - params.getTextPaint(), pct, paraStart, paraEnd, params.getTextDirection(), - hyphenationMode, computeLayout, pct.getMeasuredParagraph(i), - null /* no recycle */))); + params.getTextPaint(), params.getLineBreakConfig(), pct, paraStart, paraEnd, + params.getTextDirection(), hyphenationMode, computeLayout, + pct.getMeasuredParagraph(i), null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } @@ -456,8 +520,9 @@ public class PrecomputedText implements Spannable { } result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( - params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(), - hyphenationMode, computeLayout, null /* no hint */, null /* no recycle */))); + params.getTextPaint(), params.getLineBreakConfig(), text, paraStart, paraEnd, + params.getTextDirection(), hyphenationMode, computeLayout, null /* no hint */, + null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } @@ -544,11 +609,11 @@ public class PrecomputedText implements Spannable { public @Params.CheckResultUsableResult int checkResultUsable(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint, @Layout.BreakStrategy int strategy, - @Layout.HyphenationFrequency int frequency) { + @Layout.HyphenationFrequency int frequency, @NonNull LineBreakConfig lbConfig) { if (mStart != start || mEnd != end) { return Params.UNUSABLE; } else { - return mParams.checkResultUsable(paint, textDir, strategy, frequency); + return mParams.checkResultUsable(paint, textDir, strategy, frequency, lbConfig); } } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 4789231b0404..b10fc37bff2f 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Paint; +import android.graphics.text.LineBreakConfig; import android.graphics.text.LineBreaker; import android.os.Build; import android.text.style.LeadingMarginSpan; @@ -403,6 +404,21 @@ public class StaticLayout extends Layout { } /** + * Set the line break configuration. The line break will be passed to native used for + * calculating the text wrapping. The default value of the line break style is + * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} + * + * @param lineBreakConfig the line break configuration for text wrapping. + * @return this builder, useful for chaining. + * @see android.widget.TextView#setLineBreakConfig + */ + @NonNull + public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { + mLineBreakConfig = lineBreakConfig; + return this; + } + + /** * Build the {@link StaticLayout} after options have been set. * * <p>Note: the builder object must not be reused in any way after calling this @@ -438,6 +454,7 @@ public class StaticLayout extends Layout { @Nullable private int[] mRightIndents; private int mJustificationMode; private boolean mAddLastLineLineSpacing; + private LineBreakConfig mLineBreakConfig; private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); @@ -612,7 +629,6 @@ public class StaticLayout extends Layout { TextPaint paint = b.mPaint; int outerWidth = b.mWidth; TextDirectionHeuristic textDir = b.mTextDir; - final boolean fallbackLineSpacing = b.mFallbackLineSpacing; float spacingmult = b.mSpacingMult; float spacingadd = b.mSpacingAdd; float ellipsizedWidth = b.mEllipsizedWidth; @@ -630,6 +646,7 @@ public class StaticLayout extends Layout { mLineCount = 0; mEllipsized = false; mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT; + mFallbackLineSpacing = b.mFallbackLineSpacing; int v = 0; boolean needMultiply = (spacingmult != 1 || spacingadd != 0); @@ -670,7 +687,7 @@ public class StaticLayout extends Layout { PrecomputedText precomputed = (PrecomputedText) source; final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint, - b.mBreakStrategy, b.mHyphenationFrequency); + b.mBreakStrategy, b.mHyphenationFrequency, b.mLineBreakConfig); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: break; @@ -680,6 +697,7 @@ public class StaticLayout extends Layout { .setBreakStrategy(b.mBreakStrategy) .setHyphenationFrequency(b.mHyphenationFrequency) .setTextDirection(textDir) + .setLineBreakConfig(b.mLineBreakConfig) .build(); precomputed = PrecomputedText.create(precomputed, newParams); paragraphInfo = precomputed.getParagraphInfo(); @@ -692,8 +710,8 @@ public class StaticLayout extends Layout { } if (paragraphInfo == null) { - final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir, - b.mBreakStrategy, b.mHyphenationFrequency); + final PrecomputedText.Params param = new PrecomputedText.Params(paint, + b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency); paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart, bufEnd, false /* computeLayout */); } @@ -867,17 +885,17 @@ public class StaticLayout extends Layout { boolean moreChars = (endPos < bufEnd); - final int ascent = fallbackLineSpacing + final int ascent = mFallbackLineSpacing ? Math.min(fmAscent, Math.round(ascents[breakIndex])) : fmAscent; - final int descent = fallbackLineSpacing + final int descent = mFallbackLineSpacing ? Math.max(fmDescent, Math.round(descents[breakIndex])) : fmDescent; // The fallback ascent/descent may be larger than top/bottom of the default font // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected // clipping. - if (fallbackLineSpacing) { + if (mFallbackLineSpacing) { if (ascent < fmTop) { fmTop = ascent; } @@ -1381,6 +1399,11 @@ public class StaticLayout extends Layout { return mEllipsizedWidth; } + @Override + public boolean isFallbackLineSpacingEnabled() { + return mFallbackLineSpacing; + } + /** * Return the total height of this layout. * @@ -1407,6 +1430,7 @@ public class StaticLayout extends Layout { @UnsupportedAppUsage private int mColumns; private int mEllipsizedWidth; + private boolean mFallbackLineSpacing; /** * Keeps track if ellipsize is applied to the text. diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 1a7ec7f99c95..49e21110d679 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -71,6 +71,8 @@ public class TextLine { private Spanned mSpanned; private PrecomputedText mComputed; + private boolean mUseFallbackExtent = false; + // The start and end of a potentially existing ellipsis on this text line. // We use them to filter out replacement and metric affecting spans on ellipsized away chars. private int mEllipsisStart; @@ -141,6 +143,7 @@ public class TextLine { tl.mTabs = null; tl.mChars = null; tl.mComputed = null; + tl.mUseFallbackExtent = false; tl.mMetricAffectingSpanSpanSet.recycle(); tl.mCharacterStyleSpanSet.recycle(); @@ -171,17 +174,20 @@ public class TextLine { * @param ellipsisStart the start of the ellipsis relative to the line * @param ellipsisEnd the end of the ellipsis relative to the line. When there * is no ellipsis, this should be equal to ellipsisStart. + * @param useFallbackLineSpacing true for enabling fallback line spacing. false for disabling + * fallback line spacing. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void set(TextPaint paint, CharSequence text, int start, int limit, int dir, Directions directions, boolean hasTabs, TabStops tabStops, - int ellipsisStart, int ellipsisEnd) { + int ellipsisStart, int ellipsisEnd, boolean useFallbackLineSpacing) { mPaint = paint; mText = text; mStart = start; mLen = limit - start; mDir = dir; mDirections = directions; + mUseFallbackExtent = useFallbackLineSpacing; if (mDirections == null) { throw new IllegalArgumentException("Directions cannot be null"); } @@ -845,6 +851,30 @@ public class TextLine { previousLeading); } + private void expandMetricsFromPaint(TextPaint wp, int start, int end, + int contextStart, int contextEnd, boolean runIsRtl, FontMetricsInt fmi) { + + final int previousTop = fmi.top; + final int previousAscent = fmi.ascent; + final int previousDescent = fmi.descent; + final int previousBottom = fmi.bottom; + final int previousLeading = fmi.leading; + + int count = end - start; + int contextCount = contextEnd - contextStart; + if (mCharsValid) { + wp.getFontMetricsInt(mChars, start, count, contextStart, contextCount, runIsRtl, + fmi); + } else { + wp.getFontMetricsInt(mText, mStart + start, count, mStart + contextStart, contextCount, + runIsRtl, fmi); + } + + updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, + previousLeading); + } + + static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent, int previousDescent, int previousBottom, int previousLeading) { fmi.top = Math.min(fmi.top, previousTop); @@ -949,6 +979,10 @@ public class TextLine { shapeTextRun(consumer, wp, start, end, contextStart, contextEnd, runIsRtl, leftX); } + if (mUseFallbackExtent && fmi != null) { + expandMetricsFromPaint(wp, start, end, contextStart, contextEnd, runIsRtl, fmi); + } + if (c != null) { if (wp.bgColor != 0) { int previousColor = wp.getColor(); diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java index 02fd7b4470f0..a1d6cc8e283a 100644 --- a/core/java/android/text/TextShaper.java +++ b/core/java/android/text/TextShaper.java @@ -222,7 +222,8 @@ public class TextShaper { mp.getDirections(0, count), false /* tabstop is not supported */, null, - -1, -1 // ellipsis is not supported. + -1, -1, // ellipsis is not supported. + false /* fallback line spacing is not used */ ); tl.shape(consumer); } finally { diff --git a/core/java/android/util/Dumpable.java b/core/java/android/util/Dumpable.java new file mode 100644 index 000000000000..79c576d08866 --- /dev/null +++ b/core/java/android/util/Dumpable.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.io.PrintWriter; + +/** + * Represents an object whose state can be dumped into a {@link PrintWriter}. + */ +public interface Dumpable { + + /** + * Gets the name of the {@link Dumpable}. + * + * @return class name, by default. + */ + @NonNull + default String getDumpableName() { + return getClass().getName(); + } + + //TODO(b/149254050): decide whether it should take a ParcelFileDescription as well. + + /** + * Dumps the internal state into the given {@code writer}. + * + * @param writer writer to be written to + * @param args optional list of arguments + */ + void dump(@NonNull PrintWriter writer, @Nullable String[] args); +} diff --git a/core/java/android/util/DumpableContainer.java b/core/java/android/util/DumpableContainer.java new file mode 100644 index 000000000000..04d19dc41926 --- /dev/null +++ b/core/java/android/util/DumpableContainer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.util; + +import android.annotation.NonNull; + +/** + * Objects that contains a list of {@link Dumpable}, which will be dumped when the object itself + * is dumped. + */ +public interface DumpableContainer { + + /** + * Adds the given {@link Dumpable dumpable} to the container. + * + * <p>If a dumpable with the same {@link Dumpable#getDumpableName() name} was added before, this + * call is ignored. + * + * @param dumpable dumpable to be added. + * + * @throws IllegalArgumentException if the {@link Dumpable#getDumpableName() dumpable name} is + * {@code null}. + * + * @return {@code true} if the dumpable was added, {@code false} if the call was ignored. + */ + boolean addDumpable(@NonNull Dumpable dumpable); +} diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java index dc93a473fe44..ccae92e53c5e 100644 --- a/core/java/android/util/SparseDoubleArray.java +++ b/core/java/android/util/SparseDoubleArray.java @@ -105,7 +105,7 @@ public class SparseDoubleArray implements Cloneable { * <p>This differs from {@link #put} because instead of replacing any previous value, it adds * (in the numerical sense) to it. */ - public void add(int key, double summand) { + public void incrementValue(int key, double summand) { final double oldValue = get(key); put(key, oldValue + summand); } diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java index f2bc0c5a34d6..7185972b85bf 100644 --- a/core/java/android/util/SparseLongArray.java +++ b/core/java/android/util/SparseLongArray.java @@ -164,6 +164,30 @@ public class SparseLongArray implements Cloneable { } /** + * Adds a mapping from the specified key to the specified value, + * <b>adding</b> its value to the previous mapping from the specified key if there + * was one. + * + * <p>This differs from {@link #put} because instead of replacing any previous value, it adds + * (in the numerical sense) to it. + * + * @hide + */ + public void incrementValue(int key, long summand) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + mValues[i] += summand; + } else { + i = ~i; + + mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); + mValues = GrowingArrayUtils.insert(mValues, mSize, i, summand); + mSize++; + } + } + + /** * Returns the number of key-value mappings that this SparseLongArray * currently stores. */ diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 41b749eb5f40..bff5426f9c64 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -27,7 +27,7 @@ import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningDetails.SignatureSchemeVersion; -import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.os.Build; @@ -380,7 +380,7 @@ public class ApkSignatureVerifier { // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization // to not need to verify the whole APK when verifyFUll == false. final ZipEntry manifestEntry = jarFile.findEntry( - ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); + ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); if (manifestEntry == null) { return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Package " + apkPath + " has no manifest"); @@ -394,7 +394,7 @@ public class ApkSignatureVerifier { if (ArrayUtils.isEmpty(lastCerts)) { return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " - + ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); + + ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); } lastSigs = convertToSignatures(lastCerts); @@ -407,7 +407,7 @@ public class ApkSignatureVerifier { final String entryName = entry.getName(); if (entryName.startsWith("META-INF/")) continue; - if (entryName.equals(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME)) continue; + if (entryName.equals(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME)) continue; toVerify.add(entry); } diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java index 1ed12f74ba2c..e679f2998ca1 100644 --- a/core/java/android/view/BatchedInputEventReceiver.java +++ b/core/java/android/view/BatchedInputEventReceiver.java @@ -78,7 +78,7 @@ public class BatchedInputEventReceiver extends InputEventReceiver { } } - void doConsumeBatchedInput(long frameTimeNanos) { + protected void doConsumeBatchedInput(long frameTimeNanos) { if (mBatchedInputScheduled) { mBatchedInputScheduled = false; if (consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) { @@ -114,4 +114,38 @@ public class BatchedInputEventReceiver extends InputEventReceiver { } } private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable(); + + /** + * A {@link BatchedInputEventReceiver} that reports events to an {@link InputEventListener}. + * @hide + */ + public static class SimpleBatchedInputEventReceiver extends BatchedInputEventReceiver { + + /** @hide */ + public interface InputEventListener { + /** + * Process the input event. + * @return handled + */ + boolean onInputEvent(InputEvent event); + } + + protected InputEventListener mListener; + + public SimpleBatchedInputEventReceiver(InputChannel inputChannel, Looper looper, + Choreographer choreographer, InputEventListener listener) { + super(inputChannel, looper, choreographer); + mListener = listener; + } + + @Override + public void onInputEvent(InputEvent event) { + boolean handled = false; + try { + handled = mListener.onInputEvent(event); + } finally { + finishInputEvent(event, handled); + } + } + } } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index be172f748b55..9b8523f9b006 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -19,6 +19,9 @@ package android.view; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.FrameInfo; @@ -151,10 +154,15 @@ public final class Choreographer { private static final int MSG_DO_SCHEDULE_VSYNC = 1; private static final int MSG_DO_SCHEDULE_CALLBACK = 2; - // All frame callbacks posted by applications have this token. + // All frame callbacks posted by applications have this token or EXTENDED_FRAME_CALLBACK_TOKEN. private static final Object FRAME_CALLBACK_TOKEN = new Object() { public String toString() { return "FRAME_CALLBACK_TOKEN"; } }; + private static final Object EXTENDED_FRAME_CALLBACK_TOKEN = new Object() { + public String toString() { + return "EXTENDED_FRAME_CALLBACK_TOKEN"; + } + }; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final Object mLock = new Object(); @@ -484,6 +492,24 @@ public final class Choreographer { } /** + * Posts an extended frame callback to run on the next frame. + * <p> + * The callback runs once then is automatically removed. + * </p> + * + * @param callback The extended frame callback to run during the next frame. + * + * @see #removeExtendedFrameCallback + */ + public void postExtendedFrameCallback(@NonNull ExtendedFrameCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, EXTENDED_FRAME_CALLBACK_TOKEN, 0); + } + + /** * Removes callbacks that have the specified action and token. * * @param callbackType The callback type. @@ -573,6 +599,21 @@ public final class Choreographer { } /** + * Removes a previously posted extended frame callback. + * + * @param callback The extended frame callback to remove. + * + * @see #postExtendedFrameCallback + */ + public void removeExtendedFrameCallback(@Nullable ExtendedFrameCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + removeCallbacksInternal(CALLBACK_ANIMATION, callback, EXTENDED_FRAME_CALLBACK_TOKEN); + } + + /** * Gets the time when the current frame started. * <p> * This method provides the time in milliseconds when the frame started being rendered. @@ -673,7 +714,7 @@ public final class Choreographer { * @hide */ public long getVsyncId() { - return mLastVsyncEventData.id; + return mLastVsyncEventData.preferredFrameTimeline().vsyncId; } /** @@ -684,7 +725,7 @@ public final class Choreographer { * @hide */ public long getFrameDeadline() { - return mLastVsyncEventData.frameDeadline; + return mLastVsyncEventData.preferredFrameTimeline().deadline; } void setFPSDivisor(int divisor) { @@ -705,8 +746,9 @@ public final class Choreographer { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#doFrame " + vsyncEventData.id); + "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId); } + FrameData frameData = new FrameData(frameTimeNanos, vsyncEventData); synchronized (mLock) { if (!mFrameScheduled) { traceMessage("Frame not scheduled"); @@ -737,6 +779,7 @@ public final class Choreographer { + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); } frameTimeNanos = startNanos - lastFrameOffset; + frameData.setFrameTimeNanos(-lastFrameOffset); } if (frameTimeNanos < mLastFrameTimeNanos) { @@ -758,8 +801,10 @@ public final class Choreographer { } } - mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id, - vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval); + mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, + vsyncEventData.preferredFrameTimeline().vsyncId, + vsyncEventData.preferredFrameTimeline().deadline, startNanos, + vsyncEventData.frameInterval); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos; @@ -769,17 +814,17 @@ public final class Choreographer { AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); - doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos); mFrameInfo.markAnimationsStart(); - doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos); - doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos, + doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData, frameIntervalNanos); mFrameInfo.markPerformTraversalsStart(); - doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos); - doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, frameIntervalNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); @@ -793,8 +838,9 @@ public final class Choreographer { } } - void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) { + void doCallbacks(int callbackType, FrameData frameData, long frameIntervalNanos) { CallbackRecord callbacks; + long frameTimeNanos = frameData.mFrameTimeNanos; synchronized (mLock) { // We use "now" to determine when callbacks become due because it's possible // for earlier processing phases in a frame to post callbacks that should run @@ -831,6 +877,7 @@ public final class Choreographer { } frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; + frameData.setFrameTimeNanos(frameTimeNanos); } } } @@ -842,7 +889,7 @@ public final class Choreographer { + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); } - c.run(frameTimeNanos); + c.run(frameData); } } finally { synchronized (mLock) { @@ -942,6 +989,130 @@ public final class Choreographer { public void doFrame(long frameTimeNanos); } + /** Holds data that describes one possible VSync frame event to render at. */ + public static class FrameTimeline { + static final FrameTimeline INVALID_FRAME_TIMELINE = new FrameTimeline( + FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE); + + FrameTimeline(long vsyncId, long expectedPresentTimeNanos, long deadlineNanos) { + this.mVsyncId = vsyncId; + this.mExpectedPresentTimeNanos = expectedPresentTimeNanos; + this.mDeadlineNanos = deadlineNanos; + } + + private long mVsyncId; + private long mExpectedPresentTimeNanos; + private long mDeadlineNanos; + + /** + * The id that corresponds to this frame timeline, used to correlate a frame + * produced by HWUI with the timeline data stored in Surface Flinger. + */ + public long getVsyncId() { + return mVsyncId; + } + + /** Sets the vsync ID. */ + void resetVsyncId() { + mVsyncId = FrameInfo.INVALID_VSYNC_ID; + } + + /** + * The time in {@link System#nanoTime()} timebase which this frame is expected to be + * presented. + */ + public long getExpectedPresentTimeNanos() { + return mExpectedPresentTimeNanos; + } + + /** + * The time in {@link System#nanoTime()} timebase which this frame needs to be ready by. + */ + public long getDeadlineNanos() { + return mDeadlineNanos; + } + } + + /** + * The payload for {@link ExtendedFrameCallback} which includes frame information such as when + * the frame started being rendered, and multiple possible frame timelines and their + * information including deadline and expected present time. + */ + public static class FrameData { + static final FrameTimeline[] INVALID_FRAME_TIMELINES = new FrameTimeline[0]; + FrameData() { + this.mFrameTimelines = INVALID_FRAME_TIMELINES; + this.mPreferredFrameTimeline = FrameTimeline.INVALID_FRAME_TIMELINE; + } + + FrameData(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { + FrameTimeline[] frameTimelines = + new FrameTimeline[vsyncEventData.frameTimelines.length]; + for (int i = 0; i < vsyncEventData.frameTimelines.length; i++) { + DisplayEventReceiver.VsyncEventData.FrameTimeline frameTimeline = + vsyncEventData.frameTimelines[i]; + frameTimelines[i] = new FrameTimeline(frameTimeline.vsyncId, + frameTimeline.expectedPresentTime, frameTimeline.deadline); + } + this.mFrameTimeNanos = frameTimeNanos; + this.mFrameTimelines = frameTimelines; + this.mPreferredFrameTimeline = + frameTimelines[vsyncEventData.preferredFrameTimelineIndex]; + } + + private long mFrameTimeNanos; + private final FrameTimeline[] mFrameTimelines; + private final FrameTimeline mPreferredFrameTimeline; + + void setFrameTimeNanos(long frameTimeNanos) { + mFrameTimeNanos = frameTimeNanos; + for (FrameTimeline ft : mFrameTimelines) { + // The ID is no longer valid because the frame time that was registered with the ID + // no longer matches. + // TODO(b/205721584): Ask SF for valid vsync information. + ft.resetVsyncId(); + } + } + + /** The time in nanoseconds when the frame started being rendered. */ + public long getFrameTimeNanos() { + return mFrameTimeNanos; + } + + /** The possible frame timelines, sorted chronologically. */ + @NonNull + @SuppressLint("ArrayReturn") // For API consistency and speed. + public FrameTimeline[] getFrameTimelines() { + return mFrameTimelines; + } + + /** The platform-preferred frame timeline. */ + @NonNull + public FrameTimeline getPreferredFrameTimeline() { + return mPreferredFrameTimeline; + } + } + + /** + * Implement this interface to receive a callback to start the next frame. The callback is + * invoked on the {@link Looper} thread to which the {@link Choreographer} is attached. The + * callback payload contains information about multiple possible frames, allowing choice of + * the appropriate frame based on latency requirements. + * + * @see FrameCallback + */ + public interface ExtendedFrameCallback { + /** + * Called when a new display frame is being rendered. + * + * @param data The payload which includes frame information. Divide nanosecond values by + * {@code 1000000} to convert it to the {@link SystemClock#uptimeMillis()} + * time base. + * @see FrameCallback#doFrame + **/ + void onVsync(@NonNull FrameData data); + } + private final class FrameHandler extends Handler { public FrameHandler(Looper looper) { super(looper); @@ -983,7 +1154,8 @@ public final class Choreographer { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#onVsync " + vsyncEventData.id); + "Choreographer#onVsync " + + vsyncEventData.preferredFrameTimeline().vsyncId); } // Post the vsync event to the Handler. // The idea is to prevent incoming vsync events from completely starving @@ -1026,7 +1198,9 @@ public final class Choreographer { private static final class CallbackRecord { public CallbackRecord next; public long dueTime; - public Object action; // Runnable or FrameCallback + /** Runnable or FrameCallback or ExtendedFrameCallback object. */ + public Object action; + /** Denotes the action type. */ public Object token; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1037,6 +1211,14 @@ public final class Choreographer { ((Runnable)action).run(); } } + + void run(FrameData frameData) { + if (token == EXTENDED_FRAME_CALLBACK_TOKEN) { + ((ExtendedFrameCallback) action).onVsync(frameData); + } else { + run(frameData.getFrameTimeNanos()); + } + } } private final class CallbackQueue { diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 3cc51c7e8b51..70266c1717a1 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -19,6 +19,7 @@ package android.view; import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE; import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -138,6 +139,24 @@ public final class Display { public static final int INVALID_DISPLAY = -1; /** + * Invalid resolution width. + * @hide + */ + public static final int INVALID_DISPLAY_WIDTH = -1; + + /** + * Invalid resolution height. + * @hide + */ + public static final int INVALID_DISPLAY_HEIGHT = -1; + + /** + * Invalid refresh rate. + * @hide + */ + public static final float INVALID_DISPLAY_REFRESH_RATE = 0.0f; + + /** * The default display group id, which is the display group id of the primary display assuming * there is one. * @hide @@ -1170,6 +1189,49 @@ public final class Display { } /** + * Sets the default {@link Display.Mode} to use for the display. The display mode includes + * preference for resolution and refresh rate. + * If the mode specified is not supported by the display, then no mode change occurs. + * + * @param mode The {@link Display.Mode} to set, which can include resolution and/or + * refresh-rate. It is created using {@link Display.Mode.Builder}. + *` + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) + public void setUserPreferredDisplayMode(@NonNull Display.Mode mode) { + // Create a new object containing default values for the unused fields like mode ID and + // alternative refresh rates. + Display.Mode preferredMode = new Display.Mode(mode.getPhysicalWidth(), + mode.getPhysicalHeight(), mode.getRefreshRate()); + mGlobal.setUserPreferredDisplayMode(mDisplayId, preferredMode); + } + + /** + * Removes the display's user preferred display mode. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) + public void clearUserPreferredDisplayMode() { + mGlobal.setUserPreferredDisplayMode(mDisplayId, null); + } + + /** + * Returns the display's user preferred display mode. + * + * @hide + */ + @TestApi + @Nullable + public Display.Mode getUserPreferredDisplayMode() { + return mGlobal.getUserPreferredDisplayMode(mDisplayId); + } + + + /** * Returns whether this display can be used to display wide color gamut content. * This does not necessarily mean the device itself can render wide color gamut * content. To ensure wide color gamut content can be produced, refer to @@ -1710,6 +1772,30 @@ public final class Display { } /** + * Returns true if the specified width is valid. + * @hide + */ + public static boolean isWidthValid(int width) { + return width > 0; + } + + /** + * Returns true if the specified height is valid. + * @hide + */ + public static boolean isHeightValid(int height) { + return height > 0; + } + + /** + * Returns true if the specified refresh-rate is valid. + * @hide + */ + public static boolean isRefreshRateValid(float refreshRate) { + return refreshRate > 0.0f; + } + + /** * A mode supported by a given display. * * @see Display#getSupportedModes() @@ -1846,6 +1932,30 @@ public final class Display { } /** + * Returns {@code true} if this mode matches the given parameters, if those parameters are + * valid.<p> + * If resolution (width and height) is valid and refresh-rate is not, the method matches + * only resolution. + * If refresh-rate is valid and resolution (width and height) is not, the method matches + * only refresh-rate.</p> + * + * @hide + */ + public boolean matchesIfValid(int width, int height, float refreshRate) { + if (!isWidthValid(width) && !isHeightValid(height) + && !isRefreshRateValid(refreshRate)) { + return false; + } + if (isWidthValid(width) != isHeightValid(height)) { + return false; + } + return (!isWidthValid(width) || mWidth == width) + && (!isHeightValid(height) || mHeight == height) + && (!isRefreshRateValid(refreshRate) + || Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate)); + } + + /** * Returns {@code true} if this mode equals to the other mode in all parameters except * the refresh rate. * @@ -1855,6 +1965,24 @@ public final class Display { return mWidth == other.mWidth && mHeight == other.mHeight; } + /** + * Returns {@code true} if refresh-rate is set for a display mode + * + * @hide + */ + public boolean isRefreshRateSet() { + return mRefreshRate != INVALID_DISPLAY_REFRESH_RATE; + } + + /** + * Returns {@code true} if refresh-rate is set for a display mode + * + * @hide + */ + public boolean isResolutionSet() { + return mWidth != INVALID_DISPLAY_WIDTH && mHeight != INVALID_DISPLAY_HEIGHT; + } + @Override public boolean equals(@Nullable Object other) { if (this == other) { @@ -1923,6 +2051,80 @@ public final class Display { return new Mode[size]; } }; + + /** + * Builder is used to create {@link Display.Mode} objects + * + * @hide + */ + @TestApi + public static final class Builder { + private int mWidth; + private int mHeight; + private float mRefreshRate; + + public Builder() { + mWidth = Display.INVALID_DISPLAY_WIDTH; + mHeight = Display.INVALID_DISPLAY_HEIGHT; + mRefreshRate = Display.INVALID_DISPLAY_REFRESH_RATE; + } + + /** + * Sets the resolution (width and height) of a {@link Display.Mode} + * + * @return Instance of {@link Builder} + */ + @NonNull + public Builder setResolution(int width, int height) { + if (width > 0 && height > 0) { + mWidth = width; + mHeight = height; + } + return this; + } + + /** + * Sets the refresh rate of a {@link Display.Mode} + * + * @return Instance of {@link Builder} + */ + @NonNull + public Builder setRefreshRate(float refreshRate) { + if (refreshRate > 0.0f) { + mRefreshRate = refreshRate; + } + return this; + } + + /** + * Creates the {@link Display.Mode} object. + * + * <p> + * If resolution needs to be set, but refresh-rate doesn't matter, create a mode with + * Builder and call setResolution. + * {@code + * Display.Mode mode = + * new Display.Mode.Builder() + * .setResolution(width, height) + * .build(); + * } + * </p><p> + * If refresh-rate needs to be set, but resolution doesn't matter, create a mode with + * Builder and call setRefreshRate. + * {@code + * Display.Mode mode = + * new Display.Mode.Builder() + * .setRefreshRate(refreshRate) + * .build(); + * } + * </p> + */ + @NonNull + public Mode build() { + Display.Mode mode = new Mode(mWidth, mHeight, mRefreshRate); + return mode; + } + } } /** diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index ae323226d9cc..9889eaaf12e3 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -1249,4 +1249,119 @@ public final class DisplayCutout { return String.valueOf(mInner); } } + + /** + * A Builder class to construct a DisplayCutout instance. + * + * <p>Note that this is only for tests purpose. For production code, developers should always + * use a {@link DisplayCutout} obtained from the system.</p> + */ + public static final class Builder { + private Insets mSafeInsets = Insets.NONE; + private Insets mWaterfallInsets = Insets.NONE; + private Path mCutoutPath; + private final Rect mBoundingRectLeft = new Rect(); + private final Rect mBoundingRectTop = new Rect(); + private final Rect mBoundingRectRight = new Rect(); + private final Rect mBoundingRectBottom = new Rect(); + + /** + * Begin building a DisplayCutout. + */ + public Builder() { + } + + /** + * Construct a new {@link DisplayCutout} with the set parameters. + */ + @NonNull + public DisplayCutout build() { + final CutoutPathParserInfo info; + if (mCutoutPath != null) { + // Create a fake CutoutPathParserInfo and set it to sCachedCutoutPathParserInfo so + // that when getCutoutPath() is called, it will return the cached Path. + info = new CutoutPathParserInfo(0, 0, 0, "test", 0, 1f); + synchronized (CACHE_LOCK) { + DisplayCutout.sCachedCutoutPathParserInfo = info; + DisplayCutout.sCachedCutoutPath = mCutoutPath; + } + } else { + info = null; + } + return new DisplayCutout(mSafeInsets.toRect(), mWaterfallInsets, mBoundingRectLeft, + mBoundingRectTop, mBoundingRectRight, mBoundingRectBottom, info, false); + } + + /** + * Set the safe insets. If not set, the default value is {@link Insets#NONE}. + */ + @SuppressWarnings("MissingGetterMatchingBuilder") + @NonNull + public Builder setSafeInsets(@NonNull Insets safeInsets) { + mSafeInsets = safeInsets; + return this; + } + + /** + * Set the waterfall insets of the DisplayCutout. If not set, the default value is + * {@link Insets#NONE} + */ + @NonNull + public Builder setWaterfallInsets(@NonNull Insets waterfallInsets) { + mWaterfallInsets = waterfallInsets; + return this; + } + + /** + * Set a bounding rectangle for a non-functional area on the display which is located on + * the left of the screen. If not set, the default value is an empty rectangle. + */ + @NonNull + public Builder setBoundingRectLeft(@NonNull Rect boundingRectLeft) { + mBoundingRectLeft.set(boundingRectLeft); + return this; + } + + /** + * Set a bounding rectangle for a non-functional area on the display which is located on + * the top of the screen. If not set, the default value is an empty rectangle. + */ + @NonNull + public Builder setBoundingRectTop(@NonNull Rect boundingRectTop) { + mBoundingRectTop.set(boundingRectTop); + return this; + } + + /** + * Set a bounding rectangle for a non-functional area on the display which is located on + * the right of the screen. If not set, the default value is an empty rectangle. + */ + @NonNull + public Builder setBoundingRectRight(@NonNull Rect boundingRectRight) { + mBoundingRectRight.set(boundingRectRight); + return this; + } + + /** + * Set a bounding rectangle for a non-functional area on the display which is located on + * the bottom of the screen. If not set, the default value is an empty rectangle. + */ + @NonNull + public Builder setBoundingRectBottom(@NonNull Rect boundingRectBottom) { + mBoundingRectBottom.set(boundingRectBottom); + return this; + } + + /** + * Set the cutout {@link Path}. + * + * Note that not support creating/testing multiple display cutouts with setCutoutPath() in + * parallel. + */ + @NonNull + public Builder setCutoutPath(@NonNull Path cutoutPath) { + mCutoutPath = cutoutPath; + return this; + } + } } diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 5c086328bda7..774bab41fb9a 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -138,13 +138,28 @@ public abstract class DisplayEventReceiver { } static final class VsyncEventData { - // The frame timeline vsync id, used to correlate a frame - // produced by HWUI with the timeline data stored in Surface Flinger. - public final long id; - // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is - // allotted for the frame to be completed. - public final long frameDeadline; + static final FrameTimeline[] INVALID_FRAME_TIMELINES = + {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)}; + + public static class FrameTimeline { + FrameTimeline(long vsyncId, long expectedPresentTime, long deadline) { + this.vsyncId = vsyncId; + this.expectedPresentTime = expectedPresentTime; + this.deadline = deadline; + } + + // The frame timeline vsync id, used to correlate a frame + // produced by HWUI with the timeline data stored in Surface Flinger. + public final long vsyncId; + + // The frame timestamp for when the frame is expected to be presented. + public final long expectedPresentTime; + + // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is + // allotted for the frame to be completed. + public final long deadline; + } /** * The current interval between frames in ns. This will be used to align @@ -153,16 +168,27 @@ public abstract class DisplayEventReceiver { */ public final long frameInterval; - VsyncEventData(long id, long frameDeadline, long frameInterval) { - this.id = id; - this.frameDeadline = frameDeadline; + public final FrameTimeline[] frameTimelines; + + public final int preferredFrameTimelineIndex; + + // Called from native code. + @SuppressWarnings("unused") + VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex, + long frameInterval) { + this.frameTimelines = frameTimelines; + this.preferredFrameTimelineIndex = preferredFrameTimelineIndex; this.frameInterval = frameInterval; } VsyncEventData() { - this.id = FrameInfo.INVALID_VSYNC_ID; - this.frameDeadline = Long.MAX_VALUE; this.frameInterval = -1; + this.frameTimelines = INVALID_FRAME_TIMELINES; + this.preferredFrameTimelineIndex = 0; + } + + public FrameTimeline preferredFrameTimeline() { + return frameTimelines[preferredFrameTimelineIndex]; } } @@ -256,9 +282,8 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame, - long frameTimelineVsyncId, long frameDeadline, long frameInterval) { - onVsync(timestampNanos, physicalDisplayId, frame, - new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval)); + VsyncEventData vsyncEventData) { + onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData); } // Called from native code. diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index c5bc99d042d7..45b65e551305 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -177,6 +177,10 @@ public class HapticFeedbackConstants { * Flag for {@link View#performHapticFeedback(int, int) * View.performHapticFeedback(int, int)}: Ignore the global setting * for whether to perform haptic feedback, do it always. + * + * @deprecated Starting from {@link android.os.Build.VERSION_CODES#TIRAMISU} only privileged + * apps can ignore user settings for touch feedback. */ + @Deprecated public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002; } diff --git a/core/java/android/view/ISurfaceControlViewHost.aidl b/core/java/android/view/ISurfaceControlViewHost.aidl new file mode 100644 index 000000000000..fc9661a0e61a --- /dev/null +++ b/core/java/android/view/ISurfaceControlViewHost.aidl @@ -0,0 +1,28 @@ +/* +** Copyright 2021, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.view; + +import android.content.res.Configuration; + +/** + * API from content embedder back to embedded content in SurfaceControlViewHost + * {@hide} + */ +oneway interface ISurfaceControlViewHost { + void onConfigurationChanged(in Configuration newConfig); + void onDispatchDetachedFromWindow(); +} diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index adb8b86493d5..0ef585478346 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -478,10 +478,15 @@ public final class MotionEvent extends InputEvent implements Parcelable { public static final int FLAG_IS_GENERATED_GESTURE = 0x8; /** - * This flag associated with {@link #ACTION_POINTER_UP}, this indicates that the pointer - * has been canceled. Typically this is used for palm event when the user has accidental - * touches. - * @hide + * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}. + * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED + * is set, the typical actions that occur in response for a pointer going up (such as click + * handlers, end of drawing) should be aborted. This flag is typically set when the user was + * accidentally touching the screen, such as by gripping the device, or placing the palm on the + * screen. + * + * @see #ACTION_POINTER_UP + * @see #ACTION_CANCEL */ public static final int FLAG_CANCELED = 0x20; diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index d160be59cca3..43df294466db 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -80,6 +80,7 @@ per-file Inset*.aidl = file:/services/core/java/com/android/server/wm/OWNERS per-file IPinnedStackListener.aidl = file:/services/core/java/com/android/server/wm/OWNERS per-file IRecents*.aidl = file:/services/core/java/com/android/server/wm/OWNERS per-file IRemote*.aidl = file:/services/core/java/com/android/server/wm/OWNERS +per-file ISurfaceControlViewHost*.aidl = file:/services/core/java/com/android/server/wm/OWNERS per-file IWindow*.aidl = file:/services/core/java/com/android/server/wm/OWNERS per-file RemoteAnimation*.java = file:/services/core/java/com/android/server/wm/OWNERS per-file RemoteAnimation*.aidl = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/core/java/android/view/OnBackInvokedCallback.java b/core/java/android/view/OnBackInvokedCallback.java new file mode 100644 index 000000000000..b5cd89cfa4e1 --- /dev/null +++ b/core/java/android/view/OnBackInvokedCallback.java @@ -0,0 +1,70 @@ +/* + * 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 android.view; + +import android.app.Activity; +import android.app.Dialog; + +/** + * Interface for applications to register back invocation callbacks. This allows the client + * to customize various back behaviors by overriding the corresponding callback methods. + * + * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, held + * by classes that implement {@link OnBackInvokedDispatcherOwner} (such as {@link Activity}, + * {@link Dialog} and {@link View}). + * + * Under the hood callbacks are registered at window level. When back is triggered, + * callbacks on the in-focus window are invoked in reverse order in which they are added + * within the same priority. Between different pirorities, callbacks with higher priority + * are invoked first. + * + * See {@link OnBackInvokedDispatcher#registerOnBackInvokedCallback(OnBackInvokedCallback, int)} + * for specifying callback priority. + */ +public interface OnBackInvokedCallback { + /** + * Called when a back gesture has been started, or back button has been pressed down. + * + * @hide + */ + default void onBackStarted() { }; + + /** + * Called on back gesture progress. + * + * @param touchX Absolute X location of the touch point. + * @param touchY Absolute Y location of the touch point. + * @param progress Value between 0 and 1 on how far along the back gesture is. + * + * @hide + */ + // TODO(b/210539672): combine back progress params into BackEvent. + default void onBackProgressed(int touchX, int touchY, float progress) { }; + + /** + * Called when a back gesture or back button press has been cancelled. + * + * @hide + */ + default void onBackCancelled() { }; + + /** + * Called when a back gesture has been completed and committed, or back button pressed + * has been released and committed. + */ + default void onBackInvoked() { }; +} diff --git a/core/java/android/view/OnBackInvokedDispatcher.java b/core/java/android/view/OnBackInvokedDispatcher.java new file mode 100644 index 000000000000..05c312b56cc7 --- /dev/null +++ b/core/java/android/view/OnBackInvokedDispatcher.java @@ -0,0 +1,76 @@ +/* + * 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 android.view; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SuppressLint; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Dispatcher to register {@link OnBackInvokedCallback} instances for handling + * back invocations. + * + * It also provides interfaces to update the attributes of {@link OnBackInvokedCallback}. + * Attribute updates are proactively pushed to the window manager if they change the dispatch + * target (a.k.a. the callback to be invoked next), or its behavior. + */ +public abstract class OnBackInvokedDispatcher { + /** @hide */ + @IntDef({ + PRIORITY_DEFAULT, + PRIORITY_OVERLAY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Priority{} + + /** + * Priority level of {@link OnBackInvokedCallback}s for overlays such as menus and + * navigation drawers that should receive back dispatch before non-overlays. + */ + public static final int PRIORITY_OVERLAY = 1000000; + + /** + * Default priority level of {@link OnBackInvokedCallback}s. + */ + public static final int PRIORITY_DEFAULT = 0; + + /** + * Registers a {@link OnBackInvokedCallback}. + * + * Within the same priority level, callbacks are invoked in the reverse order in which + * they are registered. Higher priority callbacks are invoked before lower priority ones. + * + * @param callback The callback to be registered. If the callback instance has been already + * registered, the existing instance (no matter its priority) will be + * unregistered and registered again. + * @param priority The priority of the callback. + */ + @SuppressLint("SamShouldBeLast") + public abstract void registerOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback, @Priority int priority); + + /** + * Unregisters a {@link OnBackInvokedCallback}. + * + * @param callback The callback to be unregistered. Does nothing if the callback has not been + * registered. + */ + public abstract void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback); +} diff --git a/core/java/android/view/OnBackInvokedDispatcherOwner.java b/core/java/android/view/OnBackInvokedDispatcherOwner.java new file mode 100644 index 000000000000..0e14ed4cdb07 --- /dev/null +++ b/core/java/android/view/OnBackInvokedDispatcherOwner.java @@ -0,0 +1,33 @@ +/* + * 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 android.view; + +import android.annotation.Nullable; + +/** + * A class that provides an {@link OnBackInvokedDispatcher} that allows you to register + * an {@link OnBackInvokedCallback} for handling the system back invocation behavior. + */ +public interface OnBackInvokedDispatcherOwner { + /** + * Returns the {@link OnBackInvokedDispatcher} that should dispatch the back invocation + * to its registered {@link OnBackInvokedCallback}s. + * Returns null when the root view is not attached to a window or a view tree with a decor. + */ + @Nullable + OnBackInvokedDispatcher getOnBackInvokedDispatcher(); +} diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index a6c5042db275..85a9dbd736ed 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.Context; +import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.IBinder; import android.os.Parcel; @@ -45,6 +46,35 @@ public class SurfaceControlViewHost { private SurfaceControl mSurfaceControl; private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; + private final class ISurfaceControlViewHostImpl extends ISurfaceControlViewHost.Stub { + @Override + public void onConfigurationChanged(Configuration configuration) { + if (mViewRoot == null) { + return; + } + mViewRoot.mHandler.post(() -> { + if (mWm != null) { + mWm.setConfiguration(configuration); + } + if (mViewRoot != null) { + mViewRoot.forceWmRelayout(); + } + }); + } + + @Override + public void onDispatchDetachedFromWindow() { + if (mViewRoot == null) { + return; + } + mViewRoot.mHandler.post(() -> { + release(); + }); + } + } + + private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl(); + /** * Package encapsulating a Surface hierarchy which contains interactive view * elements. It's expected to get this object from @@ -71,12 +101,14 @@ public class SurfaceControlViewHost { private SurfaceControl mSurfaceControl; private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; private final IBinder mInputToken; + private final ISurfaceControlViewHost mRemoteInterface; SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection, - IBinder inputToken) { + IBinder inputToken, ISurfaceControlViewHost ri) { mSurfaceControl = sc; mAccessibilityEmbeddedConnection = connection; mInputToken = inputToken; + mRemoteInterface = ri; } /** @@ -97,6 +129,7 @@ public class SurfaceControlViewHost { } mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection; mInputToken = other.mInputToken; + mRemoteInterface = other.mRemoteInterface; } private SurfacePackage(Parcel in) { @@ -105,6 +138,8 @@ public class SurfaceControlViewHost { mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface( in.readStrongBinder()); mInputToken = in.readStrongBinder(); + mRemoteInterface = ISurfaceControlViewHost.Stub.asInterface( + in.readStrongBinder()); } /** @@ -126,6 +161,13 @@ public class SurfaceControlViewHost { return mAccessibilityEmbeddedConnection; } + /** + * @hide + */ + public ISurfaceControlViewHost getRemoteInterface() { + return mRemoteInterface; + } + @Override public int describeContents() { return 0; @@ -136,6 +178,7 @@ public class SurfaceControlViewHost { mSurfaceControl.writeToParcel(out, flags); out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder()); out.writeStrongBinder(mInputToken); + out.writeStrongBinder(mRemoteInterface.asBinder()); } /** @@ -231,7 +274,7 @@ public class SurfaceControlViewHost { public @Nullable SurfacePackage getSurfacePackage() { if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) { return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection, - mViewRoot.getInputToken()); + mViewRoot.getInputToken(), mRemoteInterface); } else { return null; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2307a039bfa5..2126fd523c45 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -834,7 +834,7 @@ import java.util.function.Predicate; */ @UiThread public class View implements Drawable.Callback, KeyEvent.Callback, - AccessibilityEventSource { + AccessibilityEventSource, OnBackInvokedDispatcherOwner { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static final boolean DBG = false; @@ -8663,12 +8663,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAttachInfo == null) { return; } + RectF position = mAttachInfo.mTmpTransformRect; + getBoundsToScreenInternal(position, clipToParent); + outRect.set(Math.round(position.left), Math.round(position.top), + Math.round(position.right), Math.round(position.bottom)); + } + /** + * Gets the location of this view in screen coordinates. + * + * @param outRect The output location + * @param clipToParent Whether to clip child bounds to the parent ones. + * @hide + */ + public void getBoundsOnScreen(RectF outRect, boolean clipToParent) { + if (mAttachInfo == null) { + return; + } RectF position = mAttachInfo.mTmpTransformRect; + getBoundsToScreenInternal(position, clipToParent); + outRect.set(position.left, position.top, position.right, position.bottom); + } + + private void getBoundsToScreenInternal(RectF position, boolean clipToParent) { position.set(0, 0, mRight - mLeft, mBottom - mTop); mapRectFromViewToScreenCoords(position, clipToParent); - outRect.set(Math.round(position.left), Math.round(position.top), - Math.round(position.right), Math.round(position.bottom)); } /** @@ -14248,34 +14267,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, hideTooltip(); return true; } - } - - if (action == R.id.accessibilityActionDragDrop) { - if (!canAcceptAccessibilityDrop()) { + case R.id.accessibilityActionDragDrop: { + if (!canAcceptAccessibilityDrop()) { + return false; + } + try { + if (mAttachInfo != null && mAttachInfo.mSession != null) { + final int[] location = new int[2]; + getLocationInWindow(location); + final int centerX = location[0] + getWidth() / 2; + final int centerY = location[1] + getHeight() / 2; + return mAttachInfo.mSession.dropForAccessibility(mAttachInfo.mWindow, + centerX, centerY); + } + } catch (RemoteException e) { + Log.e(VIEW_LOG_TAG, "Unable to drop for accessibility", e); + } return false; } - try { - if (mAttachInfo != null && mAttachInfo.mSession != null) { - final int[] location = new int[2]; - getLocationInWindow(location); - final int centerX = location[0] + getWidth() / 2; - final int centerY = location[1] + getHeight() / 2; - return mAttachInfo.mSession.dropForAccessibility(mAttachInfo.mWindow, - centerX, centerY); + case R.id.accessibilityActionDragCancel: { + if (!startedSystemDragForAccessibility()) { + return false; + } + if (mAttachInfo != null && mAttachInfo.mDragToken != null) { + cancelDragAndDrop(); + return true; } - } catch (RemoteException e) { - Log.e(VIEW_LOG_TAG, "Unable to drop for accessibility", e); - } - return false; - } else if (action == R.id.accessibilityActionDragCancel) { - if (!startedSystemDragForAccessibility()) { return false; } - if (mAttachInfo != null && mAttachInfo.mDragToken != null) { - cancelDragAndDrop(); - return true; - } - return false; } return false; } @@ -31257,4 +31276,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } return null; } + + /** + * Returns the {@link OnBackInvokedDispatcher} instance of the window this view is attached to. + * + * @return The {@link OnBackInvokedDispatcher} or {@code null} if the view is neither attached + * to a window or a view tree with a decor. + */ + @Nullable + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + ViewParent parent = getParent(); + if (parent instanceof View) { + return ((View) parent).getOnBackInvokedDispatcher(); + } else if (parent instanceof ViewRootImpl) { + // Get the fallback dispatcher on {@link ViewRootImpl} if the view tree doesn't have + // a {@link com.android.internal.policy.DecorView}. + return ((ViewRootImpl) parent).getOnBackInvokedDispatcher(); + } + return null; + } } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 07d5fc533a3a..25e0eca12bf3 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -1896,7 +1896,7 @@ public class ViewDebug { private Canvas mCanvas; private Bitmap mBitmap; - private boolean mEnabledHwBitmapsInSwMode; + private boolean mEnabledHwFeaturesInSwMode; @Override public Canvas getCanvas(View view, int width, int height) { @@ -1913,7 +1913,7 @@ public class ViewDebug { if (mCanvas == null) { mCanvas = new Canvas(); } - mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled(); + mEnabledHwFeaturesInSwMode = mCanvas.isHwFeaturesInSwModeEnabled(); mCanvas.setBitmap(mBitmap); return mCanvas; } @@ -1921,7 +1921,7 @@ public class ViewDebug { @Override public Bitmap createBitmap() { mCanvas.setBitmap(null); - mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode); + mCanvas.setHwFeaturesInSwModeEnabled(mEnabledHwFeaturesInSwMode); return mBitmap; } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 77185116292e..cec8d4c89c5e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -192,6 +192,7 @@ import android.view.contentcapture.MainContentCaptureSession; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.window.ClientWindowFrames; +import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -309,6 +310,12 @@ public final class ViewRootImpl implements ViewParent, private @SurfaceControl.BufferTransform int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; /** + * The fallback {@link OnBackInvokedDispatcher} when the window doesn't have a decor view. + */ + private WindowOnBackInvokedDispatcher mFallbackOnBackInvokedDispatcher = + new WindowOnBackInvokedDispatcher(); + + /** * Callback for notifying about global configuration changes. */ public interface ConfigChangedCallback { @@ -871,6 +878,7 @@ public final class ViewRootImpl implements ViewParent, mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled(); mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; + mFallbackOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow); } public static void addFirstDrawHandler(Runnable callback) { @@ -1120,6 +1128,9 @@ public final class ViewRootImpl implements ViewParent, if (pendingInsetsController != null) { pendingInsetsController.replayAndAttach(mInsetsController); } + ((RootViewSurfaceTaker) mView) + .provideWindowOnBackInvokedDispatcher() + .attachToWindow(mWindowSession, mWindow); } try { @@ -10634,9 +10645,26 @@ public final class ViewRootImpl implements ViewParent, } mBLASTDrawConsumer = consume; return true; - } + } boolean wasRelayoutRequested() { return mRelayoutRequested; } + + void forceWmRelayout() { + mForceNextWindowRelayout = true; + scheduleTraversals(); + } + + /** + * Returns the {@link OnBackInvokedDispatcher} on the decor view if one exists, or the + * fallback {@link OnBackInvokedDispatcher} instance. + */ + @Nullable + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + if (mView instanceof RootViewSurfaceTaker) { + return ((RootViewSurfaceTaker) mView).provideWindowOnBackInvokedDispatcher(); + } + return mFallbackOnBackInvokedDispatcher; + } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cd9f3eb65bc8..5be3a57e8527 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3581,7 +3581,8 @@ public interface WindowManager extends ViewManager { /** * If specified, the insets provided by this window will be our window frame minus the - * insets specified by providedInternalInsets. + * insets specified by providedInternalInsets. This should not be used together with + * {@link WindowState#mGivenContentInsets}. If both of them are set, both will be applied. * * @hide */ diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index d699194dd77f..5176f9bb0f93 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -87,7 +87,7 @@ public class WindowlessWindowManager implements IWindowSession { mHostInputToken = hostInputToken; } - protected void setConfiguration(Configuration configuration) { + public void setConfiguration(Configuration configuration) { mConfiguration.setTo(configuration); } @@ -330,6 +330,7 @@ public class WindowlessWindowManager implements IWindowSession { public void setInsets(android.view.IWindow window, int touchableInsets, android.graphics.Rect contentInsets, android.graphics.Rect visibleInsets, android.graphics.Region touchableRegion) { + setTouchRegion(window.asBinder(), touchableRegion); } @Override diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 7a33507e9d78..e54ed18c51a6 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1766,6 +1766,74 @@ public final class AccessibilityManager { } } + /** + * Sets the system audio caption enabled state. + * + * @param isEnabled The system audio captioning enabled state. + * @param userId The user Id. + * @hide + */ + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.setSystemAudioCaptioningRequested(isEnabled, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Gets the system audio caption UI enabled state. + * + * @param userId The user Id. + * @return the system audio caption UI enabled state. + * @hide + */ + public boolean isSystemAudioCaptioningUiRequested(int userId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return false; + } + } + try { + return service.isSystemAudioCaptioningUiRequested(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Sets the system audio caption UI enabled state. + * + * @param isEnabled The system audio captioning UI enabled state. + * @param userId The user Id. + * @hide + */ + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.setSystemAudioCaptioningUiRequested(isEnabled, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + private IAccessibilityManager getServiceLocked() { if (mService == null) { tryConnectToServiceLocked(null); diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index db7c6634c8a7..0a33d6cd82ea 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -4321,15 +4321,13 @@ public class AccessibilityNodeInfo implements Parcelable { return "ACTION_PRESS_AND_HOLD"; case R.id.accessibilityActionImeEnter: return "ACTION_IME_ENTER"; + case R.id.accessibilityActionDragStart: + return "ACTION_DRAG"; + case R.id.accessibilityActionDragCancel: + return "ACTION_CANCEL_DRAG"; + case R.id.accessibilityActionDragDrop: + return "ACTION_DROP"; default: - // TODO(197520937): Use finalized constants in switch - if (action == R.id.accessibilityActionDragStart) { - return "ACTION_DRAG"; - } else if (action == R.id.accessibilityActionDragCancel) { - return "ACTION_CANCEL_DRAG"; - } else if (action == R.id.accessibilityActionDragDrop) { - return "ACTION_DROP"; - } return "ACTION_UNKNOWN"; } } diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java index 3d68692a3b5c..05c74f2d0111 100644 --- a/core/java/android/view/accessibility/CaptioningManager.java +++ b/core/java/android/view/accessibility/CaptioningManager.java @@ -16,12 +16,16 @@ package android.view.accessibility; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; +import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Color; import android.graphics.Typeface; @@ -30,6 +34,8 @@ import android.os.Handler; import android.provider.Settings.Secure; import android.text.TextUtils; +import com.android.internal.R; + import java.util.ArrayList; import java.util.Locale; @@ -41,6 +47,7 @@ import java.util.Locale; public class CaptioningManager { /** Default captioning enabled value. */ private static final int DEFAULT_ENABLED = 0; + private static final boolean SYSTEM_AUDIO_CAPTIONING_DEFAULT_ENABLED = false; /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */ private static final int DEFAULT_PRESET = 0; @@ -51,6 +58,9 @@ public class CaptioningManager { private final ArrayList<CaptioningChangeListener> mListeners = new ArrayList<>(); private final ContentResolver mContentResolver; private final ContentObserver mContentObserver; + private final Resources mResources; + private final Context mContext; + private final AccessibilityManager mAccessibilityManager; /** * Creates a new captioning manager for the specified context. @@ -58,10 +68,13 @@ public class CaptioningManager { * @hide */ public CaptioningManager(Context context) { + mContext = context; mContentResolver = context.getContentResolver(); + mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); final Handler handler = new Handler(context.getMainLooper()); mContentObserver = new MyContentObserver(handler); + mResources = context.getResources(); } /** @@ -137,6 +150,60 @@ public class CaptioningManager { } /** + * @return the system audio caption enabled state. + */ + public final boolean isSystemAudioCaptioningRequested() { + return Secure.getIntForUser(mContentResolver, Secure.ODI_CAPTIONS_ENABLED, + SYSTEM_AUDIO_CAPTIONING_DEFAULT_ENABLED ? 1 : 0, mContext.getUserId()) == 1; + } + + /** + * Sets the system audio caption enabled state. + * + * @param isEnabled The system audio captioning enabled state. + * + * @throws SecurityException if the caller does not have permission + * {@link Manifest.permission#SET_SYSTEM_AUDIO_CAPTION} + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public final void setSystemAudioCaptioningRequested(boolean isEnabled) { + if (mAccessibilityManager != null) { + mAccessibilityManager.setSystemAudioCaptioningRequested(isEnabled, + mContext.getUserId()); + } + } + + /** + * @return the system audio caption UI enabled state. + */ + public final boolean isSystemAudioCaptioningUiRequested() { + return mAccessibilityManager != null + && mAccessibilityManager.isSystemAudioCaptioningUiRequested(mContext.getUserId()); + } + + /** + * Sets the system audio caption UI enabled state. + * + * @param isEnabled The system audio captioning UI enabled state. + * + * @throws SecurityException if the caller does not have permission + * {@link Manifest.permission#SET_SYSTEM_AUDIO_CAPTION} + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public final void setSystemAudioCaptioningUiRequested(boolean isEnabled) { + if (mAccessibilityManager != null) { + mAccessibilityManager.setSystemAudioCaptioningUiRequested(isEnabled, + mContext.getUserId()); + } + } + + /** * Adds a listener for changes in the user's preferred captioning enabled * state and visual properties. * @@ -155,6 +222,8 @@ public class CaptioningManager { registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_PRESET); + registerObserver(Secure.ODI_CAPTIONS_ENABLED); + registerObserver(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED); } mListeners.add(listener); @@ -181,6 +250,13 @@ public class CaptioningManager { } } + /** + * Returns true if system wide call captioning is enabled for this device. + */ + public boolean isCallCaptioningEnabled() { + return mResources.getBoolean(R.bool.config_systemCaptionsServiceCallsEnabled); + } + private void notifyEnabledChanged() { final boolean enabled = isEnabled(); synchronized (mListeners) { @@ -217,6 +293,24 @@ public class CaptioningManager { } } + private void notifySystemAudioCaptionChanged() { + final boolean enabled = isSystemAudioCaptioningRequested(); + synchronized (mListeners) { + for (CaptioningChangeListener listener : mListeners) { + listener.onSystemAudioCaptioningChanged(enabled); + } + } + } + + private void notifySystemAudioCaptionUiChanged() { + final boolean enabled = isSystemAudioCaptioningUiRequested(); + synchronized (mListeners) { + for (CaptioningChangeListener listener : mListeners) { + listener.onSystemAudioCaptioningUiChanged(enabled); + } + } + } + private class MyContentObserver extends ContentObserver { private final Handler mHandler; @@ -236,6 +330,10 @@ public class CaptioningManager { notifyLocaleChanged(); } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) { notifyFontScaleChanged(); + } else if (Secure.ODI_CAPTIONS_ENABLED.equals(name)) { + notifySystemAudioCaptionChanged(); + } else if (Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED.equals(name)) { + notifySystemAudioCaptionUiChanged(); } else { // We only need a single callback when multiple style properties // change in rapid succession. @@ -553,5 +651,51 @@ public class CaptioningManager { * @see CaptioningManager#getFontScale() */ public void onFontScaleChanged(float fontScale) {} + + + /** + * Called when the system audio caption enabled state changes. + * + * @param enabled the system audio caption enabled state + */ + public void onSystemAudioCaptioningChanged(boolean enabled) {} + + /** + * Called when the system audio caption UI enabled state changes. + * + * @param enabled the system audio caption UI enabled state + */ + public void onSystemAudioCaptioningUiChanged(boolean enabled) {} + } + + /** + * Interface for accessing the system audio captioning related secure setting keys. + * + * @hide + */ + public interface SystemAudioCaptioningAccessing { + /** + * Sets the system audio caption enabled state. + * + * @param isEnabled The system audio captioning enabled state. + * @param userId The user Id. + */ + void setSystemAudioCaptioningRequested(boolean isEnabled, int userId); + + /** + * Gets the system audio caption UI enabled state. + * + * @param userId The user Id. + * @return the system audio caption UI enabled state. + */ + boolean isSystemAudioCaptioningUiRequested(int userId); + + /** + * Sets the system audio caption UI enabled state. + * + * @param isEnabled The system audio captioning UI enabled state. + * @param userId The user Id. + */ + void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId); } } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 4e8d2da70159..645ddf5542f7 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -100,4 +100,14 @@ interface IAccessibilityManager { int getFocusColor(); boolean isAudioDescriptionByDefaultEnabled(); + + // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION + // System process only + void setSystemAudioCaptioningRequested(boolean isEnabled, int userId); + + boolean isSystemAudioCaptioningUiRequested(int userId); + + // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION + // System process only + void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId); } diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index b1d618eff40a..ab749ee284a8 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -209,6 +209,13 @@ public abstract class Animation implements Cloneable { private boolean mShowWallpaper; private boolean mHasRoundedCorners; + /** + * Whether to show a background behind the windows during the animation. + * @see #getShowBackground() + * @see #setShowBackground(boolean) + */ + private boolean mShowBackground; + private boolean mMore = true; private boolean mOneMoreTime = true; @@ -266,6 +273,8 @@ public abstract class Animation implements Cloneable { a.getBoolean(com.android.internal.R.styleable.Animation_showWallpaper, false)); setHasRoundedCorners( a.getBoolean(com.android.internal.R.styleable.Animation_hasRoundedCorners, false)); + setShowBackground( + a.getBoolean(com.android.internal.R.styleable.Animation_showBackground, false)); final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0); @@ -698,6 +707,24 @@ public abstract class Animation implements Cloneable { } /** + * If showBackground is {@code true} and this animation is applied on a window, then the windows + * in the animation will animate with the background associated with this window behind them. + * + * The background comes from the {@link android.R.styleable#Theme_colorBackground} that is + * applied to this window through its theme. + * + * If multiple animating windows have showBackground set to {@code true} during an animation, + * the top most window with showBackground set to {@code true} and a valid background color + * takes precedence. + * + * @param showBackground Whether to show a background behind the windows during the animation. + * @attr ref android.R.styleable#Animation_showBackground + */ + public void setShowBackground(boolean showBackground) { + mShowBackground = showBackground; + } + + /** * Gets the acceleration curve type for this animation. * * @return the {@link Interpolator} associated to this animation @@ -838,6 +865,24 @@ public abstract class Animation implements Cloneable { } /** + * If showBackground is {@code true} and this animation is applied on a window, then the windows + * in the animation will animate with the background associated with this window behind them. + * + * The background comes from the {@link android.R.styleable#Theme_colorBackground} that is + * applied to this window through its theme. + * + * If multiple animating windows have showBackground set to {@code true} during an animation, + * the top most window with showBackground set to {@code true} and a valid background color + * takes precedence. + * + * @return if the background of this window should be shown behind the animating windows. + * @attr ref android.R.styleable#Animation_showBackground + */ + public boolean getShowBackground() { + return mShowBackground; + } + + /** * <p>Indicates whether or not this animation will affect the transformation * matrix. For instance, a fade animation will not affect the matrix whereas * a scale animation will.</p> diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java index 437e54f635ee..8600f55eee70 100644 --- a/core/java/android/view/inputmethod/CursorAnchorInfo.java +++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java @@ -105,6 +105,13 @@ public final class CursorAnchorInfo implements Parcelable { private final SparseRectFArray mCharacterBoundsArray; /** + * Container of rectangular position of Editor in the local coordinates that will be transformed + * with the transformation matrix when rendered on the screen. + * @see {@link EditorBoundsInfo}. + */ + private final EditorBoundsInfo mEditorBoundsInfo; + + /** * Transformation matrix that is applied to any positional information of this class to * transform local coordinates into screen coordinates. */ @@ -141,6 +148,7 @@ public final class CursorAnchorInfo implements Parcelable { mInsertionMarkerBaseline = source.readFloat(); mInsertionMarkerBottom = source.readFloat(); mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class); + mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR); mMatrixValues = source.createFloatArray(); } @@ -163,6 +171,7 @@ public final class CursorAnchorInfo implements Parcelable { dest.writeFloat(mInsertionMarkerBaseline); dest.writeFloat(mInsertionMarkerBottom); dest.writeParcelable(mCharacterBoundsArray, flags); + dest.writeTypedObject(mEditorBoundsInfo, flags); dest.writeFloatArray(mMatrixValues); } @@ -216,6 +225,10 @@ public final class CursorAnchorInfo implements Parcelable { return false; } + if (!Objects.equals(mEditorBoundsInfo, that.mEditorBoundsInfo)) { + return false; + } + // Following fields are (partially) covered by hashCode(). if (mComposingTextStart != that.mComposingTextStart @@ -248,6 +261,7 @@ public final class CursorAnchorInfo implements Parcelable { + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline + " mInsertionMarkerBottom=" + mInsertionMarkerBottom + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray) + + " mEditorBoundsInfo=" + mEditorBoundsInfo + " mMatrix=" + Arrays.toString(mMatrixValues) + "}"; } @@ -266,6 +280,7 @@ public final class CursorAnchorInfo implements Parcelable { private float mInsertionMarkerBottom = Float.NaN; private int mInsertionMarkerFlags = 0; private SparseRectFArrayBuilder mCharacterBoundsArrayBuilder = null; + private EditorBoundsInfo mEditorBoundsInfo = null; private float[] mMatrixValues = null; private boolean mMatrixInitialized = false; @@ -356,6 +371,17 @@ public final class CursorAnchorInfo implements Parcelable { } /** + * Sets the current editor related bounds. + * + * @param bounds {@link EditorBoundsInfo} in local coordinates. + */ + @NonNull + public Builder setEditorBoundsInfo(@Nullable EditorBoundsInfo bounds) { + mEditorBoundsInfo = bounds; + return this; + } + + /** * Sets the matrix that transforms local coordinates into screen coordinates. * @param matrix transformation matrix from local coordinates into screen coordinates. null * is interpreted as an identity matrix. @@ -410,6 +436,7 @@ public final class CursorAnchorInfo implements Parcelable { if (mCharacterBoundsArrayBuilder != null) { mCharacterBoundsArrayBuilder.reset(); } + mEditorBoundsInfo = null; } } @@ -425,6 +452,7 @@ public final class CursorAnchorInfo implements Parcelable { mInsertionMarkerBottom = builder.mInsertionMarkerBottom; mCharacterBoundsArray = builder.mCharacterBoundsArrayBuilder != null ? builder.mCharacterBoundsArrayBuilder.build() : null; + mEditorBoundsInfo = builder.mEditorBoundsInfo; mMatrixValues = new float[9]; if (builder.mMatrixInitialized) { System.arraycopy(builder.mMatrixValues, 0, mMatrixValues, 0, 9); @@ -547,6 +575,14 @@ public final class CursorAnchorInfo implements Parcelable { } /** + * Returns {@link EditorBoundsInfo} editor related bounds. + */ + @Nullable + public EditorBoundsInfo getEditorBoundsInfo() { + return mEditorBoundsInfo; + } + + /** * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation * matrix that is to be applied other positional data in this class. * @return a new instance (copy) of the transformation matrix. diff --git a/core/java/android/view/inputmethod/EditorBoundsInfo.java b/core/java/android/view/inputmethod/EditorBoundsInfo.java new file mode 100644 index 000000000000..081dc819f10b --- /dev/null +++ b/core/java/android/view/inputmethod/EditorBoundsInfo.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Container of rectangular position related info for the Editor. + */ +public final class EditorBoundsInfo implements Parcelable { + + /** + * Container of rectangular position of Editor in the current window. + */ + private final RectF mEditorBounds; + + /** + * Container of rectangular position of Editor with additional padding around for initiating + * Stylus Handwriting in the current window. + */ + private final RectF mHandwritingBounds; + + private final int mHashCode; + + private EditorBoundsInfo(@NonNull Parcel source) { + mHashCode = source.readInt(); + mEditorBounds = source.readTypedObject(RectF.CREATOR); + mHandwritingBounds = source.readTypedObject(RectF.CREATOR); + } + + /** + * Returns the bounds of the Editor in local coordinates. + * + * Screen coordinates can be obtained by transforming with the + * {@link CursorAnchorInfo#getMatrix} of the containing {@code CursorAnchorInfo}. + */ + @Nullable + public RectF getEditorBounds() { + return mEditorBounds; + } + + /** + * Returns the bounds of the area that should be considered for initiating stylus handwriting + * in local coordinates. + * + * Screen coordinates can be obtained by transforming with the + * {@link CursorAnchorInfo#getMatrix} of the containing {@code CursorAnchorInfo}. + */ + @Nullable + public RectF getHandwritingBounds() { + return mHandwritingBounds; + } + + @Override + public int hashCode() { + return mHashCode; + } + + @Override + public String toString() { + return "EditorBoundsInfo{mEditorBounds=" + mEditorBounds + + " mHandwritingBounds=" + mHandwritingBounds + + "}"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null) { + return false; + } + EditorBoundsInfo bounds; + if (obj instanceof EditorBoundsInfo) { + bounds = (EditorBoundsInfo) obj; + } else { + return false; + } + return Objects.equals(bounds.mEditorBounds, mEditorBounds) + && Objects.equals(bounds.mHandwritingBounds, mHandwritingBounds); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mHashCode); + dest.writeTypedObject(mEditorBounds, flags); + dest.writeTypedObject(mHandwritingBounds, flags); + } + + /** + * Used to make this class parcelable. + */ + public static final @android.annotation.NonNull Parcelable.Creator<EditorBoundsInfo> CREATOR + = new Parcelable.Creator<EditorBoundsInfo>() { + @Override + public EditorBoundsInfo createFromParcel(@NonNull Parcel source) { + return new EditorBoundsInfo(source); + } + + @Override + public EditorBoundsInfo[] newArray(int size) { + return new EditorBoundsInfo[size]; + } + }; + + /** + * Builder for {@link CursorAnchorInfo}. + */ + public static final class Builder { + private RectF mEditorBounds = null; + private RectF mHandwritingBounds = null; + + /** + * Sets the bounding box of the current editor. + * + * @param bounds {@link RectF} in local coordinates. + */ + @NonNull + public EditorBoundsInfo.Builder setEditorBounds(@Nullable RectF bounds) { + mEditorBounds = bounds; + return this; + } + + /** + * Sets the current editor's bounds with padding for handwriting. + * + * @param bounds {@link RectF} in local coordinates. + */ + @NonNull + public EditorBoundsInfo.Builder setHandwritingBounds(@Nullable RectF bounds) { + mHandwritingBounds = bounds; + return this; + } + + /** + * Returns {@link EditorBoundsInfo} using parameters in this + * {@link EditorBoundsInfo.Builder}. + */ + @NonNull + public EditorBoundsInfo build() { + return new EditorBoundsInfo(this); + } + } + + private EditorBoundsInfo(final EditorBoundsInfo.Builder builder) { + mEditorBounds = builder.mEditorBounds; + mHandwritingBounds = builder.mHandwritingBounds; + + int hash = Objects.hashCode(mEditorBounds); + hash *= 31; + hash += Objects.hashCode(mHandwritingBounds); + mHashCode = hash; + } +} diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 5b2068ff16cd..fda72d5ba966 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -26,12 +26,16 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Log; +import android.view.InputChannel; +import android.view.MotionEvent; import android.view.View; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.InlineSuggestionsRequestInfo; +import java.util.List; + /** * The InputMethod interface represents an input method which can generate key * events and text, such as digital, email addresses, CJK characters, other @@ -100,11 +104,13 @@ public interface InputMethod { * operations that are allowed only to the * current IME. * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME. + * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME. * @hide */ @MainThread default void initializeInternal(IBinder token, - IInputMethodPrivilegedOperations privilegedOperations, int configChanges) { + IInputMethodPrivilegedOperations privilegedOperations, int configChanges, + boolean stylusHwSupported) { attachToken(token); } @@ -384,4 +390,23 @@ public interface InputMethod { */ public void setCurrentHideInputToken(IBinder hideInputToken); + /** + * Checks if IME is ready to start stylus handwriting session. + * If yes, {@link #startStylusHandwriting(InputChannel, List)} is called. + * @param requestId + * @hide + */ + default void canStartStylusHandwriting(int requestId) { + // intentionally empty + } + + /** + * Start stylus handwriting session. + * @hide + */ + default void startStylusHandwriting( + @NonNull InputChannel channel, @Nullable List<MotionEvent> events) { + // intentionally empty + } + } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 3583cd4c6d8b..6fc246eb2514 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1761,6 +1761,50 @@ public final class InputMethodManager { } /** + * Start stylus handwriting session. + * + * If supported by the current input method, a stylus handwriting session is started on the + * given View, capturing all stylus input and converting it to InputConnection commands. + * + * If handwriting mode is started successfully by the IME, any currently dispatched stylus + * pointers will be {@code android.view.MotionEvent#FLAG_CANCELED} cancelled. + * + * If Stylus handwriting mode is not supported or cannot be fulfilled for any reason by IME, + * request will be ignored and Stylus touch will continue as normal touch input. + * + * @param view the View for which stylus handwriting is requested. It and + * {@link View#hasWindowFocus its window} must be {@link View#hasFocus focused}. + */ + public void startStylusHandwriting(@NonNull View view) { + // Re-dispatch if there is a context mismatch. + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.startStylusHandwriting(view); + } + + checkFocus(); + synchronized (mH) { + if (view == null || !hasServedByInputMethodLocked(view)) { + Log.w(TAG, + "Ignoring startStylusHandwriting() as view=" + view + " is not served."); + return; + } + if (view.getViewRootImpl() != mCurRootView) { + Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus."); + return; + } + + try { + mService.startStylusHandwriting(mClient); + // TODO(b/210039666): do we need any extra work for supporting non-native + // UI toolkits? + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** * This method toggles the input method window display. * If the input window is already displayed, it gets hidden. * If not the input window will be displayed. diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 414a7f1f0e1c..1d8e9a36fb3f 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2656,6 +2656,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + /** + * Returns whether the selected child view (from the adapter's getView) is enabled. + * + * @return true if enabled + */ + public boolean isSelectedChildViewEnabled() { + return mIsChildViewEnabled; + } + + /** + * Set whether the selected child view (from the adapter's getView) is enabled. + * + * When refreshDrawableState is called, AbsListView will control the "enabled" state + * of the selector based on this. + * + * @param selectedChildViewEnabled true if enabled + */ + public void setSelectedChildViewEnabled(boolean selectedChildViewEnabled) { + mIsChildViewEnabled = selectedChildViewEnabled; + } + @Override protected void dispatchDraw(Canvas canvas) { int saveCount = 0; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 60ce65153890..ac28c31ecf49 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -115,6 +115,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.LinearInterpolator; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.EditorBoundsInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -1073,7 +1074,7 @@ public class Editor { com.android.internal.R.dimen.textview_error_popup_default_width); final StaticLayout l = StaticLayout.Builder.obtain(text, 0, text.length(), tv.getPaint(), defaultWidthInPixels) - .setUseLineSpacingFromFallbacks(tv.mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(tv.isFallbackLineSpacingForStaticLayout()) .build(); float max = 0; @@ -4570,6 +4571,12 @@ public class Editor { mTextView.getLocationOnScreen(mTmpIntOffset); mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]); builder.setMatrix(mViewToScreenMatrix); + final RectF bounds = new RectF(); + mTextView.getBoundsOnScreen(bounds, false /* clipToParent */); + EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); + //TODO(b/210039666): add Handwriting bounds once they're available. + builder.setEditorBoundsInfo( + boundsBuilder.setEditorBounds(bounds).build()); final float viewportToContentHorizontalOffset = mTextView.viewportToContentHorizontalOffset(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 67a271558f83..0fe06befa789 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -48,6 +48,9 @@ import android.annotation.XmlRes; import android.app.Activity; import android.app.PendingIntent; import android.app.assist.AssistStructure; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ClipDescription; @@ -76,6 +79,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; +import android.graphics.text.LineBreakConfig; import android.icu.text.DecimalFormatSymbols; import android.os.AsyncTask; import android.os.Build; @@ -345,6 +349,7 @@ import java.util.function.Supplier; * @attr ref android.R.styleable#TextView_fontVariationSettings * @attr ref android.R.styleable#TextView_breakStrategy * @attr ref android.R.styleable#TextView_hyphenationFrequency + * @attr ref android.R.styleable#TextView_lineBreakStyle * @attr ref android.R.styleable#TextView_autoSizeTextType * @attr ref android.R.styleable#TextView_autoSizeMinTextSize * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize @@ -453,6 +458,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; + /** + * This change ID enables the fallback text line spacing (line height) for BoringLayout. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id + + /** + * This change ID enables the fallback text line spacing (line height) for StaticLayout. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P) + public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id + // System wide time for last cut, copy or text changed action. static long sLastCutCopyOrTextChangedTime; @@ -756,6 +777,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Layout mLayout; private boolean mLocalesChanged = false; private int mTextSizeUnit = -1; + private LineBreakConfig mLineBreakConfig = new LineBreakConfig(); // This is used to reflect the current user preference for changing font weight and making text // more bold. @@ -766,8 +788,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mListenerChanged = false; // True if internationalized input should be used for numbers and date and time. private final boolean mUseInternationalizedInput; - // True if fallback fonts that end up getting used should be allowed to affect line spacing. - /* package */ boolean mUseFallbackLineSpacing; + + // Fallback fonts that end up getting used should be allowed to affect line spacing. + private static final int FALLBACK_LINE_SPACING_NONE = 0; + private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1; + private static final int FALLBACK_LINE_SPACING_ALL = 2; + + private int mUseFallbackLineSpacing; // True if the view text can be padded for compat reasons, when the view is translated. private final boolean mUseTextPaddingForUiTranslation; @@ -1418,6 +1445,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); break; + case com.android.internal.R.styleable.TextView_lineBreakStyle: + mLineBreakConfig.setLineBreakStyle( + a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE)); + break; + case com.android.internal.R.styleable.TextView_autoSizeTextType: mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); break; @@ -1479,7 +1511,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; - mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; + if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL; + } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; + } else { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE; + } // TODO(b/179693024): Use a ChangeId instead. mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; @@ -4541,8 +4579,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_fallbackLineSpacing */ public void setFallbackLineSpacing(boolean enabled) { - if (mUseFallbackLineSpacing != enabled) { - mUseFallbackLineSpacing = enabled; + int fallbackStrategy; + if (enabled) { + if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { + fallbackStrategy = FALLBACK_LINE_SPACING_ALL; + } else { + fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; + } + } else { + fallbackStrategy = FALLBACK_LINE_SPACING_NONE; + } + if (mUseFallbackLineSpacing != fallbackStrategy) { + mUseFallbackLineSpacing = fallbackStrategy; if (mLayout != null) { nullLayouts(); requestLayout(); @@ -4560,7 +4608,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @InspectableProperty public boolean isFallbackLineSpacing() { - return mUseFallbackLineSpacing; + return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE; + } + + private boolean isFallbackLineSpacingForBoringLayout() { + return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL; + } + + // Package privte for accessing from Editor.java + /* package */ boolean isFallbackLineSpacingForStaticLayout() { + return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL + || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; } /** @@ -4738,13 +4796,50 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Sets line break configuration indicates which strategy needs to be used when calculating the + * text wrapping. There are thee strategies for the line break style(lb): + * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}, + * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and + * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. + * The default value of the line break style is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, + * which means no line break style is specified. + * See <a href="https://drafts.csswg.org/css-text/#line-break-property"> + * the line-break property</a> + * + * @param lineBreakConfig the line break config for text wrapping. + */ + public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { + if (mLineBreakConfig.equals(lineBreakConfig)) { + return; + } + mLineBreakConfig.set(lineBreakConfig); + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + + /** + * Get the current line break configuration for text wrapping. + * + * @return the current line break configuration to be used for text wrapping. + */ + public @NonNull LineBreakConfig getLineBreakConfig() { + LineBreakConfig lbConfig = new LineBreakConfig(); + lbConfig.set(mLineBreakConfig); + return lbConfig; + } + + /** * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. * * @return a current {@link PrecomputedText.Params} * @see PrecomputedText */ public @NonNull PrecomputedText.Params getTextMetricsParams() { - return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), + return new PrecomputedText.Params(new TextPaint(mTextPaint), mLineBreakConfig, + getTextDirectionHeuristic(), mBreakStrategy, mHyphenationFrequency); } @@ -4760,6 +4855,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTextDir = params.getTextDirection(); mBreakStrategy = params.getBreakStrategy(); mHyphenationFrequency = params.getHyphenationFrequency(); + mLineBreakConfig.set(params.getLineBreakConfig()); if (mLayout != null) { nullLayouts(); requestLayout(); @@ -6298,7 +6394,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, - mHyphenationFrequency); + mHyphenationFrequency, mLineBreakConfig); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( @@ -9148,7 +9244,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (hintBoring == UNKNOWN_BORING) { hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, - mHintBoring); + isFallbackLineSpacingForBoringLayout(), mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } @@ -9190,11 +9286,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) - .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) + .setLineBreakConfig(mLineBreakConfig); if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -9250,7 +9347,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -9259,7 +9356,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener result = builder.build(); } else { if (boring == UNKNOWN_BORING) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mBoring); if (boring != null) { mBoring = boring; } @@ -9303,11 +9401,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) - .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) + .setLineBreakConfig(mLineBreakConfig); if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -9430,7 +9529,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (des < 0) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mBoring); if (boring != null) { mBoring = boring; } @@ -9463,7 +9563,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (hintDes < 0) { - hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); + hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } @@ -9667,12 +9768,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(getBreakStrategy()) .setHyphenationFrequency(getHyphenationFrequency()) .setJustificationMode(getJustificationMode()) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) - .setTextDirection(getTextDirectionHeuristic()); + .setTextDirection(getTextDirectionHeuristic()) + .setLineBreakConfig(mLineBreakConfig); final StaticLayout layout = layoutBuilder.build(); diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index 974a1dd50cf5..88ece5c536f6 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -101,14 +101,6 @@ public class DisplayAreaOrganizer extends WindowOrganizer { public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7; /** - * Display area for one handed background layer, which preventing when user - * turning the Dark theme on, they can not clearly identify the screen has entered - * one handed mode. - * @hide - */ - public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8; - - /** * Display area hosting IME window tokens (@see ImeContainer). By default, IMEs are parented * to FEATURE_IME_PLACEHOLDER but can be reparented under other RootDisplayArea. * @@ -118,7 +110,7 @@ public class DisplayAreaOrganizer extends WindowOrganizer { * app on another screen). * @hide */ - public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 9; + public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 8; /** * The last boundary of display area for system features diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java index c3ef88192c5d..3359a41369d7 100644 --- a/core/java/android/window/DisplayWindowPolicyController.java +++ b/core/java/android/window/DisplayWindowPolicyController.java @@ -19,6 +19,7 @@ package android.window; import android.annotation.NonNull; import android.content.ComponentName; import android.content.pm.ActivityInfo; +import android.util.ArraySet; import java.io.PrintWriter; import java.util.List; @@ -81,8 +82,10 @@ public abstract class DisplayWindowPolicyController { /** * This is called when the apps that contains running activities on the display has changed. + * The running activities refer to the non-finishing activities regardless of they are running + * in a process. */ - public void onRunningAppsChanged(int[] runningUids) {} + public void onRunningAppsChanged(ArraySet<Integer> runningUids) {} /** Dump debug data */ public void dump(String prefix, final PrintWriter pw) { diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 915c8fb9a6dd..fd1e84822193 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -366,6 +367,7 @@ public final class TransitionInfo implements Parcelable { private int mStartRotation = ROTATION_UNDEFINED; private int mEndRotation = ROTATION_UNDEFINED; private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; + private @ColorInt int mBackgroundColor; public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; @@ -387,6 +389,7 @@ public final class TransitionInfo implements Parcelable { mStartRotation = in.readInt(); mEndRotation = in.readInt(); mRotationAnimation = in.readInt(); + mBackgroundColor = in.readInt(); } /** Sets the parent of this change's container. The parent must be a participant or null. */ @@ -446,6 +449,11 @@ public final class TransitionInfo implements Parcelable { mRotationAnimation = anim; } + /** Sets the background color of this change's container. */ + public void setBackgroundColor(@ColorInt int backgroundColor) { + mBackgroundColor = backgroundColor; + } + /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @Nullable public WindowContainerToken getContainer() { @@ -526,6 +534,12 @@ public final class TransitionInfo implements Parcelable { return mRotationAnimation; } + /** @return get the background color of this change's container. */ + @ColorInt + public int getBackgroundColor() { + return mBackgroundColor; + } + /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -542,6 +556,7 @@ public final class TransitionInfo implements Parcelable { dest.writeInt(mStartRotation); dest.writeInt(mEndRotation); dest.writeInt(mRotationAnimation); + dest.writeInt(mBackgroundColor); } @NonNull diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java new file mode 100644 index 000000000000..53734eb88720 --- /dev/null +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.view.IWindow; +import android.view.IWindowSession; +import android.view.OnBackInvokedCallback; +import android.view.OnBackInvokedDispatcher; + +/** + * Provides window based implementation of {@link OnBackInvokedDispatcher}. + * + * Callbacks with higher priorities receive back dispatching first. + * Within the same priority, callbacks receive back dispatching in the reverse order + * in which they are added. + * + * When the top priority callback is updated, the new callback is propagated to the Window Manager + * if the window the instance is associated with has been attached. It is allowed to register / + * unregister {@link OnBackInvokedCallback}s before the window is attached, although callbacks + * will not receive dispatches until window attachment. + * + * @hide + */ +public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { + private IWindowSession mWindowSession; + private IWindow mWindow; + + /** + * Sends the pending top callback (if one exists) to WM when the view root + * is attached a window. + */ + public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) { + mWindowSession = windowSession; + mWindow = window; + // TODO(b/209867448): Send the top callback to WM (if one exists). + } + + /** Detaches the dispatcher instance from its window. */ + public void detachFromWindow() { + mWindow = null; + mWindowSession = null; + } + + @Override + public void registerOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback, @Priority int priority) { + // TODO(b/209867448): To be implemented. + } + + @Override + public void unregisterOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback) { + // TODO(b/209867448): To be implemented. + } + + /** Clears all registered callbacks on the instance. */ + public void clear() { + // TODO(b/209867448): To be implemented. + } +} diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 4ba7ef26e9cb..547535d90e5a 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -19,9 +19,10 @@ import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; +import android.annotation.BinderThread; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityThread; import android.app.IWindowToken; import android.app.ResourcesManager; import android.content.Context; @@ -30,7 +31,9 @@ import android.inputmethodservice.AbstractInputMethodService; import android.os.Build; import android.os.Bundle; import android.os.Debug; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.view.IWindowManager; @@ -71,6 +74,8 @@ public class WindowTokenClient extends IWindowToken.Stub { private boolean mAttachToWindowContainer; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + /** * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient} * can only attach one {@link Context}. @@ -132,7 +137,8 @@ public class WindowTokenClient extends IWindowToken.Stub { if (configuration == null) { return false; } - onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); + mHandler.post(() -> onConfigurationChanged(configuration, displayId, + false /* shouldReportConfigChange */)); mAttachToWindowContainer = true; return true; } catch (RemoteException e) { @@ -179,9 +185,11 @@ public class WindowTokenClient extends IWindowToken.Stub { * @param newConfig the updated {@link Configuration} * @param newDisplayId the updated {@link android.view.Display} ID */ + @BinderThread @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { - onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */); + mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId, + true /* shouldReportConfigChange */)); } // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService @@ -192,6 +200,7 @@ public class WindowTokenClient extends IWindowToken.Stub { * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control * whether to dispatch configuration update or not. */ + @MainThread @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void onConfigurationChanged(Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) { @@ -217,16 +226,14 @@ public class WindowTokenClient extends IWindowToken.Stub { if (shouldReportConfigChange && context instanceof WindowContext) { final WindowContext windowContext = (WindowContext) context; - ActivityThread.currentActivityThread().getHandler().post( - () -> windowContext.dispatchConfigurationChanged(newConfig)); + windowContext.dispatchConfigurationChanged(newConfig); } final int diff = mConfiguration.diffPublicOnly(newConfig); if (shouldReportConfigChange && diff != 0 && context instanceof WindowProviderService) { final WindowProviderService windowProviderService = (WindowProviderService) context; - ActivityThread.currentActivityThread().getHandler().post( - () -> windowProviderService.onConfigurationChanged(newConfig)); + windowProviderService.onConfigurationChanged(newConfig); } freeTextLayoutCachesIfNeeded(diff); if (mShouldDumpConfigForIme) { @@ -248,12 +255,15 @@ public class WindowTokenClient extends IWindowToken.Stub { } } + @BinderThread @Override public void onWindowTokenRemoved() { - final Context context = mContextRef.get(); - if (context != null) { - context.destroy(); - mContextRef.clear(); - } + mHandler.post(() -> { + final Context context = mContextRef.get(); + if (context != null) { + context.destroy(); + mContextRef.clear(); + } + }); } } diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index d0719eeca04e..b4ae56f23443 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -159,6 +159,14 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O } @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // In order to make the list view work with CollapsingToolbarLayout, + // we have to enable the nested scrolling feature of the list view. + getListView().setNestedScrollingEnabled(true); + } + + @Override public boolean onOptionsItemSelected(MenuItem menuItem) { int id = menuItem.getItemId(); switch (id) { diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index f90461048a3b..0ada13a73ad2 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -163,6 +163,12 @@ public final class SystemUiDeviceConfigFlags { public static final String PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED = "location_indicators_small_enabled"; + /** + * Whether to show the location indicator for system apps. + */ + public static final String PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM = + "location_indicators_show_system"; + // Flags related to Assistant /** @@ -523,6 +529,12 @@ public final class SystemUiDeviceConfigFlags { */ public static final String TASK_MANAGER_ENABLED = "task_manager_enabled"; + + /** + * (boolean) Whether the clipboard overlay is enabled. + */ + public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java index 29b31d37e862..b786526ce676 100644 --- a/core/java/com/android/internal/content/om/OverlayConfig.java +++ b/core/java/com/android/internal/content/om/OverlayConfig.java @@ -19,7 +19,6 @@ package com.android.internal.content.om; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackagePartitions; -import android.content.pm.parsing.ParsingPackageRead; import android.os.Build; import android.os.Trace; import android.util.ArrayMap; @@ -82,7 +81,22 @@ public class OverlayConfig { public interface PackageProvider { /** Performs the given action for each package. */ - void forEachPackage(TriConsumer<ParsingPackageRead, Boolean, File> p); + void forEachPackage(TriConsumer<Package, Boolean, File> p); + + interface Package { + + String getBaseApkPath(); + + int getOverlayPriority(); + + String getOverlayTarget(); + + String getPackageName(); + + int getTargetSdkVersion(); + + boolean isOverlayIsStatic(); + } } private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> { @@ -304,7 +318,7 @@ public class OverlayConfig { private static Map<String, ParsedOverlayInfo> getOverlayPackageInfos( @NonNull PackageProvider packageManager) { final HashMap<String, ParsedOverlayInfo> overlays = new HashMap<>(); - packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem, + packageManager.forEachPackage((PackageProvider.Package p, Boolean isSystem, @Nullable File preInstalledApexPath) -> { if (p.getOverlayTarget() != null && isSystem) { overlays.put(p.getPackageName(), new ParsedOverlayInfo(p.getPackageName(), diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java index e4e0228a0429..0fafd1063ed1 100644 --- a/core/java/com/android/internal/content/om/OverlayScanner.java +++ b/core/java/com/android/internal/content/om/OverlayScanner.java @@ -16,15 +16,13 @@ package com.android.internal.content.om; -import static android.content.pm.parsing.ParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY; -import static android.content.pm.parsing.ParsingPackageUtils.checkRequiredSystemProperties; - import static com.android.internal.content.om.OverlayConfig.TAG; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.text.TextUtils; @@ -183,7 +181,8 @@ public class OverlayScanner { List<Pair<String, File>> outExcludedOverlayPackages) { final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(), - overlayApk, PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY); + overlayApk, + FrameworkParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY); if (ret.isError()) { Log.w(TAG, "Got exception loading overlay.", ret.getException()); return null; @@ -196,7 +195,8 @@ public class OverlayScanner { final String propName = apkLite.getRequiredSystemPropertyName(); final String propValue = apkLite.getRequiredSystemPropertyValue(); if ((!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) - && !checkRequiredSystemProperties(propName, propValue)) { + && !FrameworkParsingPackageUtils.checkRequiredSystemProperties(propName, + propValue)) { // The overlay package should be excluded. Adds it into the outExcludedOverlayPackages // for overlay configuration parser to ignore it. outExcludedOverlayPackages.add(Pair.create(apkLite.getPackageName(), overlayApk)); diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java index 537e797c9bac..dff9551c0c07 100644 --- a/core/java/com/android/internal/graphics/ColorUtils.java +++ b/core/java/com/android/internal/graphics/ColorUtils.java @@ -62,7 +62,10 @@ public final class ColorUtils { return Color.argb(a, r, g, b); } - private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { + /** + * Returns the composite alpha of the given foreground and background alpha. + */ + public static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF); } diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index 9d0f209d8b2d..08bc8c7fa339 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -42,4 +42,5 @@ oneway interface IInputMethodPrivilegedOperations { void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */); void notifyUserActionAsync(); void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible); + void onStylusHandwritingReady(int requestId); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index d4cc376385b8..7ebcc88b593b 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -394,4 +394,20 @@ public final class InputMethodPrivilegedOperations { throw e.rethrowFromSystemServer(); } } + + /** + * Calls {@link IInputMethodPrivilegedOperations#onStylusHandwritingReady()} + */ + @AnyThread + public void onStylusHandwritingReady(int requestId) { + final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); + if (ops == null) { + return; + } + try { + ops.onStylusHandwritingReady(requestId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index d14054d4f9f7..e1a67d861bcb 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -24,8 +24,6 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADL import static android.view.SurfaceControl.JankData.PREDICTION_ERROR; import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING; -import static com.android.internal.jank.InteractionJankMonitor.ACTION_METRICS_LOGGED; -import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_BEGIN; import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_CANCEL; import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_END; @@ -241,7 +239,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (!mSurfaceOnly) { mRendererWrapper.addObserver(mObserver); } - notifyCujEvent(ACTION_SESSION_BEGIN); } } @@ -523,7 +520,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */ missedSfFramesCount, missedAppFramesCount); - notifyCujEvent(ACTION_METRICS_LOGGED); } if (DEBUG) { Log.i(TAG, "finish: CUJ=" + mSession.getName() diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index a33b2f18819e..5a66e9aec3e7 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -16,10 +16,7 @@ package com.android.internal.jank; -import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; - import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL; -import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NOT_BEGUN; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN; @@ -65,17 +62,16 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; import android.annotation.IntDef; import android.annotation.NonNull; import android.content.Context; -import android.content.Intent; import android.os.Build; import android.os.HandlerExecutor; import android.os.HandlerThread; -import android.os.SystemProperties; import android.provider.DeviceConfig; import android.text.TextUtils; import android.util.Log; @@ -131,14 +127,8 @@ public class InteractionJankMonitor { private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3; private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; - public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN"; public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL"; - public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED"; - public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME"; - public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP"; - @VisibleForTesting - public static final String PROP_NOTIFY_CUJ_EVENT = "debug.jank.notify_cuj_events"; // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE. public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0; @@ -185,6 +175,7 @@ public class InteractionJankMonitor { public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41; public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42; public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43; + public static final int CUJ_UNFOLD_ANIM = 44; private static final int NO_STATSD_LOGGING = -1; @@ -237,6 +228,7 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM, }; private static volatile InteractionJankMonitor sInstance; @@ -301,6 +293,7 @@ public class InteractionJankMonitor { CUJ_SCREEN_OFF_SHOW_AOD, CUJ_ONE_HANDED_ENTER_TRANSITION, CUJ_ONE_HANDED_EXIT_TRANSITION, + CUJ_UNFOLD_ANIM, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -374,8 +367,7 @@ public class InteractionJankMonitor { new ChoreographerWrapper(Choreographer.getInstance()); synchronized (mLock) { - FrameTrackerListener eventsListener = - (s, act) -> handleCujEvents(config.getContext(), act, s); + FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s); return new FrameTracker(session, mWorker.getThreadHandler(), threadedRenderer, viewRoot, surfaceControl, choreographer, mMetrics, mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, @@ -383,24 +375,13 @@ public class InteractionJankMonitor { } } - private void handleCujEvents(Context context, String action, Session session) { + private void handleCujEvents(String action, Session session) { // Clear the running and timeout tasks if the end / cancel was fired within the tracker. // Or we might have memory leaks. if (needRemoveTasks(action, session)) { removeTimeout(session.getCuj()); removeTracker(session.getCuj()); } - - // Notify the receivers if necessary. - if (session.shouldNotify()) { - if (context != null) { - notifyEvents(context, action, session); - } else { - throw new IllegalArgumentException( - "Can't notify cuj events due to lack of context: cuj=" - + session.getName() + ", action=" + action); - } - } } private boolean needRemoveTasks(String action, Session session) { @@ -412,22 +393,6 @@ public class InteractionJankMonitor { return badEnd || badCancel; } - /** - * Notifies who may interest in some CUJ events. - */ - @VisibleForTesting - public void notifyEvents(Context context, String action, Session session) { - if (action.equals(ACTION_SESSION_CANCEL) - && session.getReason() == REASON_CANCEL_NOT_BEGUN) { - return; - } - Intent intent = new Intent(action); - intent.putExtra(BUNDLE_KEY_CUJ_NAME, getNameOfCuj(session.getCuj())); - intent.putExtra(BUNDLE_KEY_TIMESTAMP, session.getTimeStamp()); - intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY); - context.sendBroadcast(intent); - } - private void removeTimeout(@CujType int cujType) { synchronized (mLock) { Runnable timeout = mTimeoutActions.get(cujType); @@ -625,7 +590,17 @@ public class InteractionJankMonitor { */ public static String getNameOfInteraction(int interactionType) { // There is an offset amount of 1 between cujType and interactionType. - return getNameOfCuj(interactionType - 1); + return getNameOfCuj(getCujTypeFromInteraction(interactionType)); + } + + /** + * A helper method to translate interaction type to CUJ type. + * + * @param interactionType the interaction type defined in AtomsProto.java + * @return the integer in {@link CujType} + */ + private static int getCujTypeFromInteraction(int interactionType) { + return interactionType - 1; } /** @@ -724,6 +699,8 @@ public class InteractionJankMonitor { return "ONE_HANDED_ENTER_TRANSITION"; case CUJ_ONE_HANDED_EXIT_TRANSITION: return "ONE_HANDED_EXIT_TRANSITION"; + case CUJ_UNFOLD_ANIM: + return "UNFOLD_ANIM"; } return "UNKNOWN"; } @@ -935,13 +912,11 @@ public class InteractionJankMonitor { private final long mTimeStamp; @Reasons private int mReason = REASON_END_UNKNOWN; - private final boolean mShouldNotify; private final String mName; public Session(@CujType int cujType, @NonNull String postfix) { mCujType = cujType; mTimeStamp = System.nanoTime(); - mShouldNotify = SystemProperties.getBoolean(PROP_NOTIFY_CUJ_EVENT, false); mName = TextUtils.isEmpty(postfix) ? String.format("J<%s>", getNameOfCuj(mCujType)) : String.format("J<%s::%s>", getNameOfCuj(mCujType), postfix); @@ -981,10 +956,5 @@ public class InteractionJankMonitor { public @Reasons int getReason() { return mReason; } - - /** Determines if should notify the receivers of cuj events */ - public boolean shouldNotify() { - return mShouldNotify; - } } } diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index 2f4a14fad2ee..5378b03fe1c5 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -58,6 +58,14 @@ public interface UiEventLogger { void log(@NonNull UiEventEnum event); /** + * Log a simple event with an instance id, without package information. + * Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + * @param instance An identifier obtained from an InstanceIdSequence. If null, reduces to log(). + */ + void log(@NonNull UiEventEnum event, @Nullable InstanceId instance); + + /** * Log an event with package information. Does nothing if event.getId() <= 0. * Give both uid and packageName if both are known, but one may be omitted if unknown. * @param event an enum implementing UiEventEnum interface. diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index c0f44a5eb39b..983e0fe6144e 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -42,16 +42,21 @@ public class UiEventLoggerImpl implements UiEventLogger { } @Override + public void log(UiEventEnum event, InstanceId instanceId) { + logWithInstanceId(event, 0, null, instanceId); + } + + @Override public void logWithInstanceId(UiEventEnum event, int uid, String packageName, InstanceId instance) { final int eventID = event.getId(); - if ((eventID > 0) && (instance != null)) { + if ((eventID > 0) && (instance != null)) { FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED, /* event_id = 1 */ eventID, /* uid = 2 */ uid, /* package_name = 3 */ packageName, /* instance_id = 4 */ instance.getId()); - } else { + } else if (eventID > 0) { log(event, uid, packageName); } } @@ -78,7 +83,7 @@ public class UiEventLoggerImpl implements UiEventLogger { /* package_name = 2 */ packageName, /* instance_id = 3 */ instance.getId(), /* position_picked = 4 */ position); - } else { + } else if ((eventID > 0)) { logWithPosition(event, uid, packageName, position); } } diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 2d09434807a6..e303890c245a 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -88,6 +88,11 @@ public class UiEventLoggerFake implements UiEventLogger { } @Override + public void log(UiEventEnum event, InstanceId instance) { + logWithInstanceId(event, 0, null, instance); + } + + @Override public void log(UiEventEnum event, int uid, String packageName) { final int eventId = event.getId(); if (eventId > 0) { diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 2a203acebc72..b579be03acbd 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -34,8 +34,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; -import java.net.Inet4Address; -import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -93,8 +91,8 @@ public class VpnConfig implements Parcelable { public String interfaze; public String session; public int mtu = -1; - public List<LinkAddress> addresses = new ArrayList<LinkAddress>(); - public List<RouteInfo> routes = new ArrayList<RouteInfo>(); + public List<LinkAddress> addresses = new ArrayList<>(); + public List<RouteInfo> routes = new ArrayList<>(); public List<String> dnsServers; public List<String> searchDomains; public List<String> allowedApplications; @@ -114,12 +112,32 @@ public class VpnConfig implements Parcelable { public VpnConfig() { } - public void updateAllowedFamilies(InetAddress address) { - if (address instanceof Inet4Address) { - allowIPv4 = true; - } else { - allowIPv6 = true; - } + public VpnConfig(VpnConfig other) { + user = other.user; + interfaze = other.interfaze; + session = other.session; + mtu = other.mtu; + addresses = copyOf(other.addresses); + routes = copyOf(other.routes); + dnsServers = copyOf(other.dnsServers); + searchDomains = copyOf(other.searchDomains); + allowedApplications = copyOf(other.allowedApplications); + disallowedApplications = copyOf(other.disallowedApplications); + configureIntent = other.configureIntent; + startTime = other.startTime; + legacy = other.legacy; + blocking = other.blocking; + allowBypass = other.allowBypass; + allowIPv4 = other.allowIPv4; + allowIPv6 = other.allowIPv6; + isMetered = other.isMetered; + underlyingNetworks = other.underlyingNetworks != null ? Arrays.copyOf( + other.underlyingNetworks, other.underlyingNetworks.length) : null; + proxyInfo = other.proxyInfo; + } + + private static <T> List<T> copyOf(List<T> list) { + return list != null ? new ArrayList<>(list) : null; } public void addLegacyRoutes(String routesStr) { @@ -131,7 +149,6 @@ public class VpnConfig implements Parcelable { //each route is ip/prefix RouteInfo info = new RouteInfo(new IpPrefix(route), null, null, RouteInfo.RTN_UNICAST); this.routes.add(info); - updateAllowedFamilies(info.getDestination().getAddress()); } } @@ -144,7 +161,6 @@ public class VpnConfig implements Parcelable { //each address is ip/prefix LinkAddress addr = new LinkAddress(address); this.addresses.add(addr); - updateAllowedFamilies(addr.getAddress()); } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 9429c79697bf..2e93dcaa130b 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -164,7 +164,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - static final int VERSION = 205; + static final int VERSION = 206; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -214,6 +214,26 @@ public class BatteryStatsImpl extends BatteryStats { private static final double MILLISECONDS_IN_HOUR = 3600 * 1000; private static final long MILLISECONDS_IN_YEAR = 365 * 24 * 3600 * 1000L; + private static final LongCounter ZERO_LONG_COUNTER = new LongCounter() { + @Override + public long getCountLocked(int which) { + return 0; + } + + @Override + public long getCountForProcessState(int procState) { + return 0; + } + + @Override + public void logState(Printer pw, String prefix) { + pw.println(prefix + "mCount=0"); + } + }; + + private static final LongCounter[] ZERO_LONG_COUNTER_ARRAY = + new LongCounter[]{ZERO_LONG_COUNTER}; + private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); @@ -244,6 +264,8 @@ public class BatteryStatsImpl extends BatteryStats { private static final int[] SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS = { MeasuredEnergyStats.POWER_BUCKET_CPU, MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, + MeasuredEnergyStats.POWER_BUCKET_WIFI, + MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, }; // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate @@ -1742,7 +1764,7 @@ public class BatteryStatsImpl extends BatteryStats { } } - private static class TimeMultiStateCounter implements TimeBaseObs { + private static class TimeMultiStateCounter extends LongCounter implements TimeBaseObs { private final TimeBase mTimeBase; private final LongMultiStateCounter mCounter; @@ -1794,7 +1816,7 @@ public class BatteryStatsImpl extends BatteryStats { /** * Returns accumulated count for the specified state. */ - public long getCountLocked(int procState) { + public long getCountForProcessState(@BatteryConsumer.ProcessState int procState) { return mCounter.getCount(procState); } @@ -1802,6 +1824,12 @@ public class BatteryStatsImpl extends BatteryStats { return mCounter.getTotalCount(); } + @Override + public long getCountLocked(int statsType) { + return getTotalCountLocked(); + } + + @Override public void logState(Printer pw, String prefix) { pw.println(prefix + "mCounter=" + mCounter); } @@ -1947,6 +1975,14 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getCountForProcessState(int procState) { + if (procState == BatteryConsumer.PROCESS_STATE_ANY) { + return getCountLocked(STATS_SINCE_CHARGED); + } + return 0; + } + + @Override public void logState(Printer pw, String prefix) { pw.println(prefix + "mCount=" + mCount); } @@ -3226,57 +3262,50 @@ public class BatteryStatsImpl extends BatteryStats { public static class ControllerActivityCounterImpl extends ControllerActivityCounter implements Parcelable { - private final LongSamplingCounter mIdleTimeMillis; + private final Clock mClock; + private final TimeBase mTimeBase; + private int mNumTxStates; + private int mProcessState; + private TimeMultiStateCounter mIdleTimeMillis; private final LongSamplingCounter mScanTimeMillis; private final LongSamplingCounter mSleepTimeMillis; - private final LongSamplingCounter mRxTimeMillis; - private final LongSamplingCounter[] mTxTimeMillis; + private TimeMultiStateCounter mRxTimeMillis; + private TimeMultiStateCounter[] mTxTimeMillis; private final LongSamplingCounter mPowerDrainMaMs; private final LongSamplingCounter mMonitoredRailChargeConsumedMaMs; - public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates) { - mIdleTimeMillis = new LongSamplingCounter(timeBase); + public ControllerActivityCounterImpl(Clock clock, TimeBase timeBase, int numTxStates) { + mClock = clock; + mTimeBase = timeBase; + mNumTxStates = numTxStates; mScanTimeMillis = new LongSamplingCounter(timeBase); mSleepTimeMillis = new LongSamplingCounter(timeBase); - mRxTimeMillis = new LongSamplingCounter(timeBase); - mTxTimeMillis = new LongSamplingCounter[numTxStates]; - for (int i = 0; i < numTxStates; i++) { - mTxTimeMillis[i] = new LongSamplingCounter(timeBase); - } mPowerDrainMaMs = new LongSamplingCounter(timeBase); mMonitoredRailChargeConsumedMaMs = new LongSamplingCounter(timeBase); } - public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates, Parcel in) { - mIdleTimeMillis = new LongSamplingCounter(timeBase, in); + public ControllerActivityCounterImpl(Clock clock, TimeBase timeBase, int numTxStates, + Parcel in) { + mClock = clock; + mTimeBase = timeBase; + mNumTxStates = numTxStates; + mIdleTimeMillis = readTimeMultiStateCounter(in, timeBase); mScanTimeMillis = new LongSamplingCounter(timeBase, in); mSleepTimeMillis = new LongSamplingCounter(timeBase, in); - mRxTimeMillis = new LongSamplingCounter(timeBase, in); - final int recordedTxStates = in.readInt(); - if (recordedTxStates != numTxStates) { - throw new ParcelFormatException("inconsistent tx state lengths"); - } + mRxTimeMillis = readTimeMultiStateCounter(in, timeBase); + mTxTimeMillis = readTimeMultiStateCounters(in, timeBase, numTxStates); - mTxTimeMillis = new LongSamplingCounter[numTxStates]; - for (int i = 0; i < numTxStates; i++) { - mTxTimeMillis[i] = new LongSamplingCounter(timeBase, in); - } mPowerDrainMaMs = new LongSamplingCounter(timeBase, in); mMonitoredRailChargeConsumedMaMs = new LongSamplingCounter(timeBase, in); } public void readSummaryFromParcel(Parcel in) { - mIdleTimeMillis.readSummaryFromParcelLocked(in); + mIdleTimeMillis = readTimeMultiStateCounter(in, mTimeBase); mScanTimeMillis.readSummaryFromParcelLocked(in); mSleepTimeMillis.readSummaryFromParcelLocked(in); - mRxTimeMillis.readSummaryFromParcelLocked(in); - final int recordedTxStates = in.readInt(); - if (recordedTxStates != mTxTimeMillis.length) { - throw new ParcelFormatException("inconsistent tx state lengths"); - } - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.readSummaryFromParcelLocked(in); - } + mRxTimeMillis = readTimeMultiStateCounter(in, mTimeBase); + mTxTimeMillis = readTimeMultiStateCounters(in, mTimeBase, mNumTxStates); + mPowerDrainMaMs.readSummaryFromParcelLocked(in); mMonitoredRailChargeConsumedMaMs.readSummaryFromParcelLocked(in); } @@ -3287,52 +3316,98 @@ public class BatteryStatsImpl extends BatteryStats { } public void writeSummaryToParcel(Parcel dest) { - mIdleTimeMillis.writeSummaryFromParcelLocked(dest); + writeTimeMultiStateCounter(dest, mIdleTimeMillis); mScanTimeMillis.writeSummaryFromParcelLocked(dest); mSleepTimeMillis.writeSummaryFromParcelLocked(dest); - mRxTimeMillis.writeSummaryFromParcelLocked(dest); - dest.writeInt(mTxTimeMillis.length); - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.writeSummaryFromParcelLocked(dest); - } + writeTimeMultiStateCounter(dest, mRxTimeMillis); + writeTimeMultiStateCounters(dest, mTxTimeMillis); mPowerDrainMaMs.writeSummaryFromParcelLocked(dest); mMonitoredRailChargeConsumedMaMs.writeSummaryFromParcelLocked(dest); } @Override public void writeToParcel(Parcel dest, int flags) { - mIdleTimeMillis.writeToParcel(dest); + writeTimeMultiStateCounter(dest, mIdleTimeMillis); mScanTimeMillis.writeToParcel(dest); mSleepTimeMillis.writeToParcel(dest); - mRxTimeMillis.writeToParcel(dest); - dest.writeInt(mTxTimeMillis.length); - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.writeToParcel(dest); - } + writeTimeMultiStateCounter(dest, mRxTimeMillis); + writeTimeMultiStateCounters(dest, mTxTimeMillis); mPowerDrainMaMs.writeToParcel(dest); mMonitoredRailChargeConsumedMaMs.writeToParcel(dest); } + private TimeMultiStateCounter readTimeMultiStateCounter(Parcel in, TimeBase timeBase) { + if (in.readBoolean()) { + final TimeMultiStateCounter counter = + new TimeMultiStateCounter(timeBase, in, mClock.elapsedRealtime()); + if (counter.getStateCount() == BatteryConsumer.PROCESS_STATE_COUNT) { + return counter; + } + } + return null; + } + + private void writeTimeMultiStateCounter(Parcel dest, TimeMultiStateCounter counter) { + if (counter != null) { + dest.writeBoolean(true); + counter.writeToParcel(dest); + } else { + dest.writeBoolean(false); + } + } + + private TimeMultiStateCounter[] readTimeMultiStateCounters(Parcel in, TimeBase timeBase, + int expectedNumCounters) { + if (in.readBoolean()) { + final int numCounters = in.readInt(); + boolean valid = (numCounters == expectedNumCounters); + // Need to read counters out of the Parcel, even if all or some of them are + // invalid. + TimeMultiStateCounter[] counters = new TimeMultiStateCounter[numCounters]; + for (int i = 0; i < numCounters; i++) { + final TimeMultiStateCounter counter = + new TimeMultiStateCounter(timeBase, in, mClock.elapsedRealtime()); + if (counter.getStateCount() == BatteryConsumer.PROCESS_STATE_COUNT) { + counters[i] = counter; + } else { + valid = false; + } + } + if (valid) { + return counters; + } + } + return null; + } + + private void writeTimeMultiStateCounters(Parcel dest, TimeMultiStateCounter[] counters) { + if (counters != null) { + dest.writeBoolean(true); + dest.writeInt(counters.length); + for (TimeMultiStateCounter counter : counters) { + counter.writeToParcel(dest); + } + } else { + dest.writeBoolean(false); + } + } + public void reset(boolean detachIfReset, long elapsedRealtimeUs) { - mIdleTimeMillis.reset(detachIfReset, elapsedRealtimeUs); + resetIfNotNull(mIdleTimeMillis, detachIfReset, elapsedRealtimeUs); mScanTimeMillis.reset(detachIfReset, elapsedRealtimeUs); mSleepTimeMillis.reset(detachIfReset, elapsedRealtimeUs); - mRxTimeMillis.reset(detachIfReset, elapsedRealtimeUs); - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.reset(detachIfReset, elapsedRealtimeUs); - } + resetIfNotNull(mRxTimeMillis, detachIfReset, elapsedRealtimeUs); + resetIfNotNull(mTxTimeMillis, detachIfReset, elapsedRealtimeUs); mPowerDrainMaMs.reset(detachIfReset, elapsedRealtimeUs); mMonitoredRailChargeConsumedMaMs.reset(detachIfReset, elapsedRealtimeUs); } public void detach() { - mIdleTimeMillis.detach(); + detachIfNotNull(mIdleTimeMillis); mScanTimeMillis.detach(); mSleepTimeMillis.detach(); - mRxTimeMillis.detach(); - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.detach(); - } + detachIfNotNull(mRxTimeMillis); + detachIfNotNull(mTxTimeMillis); mPowerDrainMaMs.detach(); mMonitoredRailChargeConsumedMaMs.detach(); } @@ -3342,7 +3417,17 @@ public class BatteryStatsImpl extends BatteryStats { * milliseconds. */ @Override - public LongSamplingCounter getIdleTimeCounter() { + public LongCounter getIdleTimeCounter() { + if (mIdleTimeMillis == null) { + return ZERO_LONG_COUNTER; + } + return mIdleTimeMillis; + } + + private TimeMultiStateCounter getOrCreateIdleTimeCounter() { + if (mIdleTimeMillis == null) { + mIdleTimeMillis = createTimeMultiStateCounter(); + } return mIdleTimeMillis; } @@ -3369,7 +3454,17 @@ public class BatteryStatsImpl extends BatteryStats { * milliseconds. */ @Override - public LongSamplingCounter getRxTimeCounter() { + public LongCounter getRxTimeCounter() { + if (mRxTimeMillis == null) { + return ZERO_LONG_COUNTER; + } + return mRxTimeMillis; + } + + private TimeMultiStateCounter getOrCreateRxTimeCounter() { + if (mRxTimeMillis == null) { + mRxTimeMillis = createTimeMultiStateCounter(); + } return mRxTimeMillis; } @@ -3378,10 +3473,33 @@ public class BatteryStatsImpl extends BatteryStats { * milliseconds. */ @Override - public LongSamplingCounter[] getTxTimeCounters() { + public LongCounter[] getTxTimeCounters() { + if (mTxTimeMillis == null) { + return ZERO_LONG_COUNTER_ARRAY; + } + return mTxTimeMillis; + } + + private TimeMultiStateCounter[] getOrCreateTxTimeCounters() { + if (mTxTimeMillis == null) { + mTxTimeMillis = new TimeMultiStateCounter[mNumTxStates]; + for (int i = 0; i < mNumTxStates; i++) { + mTxTimeMillis[i] = createTimeMultiStateCounter(); + } + } return mTxTimeMillis; } + private TimeMultiStateCounter createTimeMultiStateCounter() { + final long timestampMs = mClock.elapsedRealtime(); + TimeMultiStateCounter counter = new TimeMultiStateCounter(mTimeBase, + BatteryConsumer.PROCESS_STATE_COUNT, timestampMs); + counter.setState(mapUidProcessStateToBatteryConsumerProcessState(mProcessState), + timestampMs); + counter.update(0, timestampMs); + return counter; + } + /** * @return a LongSamplingCounter, measuring power use in milli-ampere milliseconds (mAmS). */ @@ -3398,6 +3516,21 @@ public class BatteryStatsImpl extends BatteryStats { public LongSamplingCounter getMonitoredRailChargeConsumedMaMs() { return mMonitoredRailChargeConsumedMaMs; } + + private void setState(int processState, long elapsedTimeMs) { + mProcessState = processState; + if (mIdleTimeMillis != null) { + mIdleTimeMillis.setState(processState, elapsedTimeMs); + } + if (mRxTimeMillis != null) { + mRxTimeMillis.setState(processState, elapsedTimeMs); + } + if (mTxTimeMillis != null) { + for (int i = 0; i < mTxTimeMillis.length; i++) { + mTxTimeMillis[i].setState(processState, elapsedTimeMs); + } + } + } } /** Get Resource Power Manager stats. Create a new one if it doesn't already exist. */ @@ -8234,6 +8367,16 @@ public class BatteryStatsImpl extends BatteryStats { mapUidProcessStateToBatteryConsumerProcessState(procState); getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedTimeMs); getMobileRadioActiveTimeCounter().setState(batteryConsumerProcessState, elapsedTimeMs); + final ControllerActivityCounterImpl wifiControllerActivity = + getWifiControllerActivity(); + if (wifiControllerActivity != null) { + wifiControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs); + } + final ControllerActivityCounterImpl bluetoothControllerActivity = + getBluetoothControllerActivity(); + if (bluetoothControllerActivity != null) { + bluetoothControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs); + } final MeasuredEnergyStats energyStats = getOrCreateMeasuredEnergyStatsIfSupportedLocked(); if (energyStats != null) { @@ -8271,7 +8414,7 @@ public class BatteryStatsImpl extends BatteryStats { long activeTime = 0; for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) { - activeTime += mCpuActiveTimeMs.getCountLocked(procState); + activeTime += mCpuActiveTimeMs.getCountForProcessState(procState); } return activeTime; } @@ -8283,7 +8426,7 @@ public class BatteryStatsImpl extends BatteryStats { return 0; } - return mCpuActiveTimeMs.getCountLocked(procState); + return mCpuActiveTimeMs.getCountForProcessState(procState); } @Override @@ -8576,12 +8719,12 @@ public class BatteryStatsImpl extends BatteryStats { } @Override - public ControllerActivityCounter getWifiControllerActivity() { + public ControllerActivityCounterImpl getWifiControllerActivity() { return mWifiControllerActivity; } @Override - public ControllerActivityCounter getBluetoothControllerActivity() { + public ControllerActivityCounterImpl getBluetoothControllerActivity() { return mBluetoothControllerActivity; } @@ -8592,24 +8735,24 @@ public class BatteryStatsImpl extends BatteryStats { public ControllerActivityCounterImpl getOrCreateWifiControllerActivityLocked() { if (mWifiControllerActivity == null) { - mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - NUM_BT_TX_LEVELS); + mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS); } return mWifiControllerActivity; } public ControllerActivityCounterImpl getOrCreateBluetoothControllerActivityLocked() { if (mBluetoothControllerActivity == null) { - mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - NUM_BT_TX_LEVELS); + mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, NUM_BT_TX_LEVELS); } return mBluetoothControllerActivity; } public ControllerActivityCounterImpl getOrCreateModemControllerActivityLocked() { if (mModemControllerActivity == null) { - mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - ModemActivityInfo.getNumTxPowerLevels()); + mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels()); } return mModemControllerActivity; } @@ -8702,6 +8845,14 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("mBsi") @Override + public long getBluetoothMeasuredBatteryConsumptionUC( + @BatteryConsumer.ProcessState int processState) { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, + processState); + } + + @GuardedBy("mBsi") + @Override public long getCpuMeasuredBatteryConsumptionUC() { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); } @@ -8745,6 +8896,13 @@ public class BatteryStatsImpl extends BatteryStats { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI); } + @GuardedBy("mBsi") + @Override + public long getWifiMeasuredBatteryConsumptionUC(int processState) { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI, + processState); + } + /** * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time * since last marked. Also sets the mark time for both these timers. @@ -9341,7 +9499,7 @@ public class BatteryStatsImpl extends BatteryStats { if (processState == BatteryConsumer.PROCESS_STATE_ANY) { return mMobileRadioActiveTime.getTotalCountLocked(); } else { - return mMobileRadioActiveTime.getCountLocked(processState); + return mMobileRadioActiveTime.getCountForProcessState(processState); } } @@ -10291,22 +10449,22 @@ public class BatteryStatsImpl extends BatteryStats { } if (in.readInt() != 0) { - mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - NUM_WIFI_TX_LEVELS, in); + mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS, in); } else { mWifiControllerActivity = null; } if (in.readInt() != 0) { - mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - NUM_BT_TX_LEVELS, in); + mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, NUM_BT_TX_LEVELS, in); } else { mBluetoothControllerActivity = null; } if (in.readInt() != 0) { - mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - ModemActivityInfo.getNumTxPowerLevels(), in); + mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels(), in); } else { mModemControllerActivity = null; } @@ -11273,6 +11431,20 @@ public class BatteryStatsImpl extends BatteryStats { getMobileRadioActiveTimeCounter() .setState(batteryConsumerProcessState, elapsedRealtimeMs); + + final ControllerActivityCounterImpl wifiControllerActivity = + getWifiControllerActivity(); + if (wifiControllerActivity != null) { + wifiControllerActivity.setState(batteryConsumerProcessState, elapsedRealtimeMs); + } + + final ControllerActivityCounterImpl bluetoothControllerActivity = + getBluetoothControllerActivity(); + if (bluetoothControllerActivity != null) { + bluetoothControllerActivity.setState(batteryConsumerProcessState, + elapsedRealtimeMs); + } + final MeasuredEnergyStats energyStats = getOrCreateMeasuredEnergyStatsIfSupportedLocked(); if (energyStats != null) { @@ -11681,10 +11853,11 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); } - mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS); - mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mWifiActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, + NUM_WIFI_TX_LEVELS); + mBluetoothActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, NUM_BT_TX_LEVELS); - mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mModemActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels()); mMobileRadioActiveTimer = new StopwatchTimer(mClock, null, -400, null, mOnBatteryTimeBase); mMobileRadioActivePerAppTimer = new StopwatchTimer(mClock, null, -401, null, @@ -12613,7 +12786,8 @@ public class BatteryStatsImpl extends BatteryStats { continue; } - final Uid u = getUidStatsLocked(mapUid(entry.uid), elapsedRealtimeMs, uptimeMs); + final int uid = mapUid(entry.uid); + final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs); if (entry.rxBytes != 0) { u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes, entry.rxPackets); @@ -12626,8 +12800,7 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( entry.rxPackets); - // TODO(b/182845426): What if u was a mapped isolated uid? Shouldn't we sum? - rxPackets.put(u.getUid(), entry.rxPackets); + add(rxPackets, uid, entry.rxPackets); // Sum the total number of packets so that the Rx Power can // be evenly distributed amongst the apps. @@ -12646,8 +12819,7 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( entry.txPackets); - // TODO(b/182845426): What if u was a mapped isolated uid? Shouldn't we sum? - txPackets.put(u.getUid(), entry.txPackets); + add(txPackets, uid, entry.txPackets); // Sum the total number of packets so that the Tx Power can // be evenly distributed amongst the apps. @@ -12677,7 +12849,7 @@ public class BatteryStatsImpl extends BatteryStats { } } - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mWifiPowerCalculator.calcPowerWithoutControllerDataMah( entry.rxPackets, entry.txPackets, uidRunningMs, uidScanMs, uidBatchScanMs)); @@ -12774,9 +12946,10 @@ public class BatteryStatsImpl extends BatteryStats { ControllerActivityCounterImpl activityCounter = uid.getOrCreateWifiControllerActivityLocked(); - activityCounter.getRxTimeCounter().addCountLocked(scanRxTimeSinceMarkMs); - activityCounter.getTxTimeCounters()[0].addCountLocked( - scanTxTimeSinceMarkMs); + activityCounter.getOrCreateRxTimeCounter() + .increment(scanRxTimeSinceMarkMs, elapsedRealtimeMs); + activityCounter.getOrCreateTxTimeCounters()[0] + .increment(scanTxTimeSinceMarkMs, elapsedRealtimeMs); leftOverRxTimeMs -= scanRxTimeSinceMarkMs; leftOverTxTimeMs -= scanTxTimeSinceMarkMs; } @@ -12796,14 +12969,14 @@ public class BatteryStatsImpl extends BatteryStats { Slog.d(TAG, " IdleTime for UID " + uid.getUid() + ": " + myIdleTimeMs + " ms"); } - uid.getOrCreateWifiControllerActivityLocked().getIdleTimeCounter() - .addCountLocked(myIdleTimeMs); + uid.getOrCreateWifiControllerActivityLocked().getOrCreateIdleTimeCounter() + .increment(myIdleTimeMs, elapsedRealtimeMs); } if (uidEstimatedConsumptionMah != null) { double uidEstMah = mWifiPowerCalculator.calcPowerFromControllerDataMah( scanRxTimeSinceMarkMs, scanTxTimeSinceMarkMs, myIdleTimeMs); - uidEstimatedConsumptionMah.add(uid.getUid(), uidEstMah); + uidEstimatedConsumptionMah.incrementValue(uid.getUid(), uidEstMah); } } @@ -12822,10 +12995,10 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_ENERGY) { Slog.d(TAG, " TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms"); } - uid.getOrCreateWifiControllerActivityLocked().getTxTimeCounters()[0] - .addCountLocked(myTxTimeMs); + uid.getOrCreateWifiControllerActivityLocked().getOrCreateTxTimeCounters()[0] + .increment(myTxTimeMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(uid.getUid(), + uidEstimatedConsumptionMah.incrementValue(uid.getUid(), mWifiPowerCalculator.calcPowerFromControllerDataMah( 0, myTxTimeMs, 0)); } @@ -12841,10 +13014,10 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_ENERGY) { Slog.d(TAG, " RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms"); } - uid.getOrCreateWifiControllerActivityLocked().getRxTimeCounter() - .addCountLocked(myRxTimeMs); + uid.getOrCreateWifiControllerActivityLocked().getOrCreateRxTimeCounter() + .increment(myRxTimeMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(uid.getUid(), + uidEstimatedConsumptionMah.incrementValue(uid.getUid(), mWifiPowerCalculator.calcPowerFromControllerDataMah( myRxTimeMs, 0, 0)); } @@ -12854,14 +13027,14 @@ public class BatteryStatsImpl extends BatteryStats { // Update WiFi controller stats. - mWifiActivity.getRxTimeCounter().addCountLocked( - info.getControllerRxDurationMillis()); - mWifiActivity.getTxTimeCounters()[0].addCountLocked( - info.getControllerTxDurationMillis()); + mWifiActivity.getOrCreateRxTimeCounter().increment( + info.getControllerRxDurationMillis(), elapsedRealtimeMs); + mWifiActivity.getOrCreateTxTimeCounters()[0].increment( + info.getControllerTxDurationMillis(), elapsedRealtimeMs); mWifiActivity.getScanTimeCounter().addCountLocked( info.getControllerScanDurationMillis()); - mWifiActivity.getIdleTimeCounter().addCountLocked( - info.getControllerIdleDurationMillis()); + mWifiActivity.getOrCreateIdleTimeCounter().increment( + info.getControllerIdleDurationMillis(), elapsedRealtimeMs); // POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. final double opVolt = mPowerProfile.getAveragePower( @@ -12961,14 +13134,16 @@ public class BatteryStatsImpl extends BatteryStats { if (deltaInfo != null) { mHasModemReporting = true; - mModemActivity.getIdleTimeCounter().addCountLocked( - deltaInfo.getIdleTimeMillis()); + mModemActivity.getOrCreateIdleTimeCounter() + .increment(deltaInfo.getIdleTimeMillis(), elapsedRealtimeMs); mModemActivity.getSleepTimeCounter().addCountLocked( deltaInfo.getSleepTimeMillis()); - mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getReceiveTimeMillis()); + mModemActivity.getOrCreateRxTimeCounter() + .increment(deltaInfo.getReceiveTimeMillis(), elapsedRealtimeMs); for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) { - mModemActivity.getTxTimeCounters()[lvl] - .addCountLocked(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl)); + mModemActivity.getOrCreateTxTimeCounters()[lvl] + .increment(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl), + elapsedRealtimeMs); } // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. @@ -13070,7 +13245,7 @@ public class BatteryStatsImpl extends BatteryStats { // Distribute measured mobile radio charge consumption based on app radio // active time if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah( appRadioTimeUs / 1000)); } @@ -13086,7 +13261,8 @@ public class BatteryStatsImpl extends BatteryStats { if (totalRxPackets > 0 && entry.rxPackets > 0) { final long rxMs = (entry.rxPackets * deltaInfo.getReceiveTimeMillis()) / totalRxPackets; - activityCounter.getRxTimeCounter().addCountLocked(rxMs); + activityCounter.getOrCreateRxTimeCounter() + .increment(rxMs, elapsedRealtimeMs); } if (totalTxPackets > 0 && entry.txPackets > 0) { @@ -13095,7 +13271,8 @@ public class BatteryStatsImpl extends BatteryStats { long txMs = entry.txPackets * deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl); txMs /= totalTxPackets; - activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs); + activityCounter.getOrCreateTxTimeCounters()[lvl] + .increment(txMs, elapsedRealtimeMs); } } } @@ -13193,8 +13370,8 @@ public class BatteryStatsImpl extends BatteryStats { energy = info.getControllerEnergyUsed(); if (!info.getUidTraffic().isEmpty()) { for (UidTraffic traffic : info.getUidTraffic()) { - uidRxBytes.put(traffic.getUid(), traffic.getRxBytes()); - uidTxBytes.put(traffic.getUid(), traffic.getTxBytes()); + add(uidRxBytes, traffic.getUid(), traffic.getRxBytes()); + add(uidTxBytes, traffic.getUid(), traffic.getTxBytes()); } } } @@ -13317,11 +13494,13 @@ public class BatteryStatsImpl extends BatteryStats { final ControllerActivityCounterImpl counter = u.getOrCreateBluetoothControllerActivityLocked(); - counter.getRxTimeCounter().addCountLocked(scanTimeRxSinceMarkMs); - counter.getTxTimeCounters()[0].addCountLocked(scanTimeTxSinceMarkMs); + counter.getOrCreateRxTimeCounter() + .increment(scanTimeRxSinceMarkMs, elapsedRealtimeMs); + counter.getOrCreateTxTimeCounters()[0] + .increment(scanTimeTxSinceMarkMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mBluetoothPowerCalculator.calculatePowerMah( scanTimeRxSinceMarkMs, scanTimeTxSinceMarkMs, 0)); } @@ -13385,10 +13564,10 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_ENERGY) { Slog.d(TAG, "UID=" + uid + " rx_bytes=" + rxBytes + " rx_time=" + timeRxMs); } - counter.getRxTimeCounter().addCountLocked(timeRxMs); + counter.getOrCreateRxTimeCounter().increment(timeRxMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mBluetoothPowerCalculator.calculatePowerMah(timeRxMs, 0, 0)); } } @@ -13398,19 +13577,20 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_ENERGY) { Slog.d(TAG, "UID=" + uid + " tx_bytes=" + txBytes + " tx_time=" + timeTxMs); } - counter.getTxTimeCounters()[0].addCountLocked(timeTxMs); + counter.getOrCreateTxTimeCounters()[0] + .increment(timeTxMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mBluetoothPowerCalculator.calculatePowerMah(0, timeTxMs, 0)); } } } } - mBluetoothActivity.getRxTimeCounter().addCountLocked(rxTimeMs); - mBluetoothActivity.getTxTimeCounters()[0].addCountLocked(txTimeMs); - mBluetoothActivity.getIdleTimeCounter().addCountLocked(idleTimeMs); + mBluetoothActivity.getOrCreateRxTimeCounter().increment(rxTimeMs, elapsedRealtimeMs); + mBluetoothActivity.getOrCreateTxTimeCounters()[0].increment(txTimeMs, elapsedRealtimeMs); + mBluetoothActivity.getOrCreateIdleTimeCounter().increment(idleTimeMs, elapsedRealtimeMs); // POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. final double opVolt = mPowerProfile.getAveragePower( @@ -16288,6 +16468,7 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void readSummaryFromParcel(Parcel in) throws ParcelFormatException { final int version = in.readInt(); + if (version != VERSION) { Slog.w("BatteryStats", "readFromParcel: version got " + version + ", expected " + VERSION + "; erasing old stats"); @@ -17447,15 +17628,15 @@ public class BatteryStatsImpl extends BatteryStats { } mWifiActiveTimer = new StopwatchTimer(mClock, null, -900, null, mOnBatteryTimeBase, in); - mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mWifiActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS, in); for (int i=0; i<mGpsSignalQualityTimer.length; i++) { mGpsSignalQualityTimer[i] = new StopwatchTimer(mClock, null, -1000 - i, null, mOnBatteryTimeBase, in); } - mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mBluetoothActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, NUM_BT_TX_LEVELS, in); - mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mModemActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels(), in); mHasWifiReporting = in.readInt() != 0; mHasBluetoothReporting = in.readInt() != 0; @@ -17980,4 +18161,8 @@ public class BatteryStatsImpl extends BatteryStats { pw.println(); dumpMeasuredEnergyStatsLocked(pw); } + + private static void add(SparseLongArray array, int key, long delta) { + array.put(key, array.get(key) + delta); + } } diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java index c322258eda85..20535d29afcd 100644 --- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java +++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java @@ -15,6 +15,7 @@ */ package com.android.internal.os; +import android.annotation.Nullable; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryStats.ControllerActivityCounter; @@ -26,19 +27,33 @@ import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; +import java.util.Arrays; import java.util.List; public class BluetoothPowerCalculator extends PowerCalculator { private static final String TAG = "BluetoothPowerCalc"; private static final boolean DEBUG = BatteryStatsHelper.DEBUG; + + private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0]; + private final double mIdleMa; private final double mRxMa; private final double mTxMa; private final boolean mHasBluetoothPowerController; private static class PowerAndDuration { + // Return value of BT duration per app public long durationMs; + // Return value of BT power per app public double powerMah; + + public BatteryConsumer.Key[] keys; + public double[] powerPerKeyMah; + + // Aggregated BT duration across all apps + public long totalDurationMs; + // Aggregated BT power across all apps + public double totalPowerMah; } public BluetoothPowerCalculator(PowerProfile profile) { @@ -55,59 +70,88 @@ public class BluetoothPowerCalculator extends PowerCalculator { return; } - final PowerAndDuration total = new PowerAndDuration(); + BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS; + final PowerAndDuration powerAndDuration = new PowerAndDuration(); final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = builder.getUidBatteryConsumerBuilders(); for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); - calculateApp(app, total, query); + if (keys == UNINITIALIZED_KEYS) { + if (query.isProcessStateDataNeeded()) { + keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_BLUETOOTH); + powerAndDuration.keys = keys; + powerAndDuration.powerPerKeyMah = new double[keys.length]; + } else { + keys = null; + } + } + calculateApp(app, powerAndDuration, query); } final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC, query); final ControllerActivityCounter activityCounter = batteryStats.getBluetoothControllerActivity(); - final long systemDurationMs = calculateDuration(activityCounter); - final double systemPowerMah = calculatePowerMah(powerModel, measuredChargeUC, - activityCounter, query.shouldForceUsePowerProfileModel()); + calculatePowerAndDuration(null, powerModel, measuredChargeUC, + activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration); // Subtract what the apps used, but clamp to 0. - final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs); + final long systemComponentDurationMs = Math.max(0, + powerAndDuration.durationMs - powerAndDuration.totalDurationMs); if (DEBUG) { Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs) - + " power=" + formatCharge(systemPowerMah)); + + " power=" + formatCharge(powerAndDuration.powerMah)); } builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, systemDurationMs) + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, - Math.max(systemPowerMah, total.powerMah), powerModel); + Math.max(powerAndDuration.powerMah, powerAndDuration.totalPowerMah), + powerModel); builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.durationMs) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.powerMah, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) + .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.totalDurationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.totalPowerMah, powerModel); } - private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total, + private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration powerAndDuration, BatteryUsageStatsQuery query) { final long measuredChargeUC = app.getBatteryStatsUid().getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC, query); final ControllerActivityCounter activityCounter = app.getBatteryStatsUid().getBluetoothControllerActivity(); - final long durationMs = calculateDuration(activityCounter); - final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter, - query.shouldForceUsePowerProfileModel()); + calculatePowerAndDuration(app.getBatteryStatsUid(), powerModel, measuredChargeUC, + activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration); - app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, durationMs) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah, powerModel); + app.setUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.durationMs) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.powerMah, + powerModel); + + powerAndDuration.totalDurationMs += powerAndDuration.durationMs; + powerAndDuration.totalPowerMah += powerAndDuration.powerMah; - total.durationMs += durationMs; - total.powerMah += powerMah; + if (query.isProcessStateDataNeeded() && powerAndDuration.keys != null) { + for (int j = 0; j < powerAndDuration.keys.length; j++) { + BatteryConsumer.Key key = powerAndDuration.keys[j]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + app.setConsumedPower(key, powerAndDuration.powerPerKeyMah[j], powerModel); + } + } } @Override @@ -117,12 +161,12 @@ public class BluetoothPowerCalculator extends PowerCalculator { return; } - PowerAndDuration total = new PowerAndDuration(); + PowerAndDuration powerAndDuration = new PowerAndDuration(); for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper app = sippers.get(i); if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(app, app.uidObj, statsType, total); + calculateApp(app, app.uidObj, statsType, powerAndDuration); } } @@ -131,13 +175,14 @@ public class BluetoothPowerCalculator extends PowerCalculator { final int powerModel = getPowerModel(measuredChargeUC); final ControllerActivityCounter activityCounter = batteryStats.getBluetoothControllerActivity(); - final long systemDurationMs = calculateDuration(activityCounter); - final double systemPowerMah = - calculatePowerMah(powerModel, measuredChargeUC, activityCounter, false); + calculatePowerAndDuration(null, powerModel, measuredChargeUC, activityCounter, false, + powerAndDuration); // Subtract what the apps used, but clamp to 0. - final double powerMah = Math.max(0, systemPowerMah - total.powerMah); - final long durationMs = Math.max(0, systemDurationMs - total.durationMs); + final double powerMah = Math.max(0, + powerAndDuration.powerMah - powerAndDuration.totalPowerMah); + final long durationMs = Math.max(0, + powerAndDuration.durationMs - powerAndDuration.totalDurationMs); if (DEBUG && powerMah != 0) { Log.d(TAG, "Bluetooth active: time=" + (durationMs) + " power=" + formatCharge(powerMah)); @@ -160,65 +205,102 @@ public class BluetoothPowerCalculator extends PowerCalculator { } private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, - PowerAndDuration total) { - + PowerAndDuration powerAndDuration) { final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC); final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity(); - final long durationMs = calculateDuration(activityCounter); - final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter, - false); + calculatePowerAndDuration(u, powerModel, measuredChargeUC, activityCounter, + false, powerAndDuration); - app.bluetoothRunningTimeMs = durationMs; - app.bluetoothPowerMah = powerMah; + app.bluetoothRunningTimeMs = powerAndDuration.durationMs; + app.bluetoothPowerMah = powerAndDuration.powerMah; app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType); app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType); - total.durationMs += durationMs; - total.powerMah += powerMah; + powerAndDuration.totalDurationMs += powerAndDuration.durationMs; + powerAndDuration.totalPowerMah += powerAndDuration.powerMah; } - private long calculateDuration(ControllerActivityCounter counter) { + /** Returns bluetooth power usage based on the best data available. */ + private void calculatePowerAndDuration(@Nullable BatteryStats.Uid uid, + @BatteryConsumer.PowerModel int powerModel, + long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower, + PowerAndDuration powerAndDuration) { if (counter == null) { - return 0; + powerAndDuration.durationMs = 0; + powerAndDuration.powerMah = 0; + if (powerAndDuration.powerPerKeyMah != null) { + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + return; } - return counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - + counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - + counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - } - - /** Returns bluetooth power usage based on the best data available. */ - private double calculatePowerMah(@BatteryConsumer.PowerModel int powerModel, - long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower) { - if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { - return uCtoMah(measuredChargeUC); - } + final BatteryStats.LongCounter idleTimeCounter = counter.getIdleTimeCounter(); + final BatteryStats.LongCounter rxTimeCounter = counter.getRxTimeCounter(); + final BatteryStats.LongCounter txTimeCounter = counter.getTxTimeCounters()[0]; + final long idleTimeMs = idleTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + final long rxTimeMs = rxTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + final long txTimeMs = txTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - if (counter == null) { - return 0; - } + powerAndDuration.durationMs = idleTimeMs + rxTimeMs + txTimeMs; - if (!ignoreReportedPower) { - final double powerMah = - counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - / (double) (1000 * 60 * 60); - if (powerMah != 0) { - return powerMah; + if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { + powerAndDuration.powerMah = uCtoMah(measuredChargeUC); + if (uid != null && powerAndDuration.keys != null) { + for (int i = 0; i < powerAndDuration.keys.length; i++) { + BatteryConsumer.Key key = powerAndDuration.keys[i]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + powerAndDuration.powerPerKeyMah[i] = + uCtoMah(uid.getBluetoothMeasuredBatteryConsumptionUC(processState)); + } + } + } else { + if (!ignoreReportedPower) { + final double powerMah = + counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) + / (double) (1000 * 60 * 60); + if (powerMah != 0) { + powerAndDuration.powerMah = powerMah; + if (powerAndDuration.powerPerKeyMah != null) { + // Leave this use case unsupported: used energy is reported + // via BluetoothActivityEnergyInfo rather than PowerStats HAL. + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + return; + } } - } - if (!mHasBluetoothPowerController) { - return 0; + if (mHasBluetoothPowerController) { + powerAndDuration.powerMah = calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs); + + if (powerAndDuration.keys != null) { + for (int i = 0; i < powerAndDuration.keys.length; i++) { + BatteryConsumer.Key key = powerAndDuration.keys[i]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + powerAndDuration.powerPerKeyMah[i] = + calculatePowerMah( + rxTimeCounter.getCountForProcessState(processState), + txTimeCounter.getCountForProcessState(processState), + idleTimeCounter.getCountForProcessState(processState)); + } + } + } else { + powerAndDuration.powerMah = 0; + if (powerAndDuration.powerPerKeyMah != null) { + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + } } - - final long idleTimeMs = - counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - final long rxTimeMs = - counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - final long txTimeMs = - counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - return calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs); } /** Returns estimated bluetooth power usage based on usage times. */ diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index 3915b0e01e7f..2a71ac6f441b 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -25,6 +25,7 @@ import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; +import java.util.Arrays; import java.util.List; /** @@ -34,6 +35,9 @@ import java.util.List; public class WifiPowerCalculator extends PowerCalculator { private static final boolean DEBUG = BatteryStatsHelper.DEBUG; private static final String TAG = "WifiPowerCalculator"; + + private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0]; + private final UsageBasedPowerEstimator mIdlePowerEstimator; private final UsageBasedPowerEstimator mTxPowerEstimator; private final UsageBasedPowerEstimator mRxPowerEstimator; @@ -51,6 +55,9 @@ public class WifiPowerCalculator extends PowerCalculator { public long wifiTxPackets; public long wifiRxBytes; public long wifiTxBytes; + + public BatteryConsumer.Key[] keys; + public double[] powerPerKeyMah; } public WifiPowerCalculator(PowerProfile profile) { @@ -77,7 +84,7 @@ public class WifiPowerCalculator extends PowerCalculator { @Override public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { - + BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS; long totalAppDurationMs = 0; double totalAppPowerMah = 0; final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); @@ -85,9 +92,20 @@ public class WifiPowerCalculator extends PowerCalculator { builder.getUidBatteryConsumerBuilders(); for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + if (keys == UNINITIALIZED_KEYS) { + if (query.isProcessStateDataNeeded()) { + keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_WIFI); + powerDurationAndTraffic.keys = keys; + powerDurationAndTraffic.powerPerKeyMah = new double[keys.length]; + } else { + keys = null; + } + } + final long consumptionUC = app.getBatteryStatsUid().getWifiMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(consumptionUC, query); + calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), powerModel, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, batteryStats.hasWifiActivityReporting(), consumptionUC); @@ -99,6 +117,20 @@ public class WifiPowerCalculator extends PowerCalculator { powerDurationAndTraffic.durationMs); app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI, powerDurationAndTraffic.powerMah, powerModel); + + if (query.isProcessStateDataNeeded() && keys != null) { + for (int j = 0; j < keys.length; j++) { + BatteryConsumer.Key key = keys[j]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the total across all process states + continue; + } + + app.setConsumedPower(key, powerDurationAndTraffic.powerPerKeyMah[j], + powerModel); + } + } } final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC(); @@ -193,21 +225,23 @@ public class WifiPowerCalculator extends PowerCalculator { BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { - powerDurationAndTraffic.powerMah = uCtoMah(consumptionUC); - } - if (hasWifiActivityReporting && mHasWifiPowerController) { final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); if (counter != null) { - final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); - final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType); - final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); + final BatteryStats.LongCounter rxTimeCounter = counter.getRxTimeCounter(); + final BatteryStats.LongCounter txTimeCounter = counter.getTxTimeCounters()[0]; + final BatteryStats.LongCounter idleTimeCounter = counter.getIdleTimeCounter(); + + final long rxTime = rxTimeCounter.getCountLocked(statsType); + final long txTime = txTimeCounter.getCountLocked(statsType); + final long idleTime = idleTimeCounter.getCountLocked(statsType); powerDurationAndTraffic.durationMs = idleTime + rxTime + txTime; if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) { powerDurationAndTraffic.powerMah = calcPowerFromControllerDataMah(rxTime, txTime, idleTime); + } else { + powerDurationAndTraffic.powerMah = uCtoMah(consumptionUC); } if (DEBUG && powerDurationAndTraffic.powerMah != 0) { @@ -215,9 +249,32 @@ public class WifiPowerCalculator extends PowerCalculator { + "ms tx=" + txTime + "ms power=" + formatCharge( powerDurationAndTraffic.powerMah)); } + + if (powerDurationAndTraffic.keys != null) { + for (int i = 0; i < powerDurationAndTraffic.keys.length; i++) { + final int processState = powerDurationAndTraffic.keys[i].processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + continue; + } + + if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) { + powerDurationAndTraffic.powerPerKeyMah[i] = + calcPowerFromControllerDataMah( + rxTimeCounter.getCountForProcessState(processState), + txTimeCounter.getCountForProcessState(processState), + idleTimeCounter.getCountForProcessState(processState)); + } else { + powerDurationAndTraffic.powerPerKeyMah[i] = + uCtoMah(u.getWifiMeasuredBatteryConsumptionUC(processState)); + } + } + } } else { powerDurationAndTraffic.durationMs = 0; powerDurationAndTraffic.powerMah = 0; + if (powerDurationAndTraffic.powerPerKeyMah != null) { + Arrays.fill(powerDurationAndTraffic.powerPerKeyMah, 0); + } } } else { final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; @@ -233,6 +290,14 @@ public class WifiPowerCalculator extends PowerCalculator { powerDurationAndTraffic.wifiRxPackets, powerDurationAndTraffic.wifiTxPackets, wifiRunningTime, wifiScanTimeMs, batchTimeMs); + } else { + powerDurationAndTraffic.powerMah = uCtoMah(consumptionUC); + } + + if (powerDurationAndTraffic.powerPerKeyMah != null) { + // Per-process state attribution is not supported in the absence of WiFi + // activity reporting + Arrays.fill(powerDurationAndTraffic.powerPerKeyMah, 0); } if (DEBUG && powerDurationAndTraffic.powerMah != 0) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index ba7a0ef893d0..d7eeb7b8dec0 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -87,6 +87,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedDispatcher; import android.view.PendingInsetsController; import android.view.ThreadedRenderer; import android.view.View; @@ -108,6 +109,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.PopupWindow; +import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.graphics.drawable.BackgroundBlurDrawable; @@ -294,6 +296,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return true; }; private Consumer<Boolean> mCrossWindowBlurEnabledListener; + private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { @@ -322,6 +325,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind initResizingPaints(); mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK); + mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher(); } void setBackgroundFallback(@Nullable Drawable fallbackDrawable) { @@ -1869,6 +1873,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } mPendingInsetsController.detach(); + mOnBackInvokedDispatcher.detachFromWindow(); } @Override @@ -1913,6 +1918,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return mPendingInsetsController; } + @Override + public WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher() { + return mOnBackInvokedDispatcher; + } + private ActionMode createActionMode( int type, ActionMode.Callback2 callback, View originatingView) { switch (type) { @@ -2367,6 +2377,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } } + mOnBackInvokedDispatcher.clear(); } @Override @@ -2648,6 +2659,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } + /** + * Returns the {@link OnBackInvokedDispatcher} on the decor view. + */ + @Override + @Nullable + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + return mOnBackInvokedDispatcher; + } + @Override public String toString() { return "DecorView@" + Integer.toHexString(this.hashCode()) + "[" diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java index 3ca33203f554..4b3af1536175 100644 --- a/core/java/com/android/internal/util/FileRotator.java +++ b/core/java/com/android/internal/util/FileRotator.java @@ -17,7 +17,7 @@ package com.android.internal.util; import android.os.FileUtils; -import android.util.Slog; +import android.util.Log; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -32,7 +32,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import libcore.io.IoUtils; -import libcore.io.Streams; /** * Utility that rotates files over time, similar to {@code logrotate}. There is @@ -47,6 +46,8 @@ import libcore.io.Streams; * <p> * Users must periodically call {@link #maybeRotate(long)} to perform actual * rotation. Not inherently thread safe. + * + * @hide */ public class FileRotator { private static final String TAG = "FileRotator"; @@ -110,7 +111,7 @@ public class FileRotator { if (!name.startsWith(mPrefix)) continue; if (name.endsWith(SUFFIX_BACKUP)) { - if (LOGD) Slog.d(TAG, "recovering " + name); + if (LOGD) Log.d(TAG, "recovering " + name); final File backupFile = new File(mBasePath, name); final File file = new File( @@ -120,7 +121,7 @@ public class FileRotator { backupFile.renameTo(file); } else if (name.endsWith(SUFFIX_NO_BACKUP)) { - if (LOGD) Slog.d(TAG, "recovering " + name); + if (LOGD) Log.d(TAG, "recovering " + name); final File noBackupFile = new File(mBasePath, name); final File file = new File( @@ -231,7 +232,7 @@ public class FileRotator { * if the write fails. */ private void rewriteSingle(Rewriter rewriter, String name) throws IOException { - if (LOGD) Slog.d(TAG, "rewriting " + name); + if (LOGD) Log.d(TAG, "rewriting " + name); final File file = new File(mBasePath, name); final File backupFile; @@ -291,7 +292,7 @@ public class FileRotator { // read file when it overlaps if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) { - if (LOGD) Slog.d(TAG, "reading matching " + name); + if (LOGD) Log.d(TAG, "reading matching " + name); final File file = new File(mBasePath, name); readFile(file, reader); @@ -348,7 +349,7 @@ public class FileRotator { if (info.isActive()) { if (info.startMillis <= rotateBefore) { // found active file; rotate if old enough - if (LOGD) Slog.d(TAG, "rotating " + name); + if (LOGD) Log.d(TAG, "rotating " + name); info.endMillis = currentTimeMillis; @@ -358,7 +359,7 @@ public class FileRotator { } } else if (info.endMillis <= deleteBefore) { // found rotated file; delete if old enough - if (LOGD) Slog.d(TAG, "deleting " + name); + if (LOGD) Log.d(TAG, "deleting " + name); final File file = new File(mBasePath, name); file.delete(); @@ -383,7 +384,10 @@ public class FileRotator { writer.write(bos); bos.flush(); } finally { - FileUtils.sync(fos); + try { + fos.getFD().sync(); + } catch (IOException e) { + } IoUtils.closeQuietly(bos); } } diff --git a/core/java/com/android/internal/util/dump/DumpableContainerImpl.java b/core/java/com/android/internal/util/dump/DumpableContainerImpl.java new file mode 100644 index 000000000000..d48b4b136f4a --- /dev/null +++ b/core/java/com/android/internal/util/dump/DumpableContainerImpl.java @@ -0,0 +1,139 @@ +/* + * 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.util.dump; + +import android.annotation.Nullable; +import android.util.ArrayMap; +import android.util.Dumpable; +import android.util.DumpableContainer; +import android.util.IndentingPrintWriter; +import android.util.Log; + +import java.io.PrintWriter; +import java.util.Objects; + +// TODO(b/149254050): add unit tests +/** + * Helper class for {@link DumpableContainer} implementations - they can "implement it by + * association", i.e., by delegating the interface methods to a {@code DumpableContainerImpl}. + * + * @hide + */ +public final class DumpableContainerImpl implements DumpableContainer { + + private static final String TAG = DumpableContainerImpl.class.getSimpleName(); + + private static final boolean DEBUG = false; + + @Nullable + private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>(); + + @Override + public boolean addDumpable(Dumpable dumpable) { + Objects.requireNonNull(dumpable, "dumpable"); + String name = dumpable.getDumpableName(); + Objects.requireNonNull(name, () -> "name of" + dumpable); + + if (mDumpables.containsKey(name)) { + Log.e(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable" + + " with that name (" + name + "): " + mDumpables.get(name)); + return false; + } + + if (DEBUG) { + Log.d(TAG, "Adding " + name + " -> " + dumpable); + } + mDumpables.put(name, dumpable); + return true; + } + + /** + * Dumps the number of dumpable, without a newline. + */ + private int dumpNumberDumpables(IndentingPrintWriter writer) { + int size = mDumpables == null ? 0 : mDumpables.size(); + if (size == 0) { + writer.print("No dumpables"); + } else { + writer.print(size); writer.print(" dumpables"); + } + return size; + } + + /** + * Lists the name of all dumpables to the given {@code writer}. + */ + public void listDumpables(String prefix, PrintWriter writer) { + IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix); + + int size = dumpNumberDumpables(ipw); + if (size == 0) { + ipw.println(); + return; + } + ipw.print(": "); + for (int i = 0; i < size; i++) { + ipw.print(mDumpables.keyAt(i)); + if (i < size - 1) ipw.print(' '); + } + ipw.println(); + } + + /** + * Dumps the content of all dumpables to the given {@code writer}. + */ + public void dumpAllDumpables(String prefix, PrintWriter writer, String[] args) { + IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix); + int size = dumpNumberDumpables(ipw); + if (size == 0) { + ipw.println(); + return; + } + ipw.println(": "); + + for (int i = 0; i < size; i++) { + String dumpableName = mDumpables.keyAt(i); + ipw.print('#'); ipw.print(i); ipw.print(": "); ipw.println(dumpableName); + Dumpable dumpable = mDumpables.valueAt(i); + indentAndDump(ipw, dumpable, args); + } + } + + private void indentAndDump(IndentingPrintWriter writer, Dumpable dumpable, String[] args) { + writer.increaseIndent(); + try { + dumpable.dump(writer, args); + } finally { + writer.decreaseIndent(); + } + } + + /** + * Dumps the content of a specific dumpable to the given {@code writer}. + */ + @SuppressWarnings("resource") // cannot close ipw as it would close writer + public void dumpOneDumpable(String prefix, PrintWriter writer, String dumpableName, + String[] args) { + IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix); + Dumpable dumpable = mDumpables.get(dumpableName); + if (dumpable == null) { + ipw.print("No "); ipw.println(dumpableName); + return; + } + ipw.print(dumpableName); ipw.println(':'); + indentAndDump(ipw, dumpable, args); + } +} diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 139660a29ede..402fa64036a0 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -19,6 +19,7 @@ package com.android.internal.view; import android.os.IBinder; import android.os.ResultReceiver; import android.view.InputChannel; +import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethodSubtype; @@ -36,7 +37,7 @@ import com.android.internal.view.InlineSuggestionsRequestInfo; */ oneway interface IInputMethod { void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges); + int configChanges, boolean stylusHwSupported); void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo, in IInlineSuggestionsRequestCallback cb); @@ -59,4 +60,8 @@ oneway interface IInputMethod { void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver); void changeInputMethodSubtype(in InputMethodSubtype subtype); + + void canStartStylusHandwriting(int requestId); + + void startStylusHandwriting(in InputChannel channel, in List<MotionEvent> events); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 2dc7c42c95a9..0df3e870b80c 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -81,4 +81,7 @@ interface IInputMethodManager { void startImeTrace(); // Stops an ime trace. void stopImeTrace(); + + /** Start Stylus handwriting session **/ + void startStylusHandwriting(in IInputMethodClient client); } diff --git a/core/java/com/android/internal/view/RootViewSurfaceTaker.java b/core/java/com/android/internal/view/RootViewSurfaceTaker.java index efd5fb2e1edf..4b89bf5082ba 100644 --- a/core/java/com/android/internal/view/RootViewSurfaceTaker.java +++ b/core/java/com/android/internal/view/RootViewSurfaceTaker.java @@ -15,11 +15,12 @@ */ package com.android.internal.view; +import android.annotation.NonNull; import android.annotation.Nullable; import android.view.InputQueue; import android.view.PendingInsetsController; import android.view.SurfaceHolder; -import android.view.WindowInsetsController; +import android.window.WindowOnBackInvokedDispatcher; /** hahahah */ public interface RootViewSurfaceTaker { @@ -30,4 +31,6 @@ public interface RootViewSurfaceTaker { InputQueue.Callback willYouTakeTheInputQueue(); void onRootViewScrollYChanged(int scrollY); @Nullable PendingInsetsController providePendingInsetsController(); + /** @hide */ + @NonNull WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher(); } diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index d16d9c619403..db4bc2c7e24a 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -24,6 +24,8 @@ import android.security.keystore.recovery.KeyChainSnapshot; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.RecoveryCertPath; import com.android.internal.widget.ICheckCredentialProgressCallback; +import com.android.internal.widget.IWeakEscrowTokenActivatedListener; +import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; @@ -96,4 +98,10 @@ interface ILockSettings { boolean tryUnlockWithCachedUnifiedChallenge(int userId); void removeCachedUnifiedChallenge(int userId); void updateEncryptionPassword(int type, in byte[] password); + boolean registerWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener); + boolean unregisterWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener); + long addWeakEscrowToken(in byte[] token, int userId, in IWeakEscrowTokenActivatedListener callback); + boolean removeWeakEscrowToken(long handle, int userId); + boolean isWeakEscrowTokenActive(long handle, int userId); + boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId); } diff --git a/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl b/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl new file mode 100644 index 000000000000..9c8d9d61848d --- /dev/null +++ b/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl @@ -0,0 +1,22 @@ +/* + * 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.internal.widget; + +/** @hide */ +oneway interface IWeakEscrowTokenActivatedListener { + void onWeakEscrowTokenActivated(long handle, int userId); +} diff --git a/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl b/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl new file mode 100644 index 000000000000..70180484ce58 --- /dev/null +++ b/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl @@ -0,0 +1,22 @@ +/* + * 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.internal.widget; + +/** @hide */ +oneway interface IWeakEscrowTokenRemovedListener { + void onWeakEscrowTokenRemoved(long handle, int userId); +} diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 5a032774c207..f8ccde4cf4d9 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1028,15 +1028,6 @@ public class LockPatternUtils { } /** - * @return Whether tactile feedback for the pattern is enabled. - */ - @UnsupportedAppUsage - public boolean isTactileFeedbackEnabled() { - return Settings.System.getIntForUser(mContentResolver, - Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0; - } - - /** * Set and store the lockout deadline, meaning the user can't attempt their unlock * pattern until the deadline has passed. * @return the chosen deadline. @@ -1236,6 +1227,28 @@ public class LockPatternUtils { } } + /** Register the given WeakEscrowTokenRemovedListener. */ + public boolean registerWeakEscrowTokenRemovedListener( + @NonNull final IWeakEscrowTokenRemovedListener listener) { + try { + return getLockSettings().registerWeakEscrowTokenRemovedListener(listener); + } catch (RemoteException e) { + Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener."); + throw e.rethrowFromSystemServer(); + } + } + + /** Unregister the given WeakEscrowTokenRemovedListener. */ + public boolean unregisterWeakEscrowTokenRemovedListener( + @NonNull final IWeakEscrowTokenRemovedListener listener) { + try { + return getLockSettings().unregisterWeakEscrowTokenRemovedListener(listener); + } catch (RemoteException e) { + Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener."); + throw e.rethrowFromSystemServer(); + } + } + public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) { try { getLockSettings().reportSuccessfulBiometricUnlock(isStrongBiometric, userId); @@ -1355,15 +1368,38 @@ public class LockPatternUtils { } /** + * Create a weak escrow token for the current user, which can later be used to unlock FBE + * or change user password. + * + * After adding, if the user currently has lockscreen password, they will need to perform a + * confirm credential operation in order to activate the token for future use. If the user + * has no secure lockscreen, then the token is activated immediately. + * + * If the user changes or removes lockscreen password, activated weak escrow tokens will be + * removed. + * + * @return a unique 64-bit token handle which is needed to refer to this token later. + */ + public long addWeakEscrowToken(byte[] token, int userId, + @NonNull IWeakEscrowTokenActivatedListener callback) { + try { + return getLockSettings().addWeakEscrowToken(token, userId, callback); + } catch (RemoteException e) { + Log.e(TAG, "Could not add weak token."); + throw e.rethrowFromSystemServer(); + } + } + + /** * Callback interface to notify when an added escrow token has been activated. */ public interface EscrowTokenStateChangeCallback { /** * The method to be called when the token is activated. * @param handle 64 bit handle corresponding to the escrow token - * @param userid user for whom the escrow token has been added + * @param userId user for whom the escrow token has been added */ - void onEscrowTokenActivated(long handle, int userid); + void onEscrowTokenActivated(long handle, int userId); } /** @@ -1379,6 +1415,21 @@ public class LockPatternUtils { } /** + * Remove a weak escrow token. + * + * @return true if the given handle refers to a valid weak token previously returned from + * {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise. + */ + public boolean removeWeakEscrowToken(long handle, int userId) { + try { + return getLockSettings().removeWeakEscrowToken(handle, userId); + } catch (RemoteException e) { + Log.e(TAG, "Could not remove the weak token."); + throw e.rethrowFromSystemServer(); + } + } + + /** * Check if the given escrow token is active or not. Only active token can be used to call * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken} * @@ -1389,6 +1440,29 @@ public class LockPatternUtils { } /** + * Check if the given weak escrow token is active or not. Only active token can be used to call + * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken} + */ + public boolean isWeakEscrowTokenActive(long handle, int userId) { + try { + return getLockSettings().isWeakEscrowTokenActive(handle, userId); + } catch (RemoteException e) { + Log.e(TAG, "Could not check the weak token."); + throw e.rethrowFromSystemServer(); + } + } + + /** Check if the given weak escrow token is valid. */ + public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) { + try { + return getLockSettings().isWeakEscrowTokenValid(handle, token, userId); + } catch (RemoteException e) { + Log.e(TAG, "Could not validate the weak token."); + throw e.rethrowFromSystemServer(); + } + } + + /** * Change a user's lock credential with a pre-configured escrow token. * * <p>This method is only available to code running in the system server process itself. diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 3994fbdca2df..2b6b933c6886 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -139,7 +139,6 @@ public class LockPatternView extends View { private boolean mInputEnabled = true; @UnsupportedAppUsage private boolean mInStealthMode = false; - private boolean mEnableHapticFeedback = true; @UnsupportedAppUsage private boolean mPatternInProgress = false; private boolean mFadePattern = true; @@ -401,13 +400,6 @@ public class LockPatternView extends View { } /** - * @return Whether the view has tactile feedback enabled. - */ - public boolean isTactileFeedbackEnabled() { - return mEnableHapticFeedback; - } - - /** * Set whether the view is in stealth mode. If true, there will be no * visible feedback as the user enters the pattern. * @@ -427,17 +419,6 @@ public class LockPatternView extends View { } /** - * Set whether the view will use tactile feedback. If true, there will be - * tactile feedback as the user enters the pattern. - * - * @param tactileFeedbackEnabled Whether tactile feedback is enabled - */ - @UnsupportedAppUsage - public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) { - mEnableHapticFeedback = tactileFeedbackEnabled; - } - - /** * Set the call back for pattern detection. * @param onPatternListener The call back. */ @@ -783,11 +764,9 @@ public class LockPatternView extends View { addCellToPattern(fillInGapCell); } addCellToPattern(cell); - if (mEnableHapticFeedback) { - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); return cell; } return null; @@ -1462,7 +1441,7 @@ public class LockPatternView extends View { return new SavedState(superState, patternString, mPatternDisplayMode.ordinal(), - mInputEnabled, mInStealthMode, mEnableHapticFeedback); + mInputEnabled, mInStealthMode); } @Override @@ -1475,7 +1454,6 @@ public class LockPatternView extends View { mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; mInputEnabled = ss.isInputEnabled(); mInStealthMode = ss.isInStealthMode(); - mEnableHapticFeedback = ss.isTactileFeedbackEnabled(); } /** @@ -1487,20 +1465,18 @@ public class LockPatternView extends View { private final int mDisplayMode; private final boolean mInputEnabled; private final boolean mInStealthMode; - private final boolean mTactileFeedbackEnabled; /** * Constructor called from {@link LockPatternView#onSaveInstanceState()} */ @UnsupportedAppUsage private SavedState(Parcelable superState, String serializedPattern, int displayMode, - boolean inputEnabled, boolean inStealthMode, boolean tactileFeedbackEnabled) { + boolean inputEnabled, boolean inStealthMode) { super(superState); mSerializedPattern = serializedPattern; mDisplayMode = displayMode; mInputEnabled = inputEnabled; mInStealthMode = inStealthMode; - mTactileFeedbackEnabled = tactileFeedbackEnabled; } /** @@ -1513,7 +1489,6 @@ public class LockPatternView extends View { mDisplayMode = in.readInt(); mInputEnabled = (Boolean) in.readValue(null); mInStealthMode = (Boolean) in.readValue(null); - mTactileFeedbackEnabled = (Boolean) in.readValue(null); } public String getSerializedPattern() { @@ -1532,10 +1507,6 @@ public class LockPatternView extends View { return mInStealthMode; } - public boolean isTactileFeedbackEnabled(){ - return mTactileFeedbackEnabled; - } - @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); @@ -1543,7 +1514,6 @@ public class LockPatternView extends View { dest.writeInt(mDisplayMode); dest.writeValue(mInputEnabled); dest.writeValue(mInStealthMode); - dest.writeValue(mTactileFeedbackEnabled); } @SuppressWarnings({ "unused", "hiding" }) // Found using reflection diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index 627381c620c1..09ff4e0aa076 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -37,6 +37,7 @@ import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; +import android.view.RoundedCorner; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -229,13 +230,29 @@ public class PointerLocationView extends View implements InputDeviceListener, @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { + int headerPaddingTop = 0; + Insets waterfallInsets = Insets.NONE; + + final RoundedCorner topLeftRounded = + insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT); + if (topLeftRounded != null) { + headerPaddingTop = topLeftRounded.getRadius(); + } + + final RoundedCorner topRightRounded = + insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT); + if (topRightRounded != null) { + headerPaddingTop = Math.max(headerPaddingTop, topRightRounded.getRadius()); + } + if (insets.getDisplayCutout() != null) { - mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop(); - mWaterfallInsets = insets.getDisplayCutout().getWaterfallInsets(); - } else { - mHeaderPaddingTop = 0; - mWaterfallInsets = Insets.NONE; + headerPaddingTop = + Math.max(headerPaddingTop, insets.getDisplayCutout().getSafeInsetTop()); + waterfallInsets = insets.getDisplayCutout().getWaterfallInsets(); } + + mHeaderPaddingTop = headerPaddingTop; + mWaterfallInsets = waterfallInsets; return super.onApplyWindowInsets(insets); } diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java index 5e6f3a46de7d..11566d9a026d 100644 --- a/core/java/com/android/internal/widget/SlidingTab.java +++ b/core/java/com/android/internal/widget/SlidingTab.java @@ -22,10 +22,9 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.media.AudioAttributes; -import android.os.UserHandle; +import android.os.VibrationAttributes; +import android.os.VibrationEffect; import android.os.Vibrator; -import android.provider.Settings; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -68,10 +67,8 @@ public class SlidingTab extends ViewGroup { private boolean mHoldLeftOnTransition = true; private boolean mHoldRightOnTransition = true; - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); + private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); private OnTriggerListener mOnTriggerListener; private int mGrabbedState = OnTriggerListener.NO_HANDLE; @@ -834,16 +831,12 @@ public class SlidingTab extends ViewGroup { * Triggers haptic feedback. */ private synchronized void vibrate(long duration) { - final boolean hapticEnabled = Settings.System.getIntForUser( - mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, - UserHandle.USER_CURRENT) != 0; - if (hapticEnabled) { - if (mVibrator == null) { - mVibrator = (android.os.Vibrator) getContext() - .getSystemService(Context.VIBRATOR_SERVICE); - } - mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES); + if (mVibrator == null) { + mVibrator = getContext().getSystemService(Vibrator.class); } + mVibrator.vibrate( + VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE), + TOUCH_VIBRATION_ATTRIBUTES); } /** diff --git a/core/jni/Android.bp b/core/jni/Android.bp index da628635af36..a3ac472bb900 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -251,6 +251,7 @@ cc_library_shared { "spatializer-aidl-cpp", "av-types-aidl-cpp", "android.hardware.camera.device@3.2", + "libandroid_net", "libandroidicu", "libbattery", "libbpf_android", diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index d0504fb481ca..d039bcff3b26 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -66,6 +66,7 @@ struct SensorOffsets jfieldID flags; //methods jmethodID setType; + jmethodID setId; jmethodID setUuid; jmethodID init; } gSensorOffsets; @@ -112,6 +113,7 @@ nativeClassInit (JNIEnv *_env, jclass _this) sensorOffsets.flags = GetFieldIDOrDie(_env,sensorClass, "mFlags", "I"); sensorOffsets.setType = GetMethodIDOrDie(_env,sensorClass, "setType", "(I)Z"); + sensorOffsets.setId = GetMethodIDOrDie(_env,sensorClass, "setId", "(I)V"); sensorOffsets.setUuid = GetMethodIDOrDie(_env,sensorClass, "setUuid", "(JJ)V"); sensorOffsets.init = GetMethodIDOrDie(_env,sensorClass, "<init>", "()V"); @@ -188,9 +190,10 @@ translateNativeSensorToJavaSensor(JNIEnv *env, jobject sensor, const Sensor& nat env->SetObjectField(sensor, sensorOffsets.stringType, stringType); } - // TODO(b/29547335): Rename "setUuid" method to "setId". - int64_t id = nativeSensor.getId(); - env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, 0); + int32_t id = nativeSensor.getId(); + env->CallVoidMethod(sensor, sensorOffsets.setId, id); + Sensor::uuid_t uuid = nativeSensor.getUuid(); + env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]); } return sensor; } diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp index 423ef7cd980b..d7fc1cc007a6 100644 --- a/core/jni/android_media_AudioAttributes.cpp +++ b/core/jni/android_media_AudioAttributes.cpp @@ -57,7 +57,7 @@ static struct { jmethodID setUsage; jmethodID setSystemUsage; jmethodID setInternalCapturePreset; - jmethodID setContentType; + jmethodID setInternalContentType; jmethodID replaceFlags; jmethodID addTag; } gAudioAttributesBuilderMethods; @@ -127,7 +127,7 @@ static jint nativeAudioAttributesToJavaAudioAttributes( gAudioAttributesBuilderMethods.setInternalCapturePreset, attributes.source); env->CallObjectMethod(jAttributeBuilder.get(), - gAudioAttributesBuilderMethods.setContentType, + gAudioAttributesBuilderMethods.setInternalContentType, attributes.content_type); env->CallObjectMethod(jAttributeBuilder.get(), gAudioAttributesBuilderMethods.replaceFlags, @@ -202,9 +202,9 @@ int register_android_media_AudioAttributes(JNIEnv *env) gAudioAttributesBuilderMethods.setInternalCapturePreset = GetMethodIDOrDie( env, audioAttributesBuilderClass, "setInternalCapturePreset", "(I)Landroid/media/AudioAttributes$Builder;"); - gAudioAttributesBuilderMethods.setContentType = GetMethodIDOrDie( - env, audioAttributesBuilderClass, "setContentType", - "(I)Landroid/media/AudioAttributes$Builder;"); + gAudioAttributesBuilderMethods.setInternalContentType = + GetMethodIDOrDie(env, audioAttributesBuilderClass, "setInternalContentType", + "(I)Landroid/media/AudioAttributes$Builder;"); gAudioAttributesBuilderMethods.replaceFlags = GetMethodIDOrDie( env, audioAttributesBuilderClass, "replaceFlags", "(I)Landroid/media/AudioAttributes$Builder;"); diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp index afad08a87d37..1be18733e97d 100644 --- a/core/jni/android_server_NetworkManagementSocketTagger.cpp +++ b/core/jni/android_server_NetworkManagementSocketTagger.cpp @@ -15,24 +15,23 @@ */ #define LOG_TAG "NMST_QTagUidNative" -#include <utils/Log.h> - -#include <nativehelper/JNIPlatformHelp.h> -#include "jni.h" -#include <utils/misc.h> +#include <android/multinetwork.h> #include <cutils/qtaguid.h> - #include <errno.h> #include <fcntl.h> -#include <sys/types.h> +#include <nativehelper/JNIPlatformHelp.h> #include <sys/socket.h> +#include <sys/types.h> +#include <utils/Log.h> +#include <utils/misc.h> + +#include "jni.h" namespace android { -static jint QTagUid_tagSocketFd(JNIEnv* env, jclass, - jobject fileDescriptor, - jint tagNum, jint uid) { +static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, + jint tagNum, jint uid) { int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor); if (env->ExceptionCheck()) { @@ -40,15 +39,14 @@ static jint QTagUid_tagSocketFd(JNIEnv* env, jclass, return (jint)-1; } - int res = qtaguid_tagSocket(userFd, tagNum, uid); + int res = android_tag_socket_with_uid(userFd, tagNum, uid); if (res < 0) { return (jint)-errno; } return (jint)res; } -static jint QTagUid_untagSocketFd(JNIEnv* env, jclass, - jobject fileDescriptor) { +static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) { int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor); if (env->ExceptionCheck()) { @@ -56,16 +54,14 @@ static jint QTagUid_untagSocketFd(JNIEnv* env, jclass, return (jint)-1; } - int res = qtaguid_untagSocket(userFd); + int res = android_untag_socket(userFd); if (res < 0) { return (jint)-errno; } return (jint)res; } -static jint QTagUid_setCounterSet(JNIEnv* env, jclass, - jint setNum, jint uid) { - +static jint setCounterSet(JNIEnv* env, jclass, jint setNum, jint uid) { int res = qtaguid_setCounterSet(setNum, uid); if (res < 0) { return (jint)-errno; @@ -73,9 +69,7 @@ static jint QTagUid_setCounterSet(JNIEnv* env, jclass, return (jint)res; } -static jint QTagUid_deleteTagData(JNIEnv* env, jclass, - jint tagNum, jint uid) { - +static jint deleteTagData(JNIEnv* env, jclass, jint tagNum, jint uid) { int res = qtaguid_deleteTagData(tagNum, uid); if (res < 0) { return (jint)-errno; @@ -84,10 +78,10 @@ static jint QTagUid_deleteTagData(JNIEnv* env, jclass, } static const JNINativeMethod gQTagUidMethods[] = { - { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)QTagUid_tagSocketFd}, - { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)QTagUid_untagSocketFd}, - { "native_setCounterSet", "(II)I", (void*)QTagUid_setCounterSet}, - { "native_deleteTagData", "(II)I", (void*)QTagUid_deleteTagData}, + { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd}, + { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd}, + { "native_setCounterSet", "(II)I", (void*)setCounterSet}, + { "native_deleteTagData", "(II)I", (void*)deleteTagData}, }; int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) { diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 011e0514b82f..3651dbdb3fa3 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -130,6 +130,7 @@ static void init() { addHyphenator("sk", 2, 2); // Slovak addHyphenator("sl", 2, 2); // Slovenian addHyphenator("sq", 2, 2); // Albanian + addHyphenator("sv", 1, 2); // Swedish addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu addHyphenator("tk", 2, 2); // Turkmen diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index ce772cf9faff..d91d526e3d4c 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -48,6 +48,16 @@ static struct { jmethodID init; } frameRateOverrideClassInfo; + struct { + jclass clazz; + jmethodID init; + } frameTimelineClassInfo; + + struct { + jclass clazz; + jmethodID init; + } vsyncEventDataClassInfo; + } gDisplayEventReceiverClassInfo; @@ -105,9 +115,38 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal)); if (receiverObj.get()) { ALOGV("receiver %p ~ Invoking vsync handler.", this); + + ScopedLocalRef<jobjectArray> + frameTimelineObjs(env, + env->NewObjectArray(vsyncEventData.frameTimelines.size(), + gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.clazz, + /*initial element*/ NULL)); + for (int i = 0; i < vsyncEventData.frameTimelines.size(); i++) { + VsyncEventData::FrameTimeline frameTimeline = vsyncEventData.frameTimelines[i]; + ScopedLocalRef<jobject> + frameTimelineObj(env, + env->NewObject(gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.clazz, + gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.init, + frameTimeline.id, + frameTimeline.expectedPresentTime, + frameTimeline.deadlineTimestamp)); + env->SetObjectArrayElement(frameTimelineObjs.get(), i, frameTimelineObj.get()); + } + ScopedLocalRef<jobject> + vsyncEventDataJava(env, + env->NewObject(gDisplayEventReceiverClassInfo + .vsyncEventDataClassInfo.clazz, + gDisplayEventReceiverClassInfo + .vsyncEventDataClassInfo.init, + frameTimelineObjs.get(), + vsyncEventData.preferredFrameTimelineIndex, + vsyncEventData.frameInterval)); + env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync, - timestamp, displayId.value, count, vsyncEventData.id, - vsyncEventData.deadlineTimestamp, vsyncEventData.frameInterval); + timestamp, displayId.value, count, vsyncEventDataJava.get()); ALOGV("receiver %p ~ Returned from vsync handler.", this); } @@ -239,7 +278,7 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.dispatchVsync = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", - "(JJIJJJ)V"); + "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V"); gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V"); gDisplayEventReceiverClassInfo.dispatchModeChanged = @@ -258,6 +297,24 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.clazz, "<init>", "(IF)V"); + jclass frameTimelineClazz = + FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData$FrameTimeline"); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz = + MakeGlobalRefOrDie(env, frameTimelineClazz); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init = + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, + "<init>", "(JJJ)V"); + + jclass vsyncEventDataClazz = + FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData"); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz = + MakeGlobalRefOrDie(env, vsyncEventDataClazz); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init = + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, + "<init>", + "([Landroid/view/" + "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V"); + return res; } diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp index 1381de567b55..08c9f20b0815 100644 --- a/core/jni/android_window_WindowInfosListener.cpp +++ b/core/jni/android_window_WindowInfosListener.cpp @@ -72,25 +72,29 @@ struct WindowInfosListener : public gui::WindowInfosListener { return; } - jobjectArray jWindowHandlesArray = - env->NewObjectArray(windowInfos.size(), gInputWindowHandleClass, nullptr); + ScopedLocalRef<jobjectArray> + jWindowHandlesArray(env, + env->NewObjectArray(windowInfos.size(), gInputWindowHandleClass, + nullptr)); for (int i = 0; i < windowInfos.size(); i++) { ScopedLocalRef<jobject> jWindowHandle(env, android_view_InputWindowHandle_fromWindowInfo(env, windowInfos[i])); - env->SetObjectArrayElement(jWindowHandlesArray, i, jWindowHandle.get()); + env->SetObjectArrayElement(jWindowHandlesArray.get(), i, jWindowHandle.get()); } - jobjectArray jDisplayInfoArray = - env->NewObjectArray(displayInfos.size(), gDisplayInfoClassInfo.clazz, nullptr); + ScopedLocalRef<jobjectArray> + jDisplayInfoArray(env, + env->NewObjectArray(displayInfos.size(), + gDisplayInfoClassInfo.clazz, nullptr)); for (int i = 0; i < displayInfos.size(); i++) { ScopedLocalRef<jobject> jDisplayInfo(env, fromDisplayInfo(env, displayInfos[i])); - env->SetObjectArrayElement(jDisplayInfoArray, i, jDisplayInfo.get()); + env->SetObjectArrayElement(jDisplayInfoArray.get(), i, jDisplayInfo.get()); } - env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, jWindowHandlesArray, - jDisplayInfoArray); + env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, + jWindowHandlesArray.get(), jDisplayInfoArray.get()); env->DeleteGlobalRef(listener); if (env->ExceptionCheck()) { diff --git a/core/proto/android/service/netstats.proto b/core/proto/android/service/netstats.proto index c8cdfddc3985..ba2b6d6bd7e0 100644 --- a/core/proto/android/service/netstats.proto +++ b/core/proto/android/service/netstats.proto @@ -17,15 +17,11 @@ syntax = "proto2"; package android.service; -import "frameworks/base/core/proto/android/privacy.proto"; - option java_multiple_files = true; option java_outer_classname = "NetworkStatsServiceProto"; // Represents dumpsys from NetworkStatsService (netstats). message NetworkStatsServiceDumpProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - repeated NetworkInterfaceProto active_interfaces = 1; repeated NetworkInterfaceProto active_uid_interfaces = 2; @@ -45,8 +41,6 @@ message NetworkStatsServiceDumpProto { // Corresponds to NetworkStatsService.mActiveIfaces/mActiveUidIfaces. message NetworkInterfaceProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - // Name of the network interface (eg: wlan). optional string interface = 1; @@ -55,26 +49,14 @@ message NetworkInterfaceProto { // Corresponds to NetworkIdentitySet. message NetworkIdentitySetProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - repeated NetworkIdentityProto identities = 1; } // Corresponds to NetworkIdentity. message NetworkIdentityProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - // Constants from ConnectivityManager.TYPE_*. optional int32 type = 1; - // Full subscriber ID on eng builds. The IMSI is scrubbed on user & userdebug - // builds to only include the info about the GSM network operator (the info - // that uniquely identifies the subscriber is removed). - optional string subscriber_id = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; - - // Name of the network (eg: MyWifi). - optional string network_id = 3 [ (android.privacy).dest = DEST_EXPLICIT ]; - optional bool roaming = 4; optional bool metered = 5; @@ -86,8 +68,6 @@ message NetworkIdentityProto { // Corresponds to NetworkStatsRecorder. message NetworkStatsRecorderProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional int64 pending_total_bytes = 1; optional NetworkStatsCollectionProto complete_history = 2; @@ -95,15 +75,11 @@ message NetworkStatsRecorderProto { // Corresponds to NetworkStatsCollection. message NetworkStatsCollectionProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - repeated NetworkStatsCollectionStatsProto stats = 1; } // Corresponds to NetworkStatsCollection.mStats. message NetworkStatsCollectionStatsProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional NetworkStatsCollectionKeyProto key = 1; optional NetworkStatsHistoryProto history = 2; @@ -111,8 +87,6 @@ message NetworkStatsCollectionStatsProto { // Corresponds to NetworkStatsCollection.Key. message NetworkStatsCollectionKeyProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional NetworkIdentitySetProto identity = 1; optional int32 uid = 2; @@ -124,8 +98,6 @@ message NetworkStatsCollectionKeyProto { // Corresponds to NetworkStatsHistory. message NetworkStatsHistoryProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - // Duration for this bucket in milliseconds. optional int64 bucket_duration_ms = 1; @@ -134,8 +106,6 @@ message NetworkStatsHistoryProto { // Corresponds to each bucket in NetworkStatsHistory. message NetworkStatsHistoryBucketProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - // Bucket start time in milliseconds since epoch. optional int64 bucket_start_ms = 1; diff --git a/core/res/Android.bp b/core/res/Android.bp index 60630626fb68..c42517d8a873 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -37,7 +37,6 @@ license { visibility: [":__subpackages__"], license_kinds: [ "SPDX-license-identifier-Apache-2.0", - "SPDX-license-identifier-GPL", ], license_text: [ "NOTICE", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ae5414d4fa47..0fd46295c8e8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -716,6 +716,7 @@ <!-- Added in T --> <protected-broadcast android:name="android.intent.action.REFRESH_SAFETY_SOURCES" /> + <protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> @@ -2182,8 +2183,9 @@ <permission android:name="android.permission.NFC_HANDOVER_STATUS" android:protectionLevel="signature|privileged" /> - <!-- @hide Allows internal management of Bluetooth state when on wireless consent mode. - <p>Not for use by third-party applications. --> + <!-- @SystemApi Allows internal management of Bluetooth state when on wireless consent mode. + <p>Not for use by third-party applications. + @hide --> <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" android:protectionLevel="signature" /> @@ -2791,6 +2793,10 @@ <permission android:name="android.permission.INTERACT_ACROSS_PROFILES" android:protectionLevel="signature|appop" /> + <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. --> + <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES" + android:protectionLevel="signature|role" /> + <!-- @SystemApi Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that they can interact across profiles in the same profile group. @hide --> @@ -3590,6 +3596,18 @@ <permission android:name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL" android:protectionLevel="signature|privileged" /> + <!-- ========================================= --> + <!-- Permissions for SupplementalApi --> + <!-- ========================================= --> + <eat-comment /> + + <!-- TODO(b/213488783): Update with correct names. --> + <!-- Allows an application to access SupplementalApis. --> + <permission android:name="android.permission.ACCESS_SUPPLEMENTAL_APIS" + android:label="@string/permlab_accessSupplementalApi" + android:description="@string/permdesc_accessSupplementalApi" + android:protectionLevel="normal" /> + <!-- ==================================== --> <!-- Private permissions --> <!-- ==================================== --> @@ -3813,6 +3831,13 @@ <permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" android:protectionLevel="signature|recents" /> + <!-- @SystemApi Allows an application to set the system audio caption and its UI + enabled state. + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" + android:protectionLevel="signature|role" /> + <!-- Allows an application to retrieve the current state of keys and switches. <p>Not for use by third-party applications. @@ -4720,6 +4745,12 @@ <permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD" android:protectionLevel="signature|privileged|role" /> + <!-- @SystemApi Allows an application to access the ultrasound content. + <p>Not for use by third-party applications.</p> + @hide --> + <permission android:name="android.permission.ACCESS_ULTRASOUND" + android:protectionLevel="signature|privileged" /> + <!-- Puts an application in the chain of trust for sound trigger operations. Being in the chain of trust allows an application to delegate an identity of a separate entity to the sound trigger system @@ -5097,6 +5128,11 @@ <permission android:name="android.permission.SET_WALLPAPER_COMPONENT" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows applications to set the wallpaper dim amount. + @hide. --> + <permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows applications to read dream settings and dream state. @hide --> <permission android:name="android.permission.READ_DREAM_STATE" @@ -5329,6 +5365,12 @@ <permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to manage weak escrow token on the device. This permission + is not available to third party applications. + @hide --> + <permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN" + android:protectionLevel="signature|privileged" /> + <!-- Allows an application to listen to trust changes. Only allowed for system processes. @hide --> <permission android:name="android.permission.TRUST_LISTENER" @@ -5579,13 +5621,6 @@ <permission android:name="android.permission.VIEW_INSTANT_APPS" android:protectionLevel="signature|preinstalled" /> - <!-- Allows an application to interact with the currently active - {@link com.android.server.communal.CommunalManagerService}. - @hide - @TestApi --> - <permission android:name="android.permission.WRITE_COMMUNAL_STATE" - android:protectionLevel="signature" /> - <!-- Allows the holder to manage whether the system can bind to services provided by instant apps. This permission is intended to protect test/development fucntionality and should be used only in such cases. @@ -6521,6 +6556,11 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service android:name="com.android.server.SmartStorageMaintIdler" + android:exported="true" + android:permission="android.permission.BIND_JOB_SERVICE" > + </service> + <service android:name="com.android.server.ZramWriteback" android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE" > diff --git a/core/res/res/drawable/default_dream_preview.xml b/core/res/res/drawable/default_dream_preview.xml new file mode 100644 index 000000000000..bf4a04b30f19 --- /dev/null +++ b/core/res/res/drawable/default_dream_preview.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <solid android:color="@android:color/white"/> +</shape>
\ No newline at end of file diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 74ac6806a9de..3bc02830b0b9 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Laat die program toe om Moenie Steur Nie-opstelling te lees en skryf."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"begin kyk van toestemminggebruik"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Laat die houer toe om die toestemminggebruik vir \'n program te begin. Behoort nooit vir normale programme nodig te wees nie."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"begin Bekyk Toestemmingbesluite"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Laat die houer toe om skerm te begin om toestemmingbesluite na te gaan. Behoort nooit vir normale programme nodig te wees nie."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"begin Bekyk Programkenmerke"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Laat die houer toe om die kenmerke-inligting vir \'n program te begin bekyk."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"kry toegang tot sensordata teen \'n hoë monsternemingkoers"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 3f5b5e033275..fb52e6ad965e 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"መተግበሪያው የአትረብሽ ውቅረትን እንዲያነብብ እና እንዲጸፍ ይፈቅዳል።"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"የእይታ ፈቃድ መጠቀምን መጀመር"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ያዢው ለአንድ መተግበሪያ የፈቃድ አጠቃቀሙን እንዲያስጀምር ያስችለዋል። ለመደበኛ መተግበሪያዎች በጭራሽ ሊያስፈልግ አይገባም።"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"የእይታ ፈቃድ ውሳኔዎችን ይጀምሩ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ያዢው የፈቃድ ውሳኔዎችን ለመገምገም ማያ ገጽ እንዲጀምሩ ያስችላቸዋል። ለመደበኛ መተግበሪያዎች በጭራሽ ሊያስፈልግ አይገባም።"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"የመተግበሪያ ባህሪያትን ማየት መጀመር"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ያዢው የአንድ መተግበሪያ የባህሪያት መረጃን ማየት እንዲጀምር ያስችለዋል።"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"የዳሳሽ ውሂቡን በከፍተኛ የናሙና ብዛት ላይ ይድረሱበት"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 9dc59c0c8f9a..12d1b83ea1df 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -745,10 +745,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"للسماح للتطبيق بقراءة إعداد \"عدم الإزعاج\" وكتابتها."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"بدء استخدام إذن العرض"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"للسماح للمالك ببدء استخدام الإذن لأحد التطبيقات. ولن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"بدء اتخاذ القرارات المتعلقة بالإذن بعرض البيانات"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"يمكنك السماح للمالك ببدء الشاشة لمراجعة القرارات المتعلقة بالأذونات. لا حاجة لذلك مع التطبيقات العادية."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"بدء عرض ميزات التطبيق"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"للسماح للمالك ببدء عرض معلومات عن ميزات التطبيق."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"الوصول إلى بيانات جهاز الاستشعار بمعدّل مرتفع للبيانات في الملف الصوتي"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index bd2b97dde961..f57e42bf7664 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অসুবিধা নিদিবৰ কনফিগাৰেশ্বনক পঢ়িবলৈ আৰু সালসলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"চোৱাৰ অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰক"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ধাৰকক কোনো এপৰ বাবে অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰিবলৈ দিয়ে। সাধাৰণ এপ্সমূহৰ বাবে কেতিয়াও প্ৰয়োজন হ’ব নালাগে।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"অনুমতিৰ সিদ্ধান্তসমূহ চোৱা আৰম্ভ কৰক"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ধাৰকক অনুমতিৰ সিদ্ধান্তসমূহ পৰ্যালোচনা কৰিবলৈ স্ক্ৰীন আৰম্ভ কৰিবলৈ দিয়ে। সাধাৰণ এপ্সমূহৰ বাবে কেতিয়াও প্ৰয়োজন হ’ব নালাগে।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"এপৰ সুবিধাসমূহৰ সম্পর্কীয় তথ্য চোৱাটো আৰম্ভ কৰক"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ধাৰকক কোনো এপৰ সুবিধাসমূহৰ সম্পর্কীয় তথ্য চোৱাটো আৰম্ভ কৰিবলৈ দিয়ে।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"এটা উচ্চ ছেম্পলিঙৰ হাৰত ছেন্সৰৰ ডেটা এক্সেছ কৰে"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 3b0c6c863840..76384f9ce6b7 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Tətbiqə \"Narahat Etməyin\" konfiqurasiyasını oxumağa və yazmağa icazə verin."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"Baxış icazəsinin istifadəsinə başlayın"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Sahibinə tətbiqin icazədən istifadəsinə başlamağa imkan verir. Adi tətbiqlər üçün heç vaxt tələb edilmir."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"icazə qərarlarına baxışı başladın"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Sahibin icazə qərarlarını nəzərdən keçirmək üçün ekranı başlatmasına icazə verir. Normal tətbiqlər tərəfindən heç vaxt tələb edilmir."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"tətbiqin funksiyalarını görməyə başlamaq"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"İstifadəçinin tətbiqin funksiyaları barədə məlumatları görməyə başlamasına icazə verir."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"sensor datasına yüksək ölçmə sürəti ilə giriş etmək"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 96f8d7d48913..15e3aa09cec5 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Dozvoljava aplikaciji da čita i upisuje konfiguraciju podešavanja Ne uznemiravaj."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"početak korišćenja dozvole za pregled"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Dozvoljava vlasniku da započne korišćenje dozvole za aplikaciju. Nikada ne bi trebalo da bude potrebna za uobičajene aplikacije."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"pokretanje pregleda odluka o dozvolama"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Dozvoljava vlasniku da pokrene ekran za proveru odluka o dozvolama. Nikada ne bi trebalo da bude potrebno za obične aplikacije."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pokretanje prikaza funkcija aplikacije"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Dozvoljava nosiocu dozvole da započne pregledanje informacija o funkcijama aplikacije."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pristup podacima senzora pri velikoj brzini uzorkovanja"</string> @@ -1552,7 +1550,7 @@ <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Veza sa uvek uključenim VPN-om je prekinuta"</string> <string name="vpn_lockdown_error" msgid="4453048646854247947">"Povezivanje na stalno uključeni VPN nije uspelo"</string> <string name="vpn_lockdown_config" msgid="8331697329868252169">"Promenite podešavanja VPN-a"</string> - <string name="upload_file" msgid="8651942222301634271">"Odaberi datoteku"</string> + <string name="upload_file" msgid="8651942222301634271">"Odaberi fajl"</string> <string name="no_file_chosen" msgid="4146295695162318057">"Nije izabrana nijedna datoteka"</string> <string name="reset" msgid="3865826612628171429">"Resetuj"</string> <string name="submit" msgid="862795280643405865">"Pošalji"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index b44449901571..e182c286ce40 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Дазваляе праграме чытаць і выконваць запіс у канфігурацыю рэжыму «Не турбаваць»."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"запусціць выкарыстанне дазволаў на прагляд"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Дазваляе трымальніку запусціць выкарыстанне дазволаў праграмай. Не патрэбна для звычайных праграм."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"запускаць прагляд рашэнняў наконт дазволаў"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Дазваляе ўладальніку запускаць экран, на якім можна праглядаць рашэнні наконт дазволаў. Ніколі не павінна патрабавацца для звычайных праграм."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"запусціць прагляд функцый праграмы"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Дазваляе трымальніку запусціць прагляд інфармацыі пра функцыі для праграмы."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"атрымліваць даныя датчыка з высокай частатой дыскрэтызацыі"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index c3d8410f80e8..41aa9f7ed95e 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Предоставя на приложението достъп за четене и запис до конфигурацията на „Не безпокойте“."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"стартиране на прегледа на използваните разрешения"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Разрешава на притежателя да стартира прегледа на използваните разрешения за дадено приложение. Нормалните приложения би трябвало никога да не се нуждаят от това."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"стартиране на прегледа на решенията за разрешенията"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Дава възможност на притежателя да стартира екрана с цел преглед на решенията за разрешенията. Нормалните приложения би трябвало никога да не се нуждаят от това."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"стартиране на прегледа на функциите на приложението"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Разрешава на притежателя да стартира прегледа на информацията за функциите на дадено приложение."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"осъществяване на достъп до данните от сензорите при висока скорост на семплиране"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 6bcf9aa8a127..7dac240dd079 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অ্যাপটিকে \'বিরক্ত করবে না\' কনফিগারেশন পড়া এবং লেখার অনুমতি দেয়।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"দেখার অনুমতি কাজে লাগানো শুরু করুন"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"কোনও অ্যাপের কোনও নির্দিষ্ট অনুমতির ব্যবহার শুরু করার ক্ষেত্রে হোল্ডারকে সাহায্য করে। সাধারণ অ্যাপের জন্য এটির পরিবর্তন হওয়ার কথা নয়।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"অনুমতি সংক্রান্ত সিদ্ধান্ত দেখা শুরু করুন"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"অনুমতি সংক্রান্ত সিদ্ধান্ত পর্যালোচনা করার জন্য, হোল্ডারকে স্ক্রিন চালু করতে দেয়। সাধারণ অ্যাপের জন্য কখনই প্রয়োজন হওয়া উচিত নয়।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"অ্যাপের ফিচার দেখা শুরু করুন"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"কোনও অ্যাপের ফিচার সম্পর্কিত তথ্য দেখা শুরু করতে অনুমতি দেয়।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"হাই স্যাম্পলিং রেটে সেন্সর ডেটা অ্যাক্সেস করুন"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index c1823c9e9d5b..28ca86d2e534 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Omogućava aplikaciji da čita i upisuje konfiguraciju načina rada Ne ometaj."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"pokrenuti korištenje odobrenja za pregled"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Dozvoljava vlasniku da pokrene korištenje odobrenja za aplikaciju. Ne bi trebalo biti potrebno za obične aplikacije."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"prikažite odluke o odobrenjima"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Dozvoljava vlasniku da pokrene ekran radi pregleda odluka o odobrenju. Obično nije potrebno za obične aplikacije."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pokretanje pregleda funkcija aplikacije"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Dozvoljava vlasniku da pokrene pregled informacija o funkcijama za aplikaciju."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pristup podacima senzora velikom brzinom uzorkovanja"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 325b6207e65f..be1b43c69670 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permet que l\'aplicació llegeixi la configuració No molestis i hi escrigui."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"comença a utilitzar el permís de visualització"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permet que un propietari comenci a utilitzar el permís amb una aplicació. No s\'hauria de necessitar mai per a les aplicacions normals."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"inicia la visualització de les decisions sobre permisos"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permet que l\'aplicació en qüestió iniciï la pantalla per revisar les decisions sobre permisos. No s\'hauria de necessitar mai per a les aplicacions normals."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"iniciar la visualització de les funcions d\'una aplicació"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permet que el propietari vegi la informació de les funcions d\'una aplicació."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"accedir a les dades del sensor a una freqüència de mostratge alta"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 68128a490fe1..d038dfcac045 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Umožňuje aplikaci číst a zapisovat konfiguraci režimu Nerušit."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"zahájení zobrazení využití oprávnění"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Umožňuje přístup zahájit využití oprávnění jiné aplikace. Běžné aplikace by toto oprávnění neměly nikdy požadovat."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"zobrazit rozhodnutí o oprávnění"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Umožňuje držiteli aktivovat obrazovku a zkontrolovat rozhodnutí o oprávnění. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"zobrazení informací o funkcích aplikace"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Umožňuje držiteli zobrazit informace o funkcích aplikace."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"přístup k datům ze senzorů s vyšší vzorkovací frekvencí"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 9134c570fd1a..afe1175aca56 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Giver appen tilladelse til at læse og redigere konfigurationen af Forstyr ikke."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"start brugen at tilladelsesvisning"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Tillader, at brugeren kan bruge en tilladelse for en app. Dette bør aldrig være nødvendigt for almindelige apps."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"starte visningen af beslutninger om tilladelser"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Giver modtageren mulighed for at åbne skærmen til gennemgang af beslutninger om tilladelser. Dette bør aldrig være nødvendigt for standardapps."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"se appfunktioner"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Giver den app, som har tilladelsen, mulighed for at se oplysninger om en apps funktioner."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"få adgang til sensordata ved høj samplingfrekvens"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index ed2a92affb88..ecaa63c1a337 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ermöglicht der App Lese- und Schreibzugriff auf die „Bitte nicht stören“-Konfiguration"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"Mit der Verwendung der Anzeigeberechtigung beginnen"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Ermöglicht dem Inhaber, die Berechtigungsnutzung für eine App zu beginnen. Sollte für normale Apps nie benötigt werden."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Entscheidungen zu Leseberechtigung starten"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Ermöglicht dem Inhaber, das Display zu starten, um Berechtigungsentscheidungen anzusehen. Sollte für normale Apps nie benötigt werden."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"App-Funktionen ansehen"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Ermöglicht der App, die Informationen über die Funktionen einer anderen App anzusehen."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"Sensordaten mit hoher Frequenz auslesen"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index ef2364008782..44fe4261526b 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Επιτρέπει στην εφαρμογή την εγγραφή και τη σύνταξη διαμόρφωσης για τη λειτουργία \"Μην ενοχλείτε\"."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"έναρξη χρήσης άδειας προβολής"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Επιτρέπει στον κάτοχο να ξεκινήσει τη χρήση της άδειας για μια εφαρμογή. Δεν απαιτείται ποτέ για κανονικές εφαρμογές."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"έναρξη προβολής αποφάσεων για άδειες"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Επιτρέπει στον κάτοχο να ξεκινήσει την προβολή για τον έλεγχο των αποφάσεων για τις άδειες. Δεν χρειάζεται ποτέ για κανονικές εφαρμογές."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"έναρξη προβολής λειτουργιών εφαρμογής"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Επιτρέπει στον κάτοχο να ξεκινήσει την προβολή των πληροφοριών για τις λειτουργίες μιας εφαρμογής."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"πρόσβαση σε δεδομένα αισθητήρα με υψηλό ρυθμό δειγματοληψίας"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index a5cb42199580..fad71eb7d67c 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite que la aplicación lea y modifique la configuración de la función No interrumpir."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso de permiso de vista"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que el propietario inicie el uso de permisos para una app. No debería requerirse para apps normales."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"iniciar vista de las decisiones sobre permisos"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite que el propietario vea la pantalla a fin de revisar las decisiones que tomó sobre los permisos. Las apps normales no deberían necesitar este permiso."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"iniciar vista de funciones de la app"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite que el propietario vea la información de las funciones de una app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"Acceder a los datos de sensores a una tasa de muestreo alta"</string> @@ -779,7 +777,7 @@ <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Evita el uso de algunas funciones de bloqueo de pantalla."</string> <string-array name="phoneTypes"> <item msgid="8996339953292723951">"Casa"</item> - <item msgid="7740243458912727194">"Móvil"</item> + <item msgid="7740243458912727194">"Celular"</item> <item msgid="8526146065496663766">"Trabajo"</item> <item msgid="8150904584178569699">"Fax laboral"</item> <item msgid="4537253139152229577">"Fax residencial"</item> @@ -822,7 +820,7 @@ </string-array> <string name="phoneTypeCustom" msgid="5120365721260686814">"Personalizado"</string> <string name="phoneTypeHome" msgid="3880132427643623588">"Casa"</string> - <string name="phoneTypeMobile" msgid="1178852541462086735">"Móvil"</string> + <string name="phoneTypeMobile" msgid="1178852541462086735">"Celular"</string> <string name="phoneTypeWork" msgid="6604967163358864607">"Trabajo"</string> <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Fax laboral"</string> <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Fax personal"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 21910b9cb188..669e914ef8fb 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite que la aplicación lea y modifique la configuración de No molestar."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso de permiso de visualización"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que el titular inicie el uso de permisos de una aplicación. Las aplicaciones normales no deberían necesitar nunca este permiso."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"iniciar la revisión de decisiones sobre los permisos"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite que el titular inicie la revisión de las decisiones sobre los permisos. Las aplicaciones normales no deberían necesitar nunca este permiso."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ver funciones de una aplicación"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite que el titular vea la información de las funciones de una aplicación."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"acceder a datos de sensores a una frecuencia de muestreo alta"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 118d605bd6ec..e4d33a7a1e6e 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Võimaldab rakendusel lugeda ja kirjutada funktsiooni Mitte segada seadistusi."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"vaatamisloa kasutamise alustamine"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Võimaldab omanikul rakenduse puhul alustada loa kasutamist. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Alustada lubade otsuste vaatamist."</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Võimaldab omanikul ekraani käivitada, et lubade otsused üle vaadata. Tavaliste rakenduste puhul ei tohiks seda kunagi vaja minna."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"rakenduse funktsioonide vaatamise alustamine"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Võimaldab omanikul alustada rakenduse funktsioonide teabe vaatamist."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"juurdepääs anduri andmetele kõrgel diskreetimissagedusel"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 2c3bdfb2df38..a3766b767f7d 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -562,7 +562,7 @@ <string name="permlab_useBiometric" msgid="6314741124749633786">"erabili hardware biometrikoa"</string> <string name="permdesc_useBiometric" msgid="7502858732677143410">"Autentifikatzeko hardware biometrikoa erabiltzeko baimena ematen die aplikazioei."</string> <string name="permlab_manageFingerprint" msgid="7432667156322821178">"kudeatu hatz-marken hardwarea"</string> - <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Erreferentzia-gako digitalen txantiloiak gehitzeko eta ezabatzeko metodoei dei egitea baimentzen die aplikazioei."</string> + <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Aztarna digitalaren txantiloiak gehitzeko eta ezabatzeko metodoei dei egitea baimentzen die aplikazioei."</string> <string name="permlab_useFingerprint" msgid="1001421069766751922">"erabili hatz-marken hardwarea"</string> <string name="permdesc_useFingerprint" msgid="412463055059323742">"Autentifikatzeko hatz-marken hardwarea erabiltzeko baimena ematen die aplikazioei."</string> <string name="permlab_audioWrite" msgid="8501705294265669405">"musika-bilduma aldatu"</string> @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ez molestatzeko moduaren konfigurazioa irakurtzeko eta bertan idazteko baimena ematen die aplikazioei."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"hasi ikusteko baimena erabiltzen"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Aplikazioaren baimena erabiltzen hasteko baimena ematen die titularrei. Aplikazio normalek ez lukete beharko."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"hasi baimenen inguruko erabakiak ikusten"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Baimenen inguruko erabakiak berrikusteko pantaila ikusten hasteko baimena ematen die titularrei. Aplikazio normalek ez lukete beharko."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"hasi aplikazioaren eginbideak ikusten"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Aplikazio baten eginbideei buruzko informazioa ikusten hasteko baimena ematen die titularrei."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"atzitu sentsoreen datuen laginak abiadura handian"</string> @@ -1635,8 +1633,8 @@ <string name="expires_on" msgid="1623640879705103121">"Iraungitze-data:"</string> <string name="serial_number" msgid="3479576915806623429">"Serie-zenbakia:"</string> <string name="fingerprints" msgid="148690767172613723">"Erreferentzia-fitxategiak:"</string> - <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 erreferentzia-gako digitala:"</string> - <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 erreferentzia-gako digitala:"</string> + <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 aztarna digitala:"</string> + <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 aztarna digitala:"</string> <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"Ikusi guztiak"</string> <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Aukeratu jarduera"</string> <string name="share_action_provider_share_with" msgid="1904096863622941880">"Partekatu hauekin:"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 050580c062fd..c9dedf919195 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"به برنامه امکان میدهد پیکربندی «مزاحم نشوید» را بخواند و بنویسد."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"شروع مشاهده استفاده از مجوز"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"به دارنده اجازه شروع استفاده از مجوز را برای برنامه میدهد. هرگز برای برنامههای معمول نیاز نیست."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"شروع مشاهده تصمیمهای مربوط به اجازهها"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"به دارنده امکان میدهد صفحه را برای مرور تصمیمهای مربوط به اجازهها شروع کند. هرگز نباید برای برنامههای عادی لازم باشد."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"مشاهده ویژگیهای برنامه"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"به دارنده اجازه میدهد اطلاعات مربوط به ویژگیهای برنامه را مشاهده کند."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"دسترسی به دادههای حسگر با نرخ نمونهبرداری بالا"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index d511e2898779..b7234beabb6c 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Sallii sovelluksen lukea ja muokata Älä häiritse -tilan asetuksia."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"aloita katseluoikeuksien käyttö"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Antaa luvanhaltijan käynnistää sovelluksen käyttöoikeuksien käytön. Ei tavallisten sovelluksien käyttöön."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"aloita lupapäätösten tarkistaminen"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Antaa luvanhaltijan käynnistää näytön lupapäätösten tarkistamiseksi. Ei tavallisten sovellusten käyttöön."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"aloittaa sovellusominaisuuksien katselun"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Antaa luvanhaltijan aloittaa sovelluksen ominaisuustietojen katselun."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"saada pääsyn anturidataan suuremmalla näytteenottotaajuudella"</string> @@ -1647,7 +1645,7 @@ <string name="activity_resolver_use_once" msgid="948462794469672658">"Vain kerran"</string> <string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"%1$s ei tue työprofiilia"</string> <string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"Tabletti"</string> - <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"Televisio"</string> + <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string> <string name="default_audio_route_name" product="default" msgid="9213546147739983977">"Puhelin"</string> <string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"Telineen kaiuttimet"</string> <string name="default_audio_route_name_hdmi" msgid="5474470558160717850">"HDMI"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 2723a6d79731..6a08237d41ef 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permet à l\'application de consulter et de modifier la configuration du mode Ne pas déranger."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"démarrer l\'affichage de l\'usage des autorisations"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permet au détenteur de démarrer l\'usage des autorisations pour une application. Cette fonctionnalité ne devrait pas être nécessaire pour les applications standards."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"démarrer les décisions d\'autorisation de lecture"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permet au détenteur de démarrer l\'écran pour revoir les décisions d\'autorisation. Ne devrait pas être requis pour les applications standards."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"démarrer l\'affichage des fonctionnalités de l\'application"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permet au détenteur de commencer à afficher les renseignements sur les fonctionnalités d\'une application."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"accéder aux données des capteurs à un taux d’échantillonnage élevé"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 365b68fcf554..c606ed3048e6 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permet à l\'application de consulter et de modifier la configuration du mode Ne pas déranger."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"activer l\'utilisation de l\'autorisation d\'affichage"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permet à l\'application autorisée d\'activer l\'utilisation de l\'autorisation pour une application. Cette fonctionnalité ne devrait pas être nécessaire pour les applications standards."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"activer l\'affichage des décisions liées aux autorisations"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permet à l\'appli autorisée de lancer un écran pour examiner les décisions liées aux autorisations. Ne devrait jamais être nécessaire pour les applis standards."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"commencer à voir les fonctionnalités d\'une appli"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permet à l\'appli autorisée de commencer à voir les infos sur les fonctionnalités d\'une appli."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"accéder aux données des capteurs à un taux d\'échantillonnage élevé"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 9c0d33a6a908..24287d92c5c2 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite á aplicación ler e escribir a configuración do modo Non molestar."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso de permiso de vista"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite ao propietario iniciar o uso de permisos dunha aplicación. As aplicacións normais non deberían precisalo nunca."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"iniciar vista das decisións sobre os permisos"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite que a persoa propietaria acceda a unha pantalla onde poderá examinar as decisións relativas aos permisos. Esta opción nunca debería ser necesaria para as aplicacións normais."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"comezar a ver as funcións da aplicación"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite que o propietario comece a ver a información das funcións dunha aplicación."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"acceder aos datos dos sensores usando unha taxa de mostraxe alta"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 2996cc3e8771..5ca2c39e2f99 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"એપ્લિકેશનને ખલેલ પાડશો નહીં ગોઠવણી વાંચવા અને લખવાની મંજૂરી આપે છે."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"પરવાનગી વપરાશ જુઓને શરૂ કરો"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"કોઈ ઍપ માટે પરવાનગી વપરાશ શરૂ કરવાની ધારકને મંજૂરી આપે છે. સામાન્ય ઍપ માટે ક્યારેય જરૂર પડી ન શકે."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"પરવાનગી સંબંધિત નિર્ણયો જોવાનું શરૂ કરો"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ધારકને પરવાનગી સંબંધિત નિર્ણયોનો રિવ્યૂ કરવા માટે સ્ક્રીન શરૂ કરવાની મંજૂરી આપે છે. સામાન્ય ઍપ માટે ક્યારેય જરૂરી હોવું જોઈએ નહીં."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ઍપની સુવિધાઓ જોવા માટેની પરવાનગી ચાલુ કરો"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ધારકને ઍપ માટેની સુવિધાઓની માહિતી જોવાનું શરૂ કરવાની મંજૂરી આપે છે."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ઉચ્ચ સેમ્પ્લિંગ રેટ પર સેન્સરનો ડેટા ઍક્સેસ કરો"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 5c6eaeb26581..b846ff8024c3 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ऐप को परेशान न करें कॉन्फ़िगरेशन पढ़ने और लिखने देती है."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"देखने की अनुमतियां चालू करें"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"इस्तेमाल करने वाले को किसी ऐप्लिकेशन के लिए अनुमतियों का इस्तेमाल शुरू करने देता है. सामान्य ऐप्लिकेशन के लिए इसकी ज़रूरत कभी नहीं पड़ती."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"अनुमतियों को देखना चालू करना"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ऐप्लिकेशन को स्क्रीन शुरू करने की अनुमति मिलती है, ताकि अनुमतियों की समीक्षा की जा सके. सामान्य ऐप्लिकेशन के लिए, इसकी ज़रूरत कभी नहीं पड़ती."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ऐप्लिकेशन की सुविधाओं को देखना शुरू करें"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ऐप्लिकेशन को, किसी ऐप्लिकेशन की सुविधाओं की जानकारी देखने की अनुमति देता है."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"सेंसर डेटा को, नमूने लेने की तेज़ दर पर ऐक्सेस करें"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 15f59dc83281..e88b331c7ff3 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Omogućuje aplikaciji čitanje i pisanje konfiguracije opcije Ne uznemiravaj."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"pokrenuti upotrebu dopuštenja za pregled"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Dopušta nositelju pokretanje upotrebe dopuštenja za aplikaciju. Ne bi smjelo biti potrebno za uobičajene aplikacije."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"pokrenuti odluke o dopuštenju za pregled"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Dopušta nositelju pokretanje zaslona za pregled odluka o dopuštenjima. Ne bi trebalo biti potrebno za uobičajene aplikacije."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pokretanje prikaza značajki aplikacije"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Dopušta nositelju pokretanje prikaza informacija o značajkama aplikacije."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pristup podacima senzora pri višoj brzini uzorkovanja"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index b7e35cf2481d..46fcc2a40677 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Az alkalmazás olvashatja és szerkesztheti a „Ne zavarjanak” funkció beállításait."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"engedélyhasználat megtekintésének elindítása"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Lehetővé teszi a felhasználó számára, hogy elindítsa az alkalmazás engedélyhasználatát. A normál alkalmazásoknak erre soha nincs szükségük."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"engedélyezési döntések megtekintésének elindítása"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Lehetővé teszi az alkalmazás számára, hogy elindítsa a képernyőt az engedélyezési döntések felülvizsgálata érdekében. A normál alkalmazások esetében erre nincs szükség."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"alkalmazásfunkciók megtekintésének megkezdése"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Engedélyezi az alkalmazás számára, hogy megkezdje az alkalmazások funkcióira vonatkozó adatok megtekintését."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"hozzáférés a szenzoradatokhoz nagy mintavételezési gyakorisággal"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 13d87803475f..b60f08b82374 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Թույլ է տալիս հավելվածին փոփոխել «Չանհանգստացնել» գործառույթի կազմաձևումը:"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"թույլտվությունների մասին տվյալների հասանելիություն"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Հավելվածին հասանելի կդառնան թույլտվությունների մասին տվյալները։ Այս թույլտվությունն անհրաժեշտ չէ սովորական հավելվածներին։"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"սկսել թույլտվությունների հետ գործողությունների դիտումը"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Թույլ է տալիս դիտել թույլտվությունների հետ գործողությունները։ Սովորական հավելվածների համար երբևէ չպետք է անհրաժեշտ լինի։"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"հավելվածի գործառույթների դիտում"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Թույլ է տալիս դիտել հավելվածի գործառույթների մասին տեղեկությունները։"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"օգտագործել սենսորների տվյալները բարձր հաճախականության վրա"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 997b25e2d6ba..7925b3f1ce6b 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Mengizinkan aplikasi membaca dan menulis konfigurasi status Jangan Ganggu."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"mulai melihat penggunaan izin"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Memungkinkan pemegang memulai penggunaan izin untuk aplikasi. Tidak diperlukan untuk aplikasi normal."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"mulai melihat keputusan izin"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Mengizinkan pemegang memulai layar untuk meninjau keputusan izin. Tidak pernah dibutuhkan untuk aplikasi normal."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"mulai lihat fitur aplikasi"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Memungkinkan pemegang mulai melihat info fitur untuk aplikasi."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"mengakses data sensor pada frekuensi pengambilan sampel yang tinggi"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 99e4d1a1daa6..c758de5f406b 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Leyfir forriti að lesa og skrifa í grunnstillingu „Ónáðið ekki“."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"heimildanotkun upphafsyfirlits"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Leyfir handhafa að byrja heimildanotkun fyrir forrit. Ætti aldrei að þurfa fyrir venjuleg forrit."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"opna ákvarðanir um skoðunarheimildir"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Gerir notandanum kleift að opna skjá til að fara yfir ákvarðanir um heimildir. Þetta ætti aldrei að þurfa fyrir venjuleg forrit."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"byrja að skoða eiginleika forrits"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Leyfir handhafa að skoða upplýsingar um eiginleika tiltekins forrits."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"aðgangur að skynjaragögnum með hárri upptökutíðni"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 4b3f305faf65..c5683faa45b4 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Consente all\'app di leggere e modificare la configurazione della funzione Non disturbare."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"avvio dell\'uso dell\'autorizzazione di visualizzazione"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Consente al titolare di avviare l\'uso delle autorizzazioni per un\'app. Non dovrebbe essere mai necessaria per le normali applicazioni."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Inizio della visualizzazione delle decisioni relative all\'autorizzazione"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Consente all\'app che ha questa autorizzazione di avviare lo schermo per esaminare le decisioni relative all\'autorizzazione. Non dovrebbe mai essere necessaria per le normali app."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"Inizio della visualizzazione di funzionalità delle app"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Consente all\'app che ha questa autorizzazione di iniziare a visualizzare le informazioni relative alle funzionalità di un\'app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"Accesso ai dati dei sensori a una frequenza di campionamento elevata"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index bdc87fe8f4b6..9da03ea1538e 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"מאפשרת לאפליקציה לקרוא ולכתוב את התצורה של התכונה \'נא לא להפריע\'."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"התחלת צפייה בהרשאות השימוש"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"מאפשרת לבעלים להפעיל את השימוש בהרשאות עבור אפליקציה מסוימת. הרשאה זו אף פעם לא נדרשת עבור אפליקציות רגילות."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ההחלטות לגבי ההרשאות להפעלת התצוגה"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"בעלי ההרשאה יוכלו להפעיל את המסך כדי לעיין בהחלטות לגבי הרשאות. ההרשאה לא נחוצה לאפליקציות רגילות בדרך כלל."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"התחלת צפייה בהרשאות של אפליקציות"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"בעלי ההרשאה יוכלו להתחיל לצפות בפרטי התכונות של אפליקציות."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"גישה לנתוני חיישנים בתדירות דגימה גבוהה"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index e6e2ed82afa0..76d248c4467a 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"サイレント モード設定の読み取りと書き込みをアプリに許可します。"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"表示権限の使用の開始"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"アプリの権限使用の開始を所有者に許可します。通常のアプリでは不要です。"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"閲覧権限の許可 / 拒否の開始"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"権限の許可 / 拒否を確認するための画面の開始を所有者に許可します。通常のアプリでは不要です。"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"アプリ機能の表示の開始"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"アプリの機能情報の表示の開始を所有者に許可します。"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"高サンプリング レートでセンサーデータにアクセスする"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 16f3fe03d8a3..91144c65832f 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"საშუალებას აძლევს აპს, წაიკითხოს და დაწეროს კონფიგურაცია „არ შემაწუხოთ“."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ნახვის ნებართვის გამოყენების დაწყება"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"მფლობელს საშუალებას აძლევს, დაიწყოს აპის ნებართვის გამოყენება. ჩვეულებრივი აპებისთვის არასოდეს უნდა იყოს საჭირო."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ნებართვის შესახებ გადაწყვეტილებების ნახვის დაწყება"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"მფლობელს საშუალებას აძლევს, გაუშვას ეკრანი ნებართვის შესახებ გადაწყვეტილებების სანახავად. ჩვეულებრივი აპებისთვის არასოდეს უნდა იყოს საჭირო."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"აპის ფუნქციების ნახვის დაწყება"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"მფლობელს საშუალებას აძლევს, დაიწყოს აპის ფუნქციების ინფორმაციის ნახვა."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"სენსორის მონაცემებზე წვდომა სემპლინგის მაღალი სიხშირით"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 155a77aac3b2..3f75495920b7 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Қолданбаға «Мазаламау» конфигурациясын оқу және жазу мүмкіндігін береді."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"рұқсаттарды пайдалану туралы деректерді көру"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Пайдаланушы қолданбаға берілетін рұқсаттарды басқара алады. Ондай рұқсаттар әдеттегі қолданбаларға керек емес."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Рұқсаттары бар әрекеттерді көру"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Қолданбаға рұқсаттары бар әрекеттерді көруге мүмкіндік береді. Әдеттегі қолданбаларға қажет емес."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"қолданба функцияларын көре бастау"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Қолданбаға функциялар туралы ақпаратты көре бастауды кідіртуге мүмкіндік береді."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"жоғары дискретизация жиілігіндегі датчик деректерін пайдалану"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 8021330769a3..ecae41a98eca 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"អនុញ្ញាតឲ្យកម្មវិធីអាន និងសរសេរការកំណត់រចនាសម្ព័ន្ធមុខងារ កុំរំខាន។"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ចាប់ផ្ដើមមើលការប្រើប្រាស់ការអនុញ្ញាត"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"អនុញ្ញាតឱ្យម្ចាស់ចាប់ផ្ដើមការប្រើប្រាស់ការអនុញ្ញាតសម្រាប់កម្មវិធី។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ចាប់ផ្ដើមមើលការសម្រេចលើការអនុញ្ញាត"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"អនុញ្ញាតឱ្យកម្មវិធីចាប់ផ្ដើមត្រួតពិនិត្យ ដើម្បីពិនិត្យមើលការសម្រេចលើការអនុញ្ញាត។ មិនចាំបាច់ទេសម្រាប់កម្មវិធីធម្មតា។"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ចាប់ផ្ដើមមើលមុខងារកម្មវិធី"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"អនុញ្ញាតឱ្យកម្មវិធីចាប់ផ្ដើមមើលព័ត៌មានមុខងារសម្រាប់កម្មវិធី។"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ចូលប្រើទិន្នន័យឧបករណ៍ចាប់សញ្ញានៅអត្រាសំណាកខ្ពស់"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 4c8871ad25cc..a24166cc0faf 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ಓದಲು ಮತ್ತು ಬರೆಯಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ವೀಕ್ಷಣಾ ಅನುಮತಿಯ ಬಳಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ಆ್ಯಪ್ಗಾಗಿ ಅನುಮತಿ ಬಳಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಲು ಹೊಂದಿರುವವರಿಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಸಾಮಾನ್ಯ ಆ್ಯಪ್ಗಳಿಗೆ ಎಂದಿಗೂ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ಅನುಮತಿಯ ನಿರ್ಧಾರಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಪ್ರಾರಂಭಿಸಿ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ಅನುಮತಿಯ ನಿರ್ಧಾರಗಳನ್ನು ಪರಿಶೀಲಿಸುವುದಕ್ಕಾಗಿ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಬಳಕೆದಾರರನ್ನು ಅನುಮತಿಸುತ್ತದೆ. ಸಾಮಾನ್ಯ ಆ್ಯಪ್ಗಳಿಗೆ ಎಂದಿಗೂ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ಆ್ಯಪ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಪ್ರಾರಂಭಿಸಿ"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ಆ್ಯಪ್ನ ವೈಶಿಷ್ಟ್ಯಗಳ ಮಾಹಿತಿಯನ್ನು ವೀಕ್ಷಿಸಲು ಬಳಕೆದಾರರನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ಹೆಚ್ಚಿನ ನಮೂನೆ ದರದಲ್ಲಿ ಸೆನ್ಸಾರ್ ಡೇಟಾ ಪ್ರವೇಶಿಸಿ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 1f71f0f9f6bc..8920be2481d4 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"앱에서 방해 금지 모드 설정을 읽고 작성하도록 허용합니다."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"권한 사용 보기 시작"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"앱의 권한 사용을 시작하려면 보유자를 허용하세요. 일반 앱에는 필요하지 않습니다."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"권한 결정 보기 시작"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"권한 결정 검토를 위해 기기 보유자가 화면을 시작할 수 있도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"앱 기능 보기"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"권한을 보유한 앱에서 앱의 기능 정보를 보도록 허용합니다."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"더 높은 샘플링 레이트로 센서 데이터 액세스"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index ca703f46c7e5..50c939361ec9 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Колдонмого \"Тынчымды алба\" режиминин конфигурациясын окуу жана жазуу мүмкүнчүлүгүн берет."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"уруксаттын колдонулушун көрүп баштоо"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Колдонмонун пайдаланылышына уруксат берүүгө мүмкүнчүлүк берет. Кадимки колдонмолорго эч качан талап кылынбашы керек."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"уруксаттар боюнча кабыл алынган чечимдерди карап чыгуу"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Ээсине уруксаттар боюнча кабыл алынган чечимдерди карап чыгуу үчүн экранды иштетүү мүмкүнчүлүгүн берет. Кадимки колдонмолорго эч качан талап кылынбашы керек."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"колдонмонун функцияларын көрүп баштоо"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Колдонуучуга функциялары тууралуу маалыматты көрүп баштоо мүмкүнчүлүгүн берет."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"үлгүнү жаздыруу ылдамдыгы жогору болгон сенсор дайындарынын үлгүсүнө мүмкүнчүлүк алуу"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 20df320fc39e..398b6b3b3e4f 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ອະນຸຍາດໃຫ້ແອັບອ່ານ ແລະຂຽນການກນຳດຄ່າ ບໍ່ລົບກວນ."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ເລີ່ມການໃຊ້ສິດອະນຸຍາດການເບິ່ງ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ອະນຸຍາດໃຫ້ຜູ້ຖືເລີ່ມການໃຊ້ສິດອະນຸຍາດສຳລັບແອັບໃດໜຶ່ງໄດ້. ແອັບປົກກະຕິບໍ່ຄວນຕ້ອງໃຊ້."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ເລີ່ມເບິ່ງການຕັດສິນໃຈການອະນຸຍາດ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ອະນຸຍາດໃຫ້ຜູ້ຖືເລີ່ມໜ້າຈໍເພື່ອກວດສອບການຕັດສິນໃຈການອະນຸຍາດ. ແອັບປົກກະຕິບໍ່ຄວນຈຳເປັນຕ້ອງໃຊ້."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ເລີ່ມເບິ່ງຄຸນສົມບັດແອັບ"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ອະນຸຍາດໃຫ້ຜູ້ຖືເລີ່ມການເບິ່ງຂໍ້ມູນຄຸນສົມບັດສຳລັບແອັບໃດໜຶ່ງ."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ເຂົ້າເຖິງຂໍ້ມູນເຊັນເຊີໃນອັດຕາຕົວຢ່າງສູງ"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index f307213a424a..de36cb68442b 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Leidžiama programai skaityti ir rašyti „Do Not Disturb“ konfigūraciją."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"pradėti peržiūrėti leidimo naudojimą"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Leidžia savininkui pradėti naudoti programos leidimą. Įprastoms programoms to neturėtų prireikti."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"pradėti sprendimų dėl leidimų peržiūrą"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Savininkui leidžiama atidaryti ekraną ir peržiūrėti sprendimus dėl leidimų. Neturėtų prireikti naudojant įprastas programas."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pradėti programos funkcijų peržiūrą"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Savininkui leidžiama pradėti programos funkcijų informacijos peržiūrą."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pasiekti jutiklių duomenis dideliu skaitmeninimo dažniu"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index ee3ba2834782..93414e0a49f2 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ļauj lietotnei lasīt un rakstīt režīma “Netraucēt” konfigurāciju."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"Datu skatīšana par izmantojamajām atļaujām"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Ļauj atļaujas īpašniekam sākt lietotnes atļauju izmantošanu. Parastām lietotnēm tas nekad nav nepieciešams."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Skatīt darbības ar atļaujām"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Lietotne ar šo atļauju var pārskatīt darbības ar atļaujām. Parastām lietotnēm šīs atļauja nekad nav nepieciešama."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"Skatīt lietotnes funkcijas"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Lietotne ar šo atļauju var skatīt informāciju par citas lietotnes funkcijām."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"piekļuve sensoru datiem, izmantojot augstu iztveršanas frekvenci"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 2c460ab3eb80..d4f099f20068 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Дозволува апликацијата да чита и пишува конфигурација Не вознемирувај."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"започнете со користење на дозволата за приказ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Дозволува сопственикот да почне со користење на дозволата за апликација. Не треба да се користи за стандардни апликации."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"да го стартува приказот за одлуки за дозволи"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Му дозволува на сопственикот да го стартува екранот за прегледување одлуки за дозволи. Не треба да се користи за стандардни апликации."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"да почне со прегледување на функциите на апликацијата"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"му дозволува на сопственикот да почне со прегледување на податоците за функциите за некоја апликација"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"пристапува до податоците со висока фреквенција на семпл"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index a0698a7a6108..1c98f25cd95a 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"\'ശല്യപ്പെടുത്തരുത്\' കോൺഫിഗറേഷൻ വായിക്കുന്നതിനും എഴുതുന്നതിനും ആപ്പിനെ അനുവദിക്കുന്നു."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"അനുമതി ഉപയോഗം കാണാൻ ആരംഭിക്കുക"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ഒരു ആപ്പിനുള്ള അനുമതി ഉപയോഗം ആരംഭിക്കാൻ ഹോൾഡറിനെ അനുവദിക്കുന്നു. സാധാരണ ആപ്പുകൾക്ക് ഒരിക്കലും ആവശ്യമില്ല."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"അനുമതിയുമായി ബന്ധപ്പെട്ട തീരുമാനങ്ങൾ കാണാൻ ആരംഭിക്കുക"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"അനുമതിയുമായി ബന്ധപ്പെട്ട തീരുമാനങ്ങൾ അവലോകനം ചെയ്യുന്നതിന് സ്ക്രീൻ ആരംഭിക്കാൻ ഹോൾഡറിനെ അനുവദിക്കുന്നു. സാധാരണ ആപ്പുകൾക്ക് ഒരിക്കലും ആവശ്യമില്ല."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ആപ്പ് ഫീച്ചറുകൾ കാണാൻ ആരംഭിക്കുക"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ആപ്പിനുള്ള ഫീച്ചറുകളുടെ വിവരങ്ങൾ കാണാൻ ആരംഭിക്കാൻ ഹോൾഡറിനെ അനുവദിക്കുന്നു."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ഉയർന്ന സാംപ്ലിംഗ് റേറ്റിൽ സെൻസർ ഡാറ്റ ആക്സസ് ചെയ്യുക"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index f86c8751bc12..db67e904d29d 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Апп-д Бүү саад бол тохируулгыг уншиж, бичихийг зөвшөөрөх"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"зөвшөөрлийн ашиглалтыг харж эхлэх"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Эзэмшигчид аппын зөвшөөрлөө ашиглаж эхлэхийг зөвшөөрдөг. Энгийн аппуудад шаардлагагүй."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"зөвшөөрлийн шийдвэрийг хянах дэлгэцийг эхлүүлэх"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Эзэмшигчид зөвшөөрлийн шийдвэрийг хянах дэлгэцийг эхлүүлэх боломжийг олгоно. Энгийн аппуудад хэзээ ч шаардагдахгүй."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"аппын онцлогуудыг үзэж эхлэх"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Аппын онцлогуудын мэдээллийг үзэж эхлэхийг эзэмшигчид зөвшөөрдөг."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"түүврийн өндөр хувиар мэдрэгчийн өгөгдөлд хандах"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index a244a2f6c33b..1385dcd51878 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"व्यत्यय आणू नका कॉंफिगरेशन वाचण्यासाठी आणि लिहिण्यासाठी ॲपला अनुमती देते."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"व्ह्यू परवानगी वापर सुरू करा"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"धारकास अॅपसाठी परवानगी वापरणे सुरू करण्याची अनुमती देते. सामान्य अॅप्ससाठी कधीही आवश्यकता नसते."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"परवानगीशी संबंधित निर्णय पाहणे सुरू करा"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"होल्डरला परवानगीशी संबंधित निर्णयांचे पुनरावलोकन करण्यासाठी स्क्रीन सुरू करण्याची अनुमती देते. सामान्य ॲप्ससाठी कधीही आवश्यक नसते."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ॲप वैशिष्ट्ये पाहणे सुरू करा"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"होल्डरला ॲपसाठी वैशिष्ट्यांची माहिती पाहण्यास सुरू करण्याची अनुमती देते."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"उच्च नमुना दराने सेन्सर डेटा अॅक्सेस करते"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 12cd501431b1..43847feefea9 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Membenarkan apl membaca dan menulis konfigurasi Jangan Ganggu."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"mulakan lihat penggunaan kebenaran"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Membenarkan pemegang memulakan penggunaan kebenaran untuk apl. Tidak sekali-kali diperlukan untuk apl biasa."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"mula melihat keputusan kebenaran"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Membenarkan pemegang memulakan skrin untuk menyemak keputusan kebenaran. Tidak diperlukan untuk apl biasa."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"mula melihat ciri apl"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Membenarkan pemegang mula melihat maklumat ciri untuk apl."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"akses data penderia pada data pensampelan yang tinggi"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 59505a5ca435..f509906480a2 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"မနှောင့်ယှက်ရန် ချိန်ညှိမှုကို အပ်ဖ်များ ဖတ်ခြင်း ပြင်ခြင်းပြုလုပ်နိုင်ရန် ခွင့်ပြုမည်။"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"အစမြင်ကွင်း ခွင့်ပြုချက် အသုံးပြုမှု"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"အက်ပ်တစ်ခုအတွက် ခွင့်ပြုချက်စတင်အသုံးပြုမှုကို ကိုင်ဆောင်သူအား ခွင့်ပြုသည်။ ပုံမှန်အက်ပ်များအတွက် ဘယ်သောအခါမျှ မလိုအပ်ပါ။"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ခွင့်ပြုသည့် ဆုံးဖြတ်ချက်များကို စတင်ကြည့်ခြင်း"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ခွင့်ပြုထားသည့်အက်ပ်အား ခွင့်ပြုသည့်ဆုံးဖြတ်ချက်များကို ကြည့်နိုင်ရန်အတွက် စခရင်စတင်ရန် ခွင့်ပြုနိုင်သည်။ သာမန်အက်ပ်များအတွက် မည်သည့်အခါမျှ မလိုအပ်နိုင်ပါ။"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"အက်ပ်ဝန်ဆောင်မှုများကို စတင်ကြည့်ခြင်း"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ဝန်ဆောင်မှုအချက်အလက်ကိုများကို ခွင့်ပြုချက်ရထားသည့် အက်ပ်အား စတင်ကြည့်နိုင်ရန် ခွင့်ပြုသည်။"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"နမူနာနှုန်းမြင့်သော အာရုံခံစနစ်ဒေတာကို သုံးပါ"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index f12287f50392..2ddd45da07be 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Lar appen lese og skrive konfigurasjon av Ikke forstyrr."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"start visning av bruk av tillatelser"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Lar innehaveren starte bruk av tillatelser for en app. Dette skal aldri være nødvendig for vanlige apper."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"starte visning av avgjørelser om tillatelser"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Lar innehaveren starte skjermen for å gjennomgå avgjørelser om tillatelser. Dette skal aldri være nødvendig for vanlige apper."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"starte visning av appfunksjoner"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Lar innehaveren se informasjon om funksjonene for en app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"tilgang til sensordata ved høy samplingfrekvens"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index bf8499e70f76..d4828b6ce942 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"बाधा नपुर्याउँनुहोस् कन्फिगरेसन पढ्न र लेख्नको लागि एपलाई अनुमति दिनुहोस्।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"हेर्ने अनुमतिको प्रयोग सुरु गर्नुहोस्"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"वाहकलाई कुनै एपसम्बन्धी अनुमतिको प्रयोग सुरु गर्न दिन्छ। साधारण एपहरूलाई कहिल्यै आवश्यक नपर्नु पर्ने हो।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"अनुमतिसम्बन्धी निर्णयहरू हेर्न सुरु गर्नुहोस्"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"होल्डरलाई अनुमतिसम्बन्धी निर्णयहरू समीक्षा गर्ने स्क्रिन सुरु गर्न दिन्छ। सामान्य एपहरूलाई कहिल्यै पनि नचाहिनु पर्ने हो।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"एपका सुविधासम्बन्धी जानकारी हेर्न थाल्नुहोस्"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"होल्डरलाई एपका सुविधासम्बन्धी जानकारी हेर्न दिन्छ।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"नमुना लिने उच्च दरमा सेन्सरसम्बन्धी डेटा प्रयोग गर्ने"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 1dac9827df2c..3b6db066eb8f 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Hiermee kan de app configuratie voor Niet storen lezen en schrijven."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"rechtengebruik starten"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Hiermee kan de houder het rechtengebruik voor een app starten. Nooit vereist voor normale apps."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"bekijken van rechtenbeslissingen starten"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Hiermee kan de houder het scherm starten om rechtenbeslissingen te bekijken. Nooit vereist voor normale apps."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"app-functies bekijken"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Hiermee kan de houder informatie over functies bekijken voor een app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"toegang krijgen tot sensorgegevens met een hoge samplingsnelheid"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index e825c2c7d13d..0220ce3c3540 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"\"ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\" କନଫିଗରେଶନ୍ ପଢ଼ିବା ତଥା ଲେଖିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦେଇଥାଏ।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ଅନୁମତି ବ୍ୟବହାର ଦେଖିବା ଆରମ୍ଭ କରନ୍ତୁ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ଏକ ଆପ୍ ପାଇଁ ଅନୁମତିର ବ୍ୟବହାର ଆରମ୍ଭ କରିବାକୁ ଧାରକକୁ ଅନୁମତି ଦେଇଥାଏ। ସାଧାରଣ ଆପ୍ଗୁଡ଼ିକ ପାଇଁ ଏହା ଆବଶ୍ୟକ ନୁହେଁ।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ଅନୁମତି ନିଷ୍ପତ୍ତିଗୁଡ଼ିକ ଦେଖିବା ଆରମ୍ଭ କରନ୍ତୁ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ଅନୁମତି ନିଷ୍ପତ୍ତିଗୁଡ଼ିକର ସମୀକ୍ଷା କରିବାକୁ ସ୍କ୍ରିନ ଚାଲୁ କରିବା ପାଇଁ ହୋଲଡରଙ୍କୁ ଅନୁମତି ଦିଏ। ସାଧାରଣ ଆପଗୁଡ଼ିକ ପାଇଁ ଏହା କେବେ ବି ଆବଶ୍ୟକ ହେବ ନାହିଁ।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ଆପର ଫିଚରଗୁଡ଼ିକୁ ଦେଖିବା ଆରମ୍ଭ କରନ୍ତୁ"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"କୌଣସି ଆପ ପାଇଁ ଫିଚରଗୁଡ଼ିକ ବିଷୟରେ ସୂଚନା ଦେଖିବା ଆରମ୍ଭ କରିବାକୁ ହୋଲଡରଙ୍କୁ ଅନୁମତି ଦିଏ।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ଏକ ଉଚ୍ଚ ନମୁନାକରଣ ରେଟରେ ସେନ୍ସର୍ ଡାଟାକୁ ଆକ୍ସେସ୍ କରନ୍ତୁ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index fd822a3d2df2..7a3bc337bb82 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -174,7 +174,7 @@ <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"ਬਹੁਤ ਜ਼ਿਆਦਾ ਬੇਨਤੀਆਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string> <string name="notification_title" msgid="5783748077084481121">"<xliff:g id="ACCOUNT">%1$s</xliff:g> ਲਈ ਸਾਈਨਇਨ ਅਸ਼ੁੱਧੀ"</string> <string name="contentServiceSync" msgid="2341041749565687871">"ਸਿੰਕ ਕਰੋ"</string> - <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ਸਮਕਾਲੀਕਿਰਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string> + <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ਸਿੰਕ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"ਬਹੁਤ ਸਾਰੀਆਂ <xliff:g id="CONTENT_TYPE">%s</xliff:g> ਨੂੰ ਮਿਟਾਉਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਗਈ।"</string> <string name="low_memory" product="tablet" msgid="5557552311566179924">"ਟੈਬਲੈੱਟ ਸਟੋਰੇਜ ਪੂਰੀ ਭਰੀ ਹੈ। ਜਗ੍ਹਾ ਖਾਲੀ ਕਰਨ ਲਈ ਕੁਝ ਫ਼ਾਈਲਾਂ ਮਿਟਾਓ।"</string> <string name="low_memory" product="watch" msgid="3479447988234030194">"ਘੜੀ ਸਟੋਰੇਜ ਪੂਰੀ ਭਰੀ ਹੈ। ਜਗ੍ਹਾ ਖਾਲੀ ਕਰਨ ਲਈ ਕੁਝ ਫ਼ਾਈਲਾਂ ਮਿਟਾਓ।"</string> @@ -676,11 +676,11 @@ <string name="face_error_vendor_unknown" msgid="7387005932083302070">"ਕੋਈ ਗੜਬੜ ਹੋ ਗਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string> <string name="face_icon_content_description" msgid="465030547475916280">"ਚਿਹਰਾ ਪ੍ਰਤੀਕ"</string> <string name="permlab_readSyncSettings" msgid="6250532864893156277">"ਸਿੰਕ ਸੈਟਿੰਗਾਂ ਪੜ੍ਹੋ"</string> - <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"ਐਪ ਨੂੰ ਕਿਸੇ ਖਾਤੇ ਲਈ ਸਮਕਾਲੀਕਰਨ ਸੈਟਿੰਗਾਂ ਪੜ੍ਹਨ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਇਹ ਪਤਾ ਕਰ ਸਕਦਾ ਹੈ ਕਿ People ਐਪ ਦਾ ਕਿਸੇ ਖਾਤੇ ਨਾਲ ਸਮਕਾਲੀਕਿਰਤ ਕੀਤਾ ਗਿਆ ਹੈ ਜਾਂ ਨਹੀਂ।"</string> - <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"ਸਮਕਾਲੀਕਰਨ ਚਾਲੂ ਅਤੇ ਬੰਦ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string> - <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"ਐਪ ਨੂੰ ਇੱਕ ਖਾਤੇ ਲਈ ਸਮਕਾਲੀਕਰਨ ਸੈਟਿੰਗਾਂ ਸੋਧਣ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਇਸਦੀ ਵਰਤੋਂ ਕਿਸੇ ਖਾਤੇ ਨਾਲ People ਐਪ ਦਾ ਸਮਕਾਲੀਕਰਨ ਚਾਲੂ ਕਰਨ ਲਈ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ।"</string> + <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"ਐਪ ਨੂੰ ਕਿਸੇ ਖਾਤੇ ਲਈ ਸਿੰਕ ਸੈਟਿੰਗਾਂ ਪੜ੍ਹਨ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਇਹ ਪਤਾ ਕਰ ਸਕਦਾ ਹੈ ਕਿ People ਐਪ ਨੂੰ ਕਿਸੇ ਖਾਤੇ ਨਾਲ ਸਿੰਕ ਕੀਤਾ ਗਿਆ ਹੈ ਜਾਂ ਨਹੀਂ।"</string> + <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"ਸਿੰਕ ਚਾਲੂ ਅਤੇ ਬੰਦ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string> + <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"ਐਪ ਨੂੰ ਇੱਕ ਖਾਤੇ ਲਈ ਸਿੰਕ ਸੈਟਿੰਗਾਂ ਸੋਧਣ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਇਸਦੀ ਵਰਤੋਂ ਕਿਸੇ ਖਾਤੇ ਨਾਲ People ਐਪ ਦਾ ਸਿੰਕ ਚਾਲੂ ਕਰਨ ਲਈ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ।"</string> <string name="permlab_readSyncStats" msgid="3747407238320105332">"ਸਿੰਕ ਅੰਕੜੇ ਪੜ੍ਹੋ"</string> - <string name="permdesc_readSyncStats" msgid="3867809926567379434">"ਐਪ ਨੂੰ ਇੱਕ ਖਾਤੇ ਲਈ ਸਮਕਾਲੀਕਰਨ ਸਥਿਤੀ ਪੜ੍ਹਨ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ, ਇਸ ਵਿੱਚ ਸਮਕਾਲੀਕਰਨ ਵਰਤਾਰਿਆਂ ਦਾ ਇਤਿਹਾਸ ਅਤੇ ਕਿੰਨੇ ਡਾਟਾ ਦਾ ਸਮਕਾਲੀਕਿਰਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਵੀ ਸ਼ਾਮਲ ਹੈ।"</string> + <string name="permdesc_readSyncStats" msgid="3867809926567379434">"ਐਪ ਨੂੰ ਇੱਕ ਖਾਤੇ ਲਈ ਸਿੰਕ ਸਥਿਤੀ ਪੜ੍ਹਨ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ, ਇਸ ਵਿੱਚ ਸਿੰਕ ਇਵੈਂਟਾਂ ਦਾ ਇਤਿਹਾਸ ਅਤੇ ਕਿੰਨਾ ਡਾਟਾ ਸਿੰਕ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਇਹ ਵੀ ਸ਼ਾਮਲ ਹੈ।"</string> <string name="permlab_sdcardRead" msgid="5791467020950064920">"ਸਮੱਗਰੀਆਂ ਪੜ੍ਹੋ"</string> <string name="permdesc_sdcardRead" msgid="6872973242228240382">"ਐਪ ਨੂੰ ਸਮੱਗਰੀਆਂ ਪੜ੍ਹਨ ਦਿੰਦੀ ਹੈ।"</string> <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ਸਮੱਗਰੀਆਂ ਦਾ ਸੰਸ਼ੋਧਨ ਕਰੋ ਜਾਂ ਮਿਟਾਓ"</string> @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ਐਪ ਨੂੰ ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ ਕੌਂਫਿਗਰੇਸ਼ਨ ਨੂੰ ਪੜ੍ਹਨ ਅਤੇ ਲਿਖਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ਇਜਾਜ਼ਤ ਵਰਤੋਂ ਦੇਖਣਾ ਸ਼ੁਰੂ ਕਰੋ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ਧਾਰਕ ਨੂੰ ਕਿਸੇ ਹੋਰ ਐਪ ਲਈ ਇਜਾਜ਼ਤ ਵਰਤੋਂ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਦਿੰਦਾ ਹੈ। ਸਧਾਰਨ ਐਪਾਂ ਲਈ ਕਦੇ ਵੀ ਲੋੜੀਂਦਾ ਨਹੀਂ ਹੋਵੇਗਾ।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਫ਼ੈਸਲਿਆਂ ਨੂੰ ਦੇਖਣਾ ਸ਼ੁਰੂ ਕਰੋ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਫ਼ੈਸਲਿਆਂ ਦੀ ਸਮੀਖਿਆ ਕਰਨ ਲਈ ਹੋਲਡਰ ਨੂੰ ਸਕ੍ਰੀਨ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ। ਸਧਾਰਨ ਐਪਾਂ ਲਈ ਕਦੇ ਵੀ ਲੋੜੀਂਦੀ ਨਹੀਂ ਹੋਵੇਗੀ।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ਐਪ ਦੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਦੇਖਣਾ ਸ਼ੁਰੂ ਕਰੋ"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ਇਸ ਨਾਲ ਹੋਲਡਰ ਨੂੰ ਕਿਸੇ ਐਪ ਦੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਬਾਰੇ ਜਾਣਕਾਰੀ ਦੇਖਣ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ਉੱਚ ਸੈਂਪਲਿੰਗ ਰੇਟ \'ਤੇ ਸੈਂਸਰ ਡਾਟਾ ਤੱਕ ਪਹੁੰਚ ਕਰੋ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index c2e8663fdf1e..bbc18679f089 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Pozwala aplikacji na odczyt i zmianę konfiguracji trybu Nie przeszkadzać."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"rozpocząć wyświetlanie użycia uprawnień"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Umożliwia rozpoczęcie korzystania z uprawnienia dotyczącego danej aplikacji jego posiadaczowi. Zwykłe aplikacje nie powinny potrzebować tego uprawnienia."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"rozpoczęcie wyświetlania decyzji dotyczących uprawnień"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Umożliwia posiadaczowi uruchomienie ekranu w celu przeglądania decyzji dotyczących uprawnień. Zwykłe aplikacje nie powinny tego potrzebować."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"rozpoczęcie wyświetlania funkcji aplikacji"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Umożliwia posiadaczowi rozpoczęcie przeglądania informacji o funkcjach aplikacji."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"dostęp do danych czujnika z wysoką częstotliwością"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 2f894839a8ea..4469a67d3caa 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite aplicației să citească și să scrie configurația Nu deranja."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"porniți folosirea permisiunii de vizualizare"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite proprietarului să pornească folosirea permisiunii pentru o aplicație. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"să înceapă să examineze deciziile privind permisiunile"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite proprietarului să deschidă ecranul pentru a examina deciziile privind permisiunile. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"începeți să vedeți funcțiile aplicației"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite proprietarului să înceapă să vadă informațiile despre funcții pentru o aplicație."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"să acceseze date de la senzori la o rată de eșantionare mare"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index e4d6527d0e7b..6b874b9e4e40 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Открывает приложению доступ к настройкам режима \"Не беспокоить\" и позволяет изменять их."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"Просмотр данных об используемых разрешениях"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Приложение получит доступ к данным об используемых разрешениях. Это разрешение не требуется обычным приложениям."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Просмотр действий с разрешениями"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Позволяет просматривать действия с разрешениями. Не используется обычными приложениями."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"Просмотр функций приложения"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Позволяет просматривать информацию о функциях приложения."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"Доступ к данным датчиков при высокой частоте дискретизации"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 027fcd2f8983..34436810b101 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"බාධා නොකරන්න වින්යාස කිරීම කියවීමට සහ ලිවීමට යෙදුමට ඉඩ දෙයි."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"අවසර භාවිතය බැලීමට ආරම්භ කරන්න"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"තබා සිටින්නාට යෙදුමක් සඳහා අවසර භාවිතය ආරම්භ කිරීමට ඉඩ දෙයි. සාමාන්ය යෙදුම් සඳහා කිසි විටෙක අවශ්ය නොවිය යුතු ය."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"නව අවසර තීරණ ආරම්භ කරන්න"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"අවසර තීරණ සමාලෝචනය කිරීමට තිරය ආරම්භ කිරීමට දරන්නාට ඉඩ දෙයි. සාමාන්ය යෙදුම් සඳහා කිසිදා අවශ්ය නොවිය යුතුය."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"යෙදුම බලන්න විශේෂාංග ආරම්භ කරන්න"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"යෙදුමක් සඳහා විශේෂාංග තොරතුරු බැලීම ආරම්භ කිරීමට දරන්නාට ඉඩ දෙන්න."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ඉහළ නියැදි කිරීමේ වේගයකින් සංවේදක දත්ත වෙත පිවිසෙන්න"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index eab7f733db65..07371009414e 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Lejon aplikacionin të lexojë dhe shkruajë konfigurimin e \"Mos shqetëso\"."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"nis përdorimin e lejes për shikimin"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Lejon që mbajtësi të nisë përdorimin e lejeve për një aplikacion. Nuk duhet të nevojitet asnjëherë për aplikacionet normale."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"nisë shikimin e vendimeve për lejet"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Lejon që zotëruesi të nisë ekranin për shqyrtimin e vendimeve për lejet. Nuk duhet të nevojitet asnjëherë për aplikacionet normale."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"fillojë shikimin e veçorive të aplikacionit"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Lejon që zotëruesi të fillojë të shikojë informacionin e veçorive për një aplikacion."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"qasu te të dhënat e sensorit me një shpejtësi kampionimi më të lartë"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 8d49f2af4c10..e117bdaaefa9 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Дозвољава апликацији да чита и уписује конфигурацију подешавања Не узнемиравај."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"почетак коришћења дозволе за преглед"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Дозвољава власнику да започне коришћење дозволе за апликацију. Никада не би требало да буде потребна за уобичајене апликације."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"покретање прегледа одлука о дозволама"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Дозвољава власнику да покрене екран за проверу одлука о дозволама. Никада не би требало да буде потребно за обичне апликације."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"покретање приказа функција апликације"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Дозвољава носиоцу дозволе да започне прегледање информација о функцијама апликације."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"приступ подацима сензора при великој брзини узорковања"</string> @@ -1552,7 +1550,7 @@ <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Веза са увек укљученим VPN-ом је прекинута"</string> <string name="vpn_lockdown_error" msgid="4453048646854247947">"Повезивање на стално укључени VPN није успело"</string> <string name="vpn_lockdown_config" msgid="8331697329868252169">"Промените подешавања VPN-а"</string> - <string name="upload_file" msgid="8651942222301634271">"Одабери датотеку"</string> + <string name="upload_file" msgid="8651942222301634271">"Одабери фајл"</string> <string name="no_file_chosen" msgid="4146295695162318057">"Није изабрана ниједна датотека"</string> <string name="reset" msgid="3865826612628171429">"Ресетуј"</string> <string name="submit" msgid="862795280643405865">"Пошаљи"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index af3586b387f7..90de24523802 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ger appen läs- och skrivbehörighet till konfigurationen för Stör ej."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"börja visa behörighetsanvändningen"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Gör att innehavaren kan öppna behörighetsanvändning för en app. Ska inte behövas för vanliga appar."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"börja visa behörighetsbeslut"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Tillåter att innehavaren öppnar skärmen för att granska behörighetsbeslut. Detta ska inte behövas för vanliga appar."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"börja visa appfunktioner"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Tillåter att innehavaren börjar visa information om funktioner för en app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"åtkomst till sensordata med en hög samplingsfrekvens"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index f70bfcd95ca7..ec241023ee35 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Inaruhusu programu kusoma na kuandika usanidi wa kipengee cha Usinisumbue."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"anzisha kipengele cha kuona matumizi ya ruhusa"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Huruhusu kishikiliaji kuanzisha matumizi ya ruhusa ya programu. Haipaswi kuhitajika kwa ajili ya programu za kawaida."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"kuanzisha uamuzi wa ruhusa za kuangalia"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Humruhusu mmiliki kuanzisha skrini ili kukagua uamuzi wa ruhusa. Haipaswi kuhitajika kwenye programu za kawaida."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"anzisha kipengele cha kuangalia vipengele vya programu"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Huruhusu mmiliki kuanza kuangalia maelezo ya vipengele vya programu."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"fikia data ya vitambuzi kwa kasi ya juu ya sampuli"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 1114bf1e0c74..e7ea59d36387 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"தொந்தரவு செய்ய வேண்டாம் உள்ளமைவைப் படிக்கவும் எழுதவும், ஆப்ஸை அனுமதிக்கிறது."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"அனுமதி உபயோகத்தை அணுகுதல்"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ஆப்ஸிற்கான அனுமதி உபயோகத்தை ஹோல்டருக்கு வழங்கும். இயல்பான ஆப்ஸிற்கு இது எப்போதுமே தேவைப்படாது."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"அனுமதி முடிவுகளைப் பார்க்கத் தொடங்குதல்"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"அனுமதி தொடர்பான முடிவுகளை மதிப்பாய்வு செய்ய, திரையைத் தொடங்குவதற்கான அனுமதியை ஹோல்டருக்கு வழங்கும். இயல்பான ஆப்ஸிற்கு இது எப்போதுமே தேவைப்படாது."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ஆப்ஸின் அம்சங்களைப் பார்க்கத் தொடங்குதல்"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ஆப்ஸின் அம்சங்கள் குறித்த தகவல்களைப் பார்ப்பதற்கான அனுமதியை ஹோல்டருக்கு வழங்கும்."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"அதிகளவிலான சாம்பிளிங் ரேட்டில் சென்சார் தரவை அணுகுதல்"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 0d9cb8b86ac5..176d845f21dd 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"అంతరాయం కలిగించవద్దు ఎంపిక కాన్ఫిగరేషన్ చదవడానికి మరియు రాయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"వీక్షణ అనుమతి వినియోగాన్ని ప్రారంభించండి"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"యాప్నకు అనుమతి వినియోగాన్ని ప్రారంభించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ ఇటువంటి అనుమతి అవసరం ఉండదు."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"వీక్షణ అనుమతి నిర్ణయాలను ప్రారంభించండి"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"అనుమతి నిర్ణయాలను రివ్యూ చేయడానికి స్క్రీన్ను ప్రారంభించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ అవసరం ఉండదు."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"యాప్ ఫీచర్లను చూడటాన్ని ప్రారంభించండి"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"యాప్ ఫీచర్ల సమాచారాన్ని చూడటాన్ని ప్రారంభించడానికి హోల్డర్ను అనుమతిస్తుంది."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"అధిక శాంపిల్ రేటు వద్ద సెన్సార్ డేటాను యాక్సెస్ చేయండి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index b6b0345acc5d..30b3d0b3c127 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"อนุญาตให้แอปอ่านและเขียนการกำหนดค่าโหมดห้ามรบกวน"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"เริ่มการใช้สิทธิ์การดู"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"อนุญาตให้เจ้าของเริ่มการใช้สิทธิ์ของแอป ไม่จำเป็นสำหรับแอปทั่วไป"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"เริ่มดูสิทธิ์ที่เลือกไว้"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"อนุญาตให้แอปเริ่มหน้าจอเพื่อดูสิทธิ์ที่เลือกไว้ แอปทั่วไปไม่จำเป็นต้องใช้"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"เริ่มดูฟีเจอร์ของแอป"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"อนุญาตให้เจ้าของเริ่มดูข้อมูลฟีเจอร์สำหรับแอป"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"เข้าถึงข้อมูลเซ็นเซอร์ที่อัตราการสุ่มตัวอย่างสูง"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index fa73788174f7..c0bbc6958482 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Nagbibigay-daan sa app na basahin at isulat ang configuration ng Huwag Istorbohin."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"simulan ang paggamit sa pahintulot sa pagtingin"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Binibigyang-daan ang may hawak na simulan ang paggamit ng pahintulot para sa isang app. Hindi dapat kailanganin kailanman para sa mga normal na app."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"simulan ang mga desisyon sa pahintulot na tumingin"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Nagbibigay-daan sa may hawak na simulan ang screen para masuri ang mga desisyon sa pahintulot. Hindi dapat kailanman kailanganin para sa mga karaniwang app."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"simulang tingnan ang mga feature ng app"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Nagbibigay-daan sa may hawak na simulang tingnan ang impormasyon ng mga feature para sa isang app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"mag-access ng data ng sensor sa mataas na rate ng pag-sample"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index adf96e077d58..8a8622715b2e 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Uygulamaya, Rahatsız Etmeyin yapılandırmasını okuma ve yazma izni verir."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"izin kullanımı görüntülemeye başlama"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"İzin sahibinin bir uygulama için izin kullanımı başlatmasına olanak tanır. Normal uygulamalar için hiçbir zaman kullanılmamalıdır."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"izin kararlarını görüntülemeye başlama"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"İzin sahibinin, izin kararlarını incelemek için ekranı başlatmasına izin verir. Normal uygulamalarda hiçbir zaman gerek duyulmaz."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"uygulama özelliklerini görüntülemeye başlama"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"İzin sahibinin, bir uygulamanın özellik bilgilerini görüntülemeye başlamasına izin verir."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"sensör verilerine daha yüksek örnekleme hızında eriş"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index b86402a462ff..f4fb575f836e 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Додаток зможе переглядати та змінювати конфігурацію режиму \"Не турбувати\"."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"перегляньте дані про використання дозволів"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Власник зможе використовувати дозволи для цього додатка. Цей дозвіл не потрібен для звичайних додатків."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"почати перегляд рішень щодо дозволів"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"У додатку може відкриватися екран для перегляду рішень щодо дозволів. Звичайні додатки ніколи не використовують цей дозвіл."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"почати перегляд функцій додатка"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Дозволяє додатку почати перегляд інформації про функції іншого додатка."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"доступ до даних датчиків із високою частотою дикретизації"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 65fe847396bd..2c712f4018b2 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ایپ کو ڈسٹرب نہ کریں کنفیگریشن لکھنے اور پڑھنے کے قابل کرتا ہے۔"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"اجازت کی استعمال کا ملاحظہ شروع کریں"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"حامل کو ایپ کی اجازت کے استعمال کو شروع کرنے کی اجازت دیتا ہے۔ عام ایپس کے لیے کبھی بھی درکار نہیں ہونا چاہیے۔"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"اجازت کے فیصلوں کو دیکھنا شروع کریں"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ہولڈر کو اجازت کے فیصلوں کے جائزے کے لیے اسکرین شروع کرنے کی اجازت دیتا ہے۔ عام ایپس کے لیے کبھی بھی اس کی ضرورت نہيں ہونی چاہیے۔"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ایپ کی خصوصیات کا ملاحظہ شروع کریں"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ہولڈر کو ایپ کے لیے خصوصیات کی معلومات دیکھنے کی اجازت دیتا ہے۔"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"نمونہ کاری کی اعلی شرح پر سینسر کے ڈیٹا تک رسائی حاصل کریں"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index bbfadf6bf805..05899edf1e8c 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Cho phép ứng dụng đọc và ghi cấu hình Không làm phiền."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"cấp quyền xem"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Cho phép chủ sở hữu cấp quyền cho một ứng dụng. Các ứng dụng thông thường sẽ không bao giờ cần quyền này."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"bắt đầu xem các quyết định cấp quyền"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Cho phép chủ sở hữu bắt đầu kiểm tra để xem xét các quyết định cấp quyền. Việc này hoàn toàn không cần thiết đối với các ứng dụng thông thường."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"bắt đầu xem các tính năng của ứng dụng"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Cho phép chủ sở hữu bắt đầu xem thông tin về tính năng của một ứng dụng."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"truy cập vào dữ liệu cảm biến ở tốc độ lấy mẫu cao"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index b7e19725716f..6acd3e01d20f 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"允许此应用读取和写入“勿扰”模式配置。"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"授权使用“查看权限”"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"允许该应用开始查看应用的权限使用情况(普通应用绝不需要此权限)。"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"开始查看权限决策"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"允许具有该权限的应用启动屏幕以查看权限决策。普通应用绝不需要此权限。"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"开始查看应用功能"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"允许具有该权限的应用开始查看某个应用的功能信息。"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"以高采样率访问传感器数据"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 86de22f3e3ff..bf51c1aae753 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"允許應用程式讀取和寫入「請勿騷擾」設定。"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"開始查看權限使用情況"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"允許應用程式開始查看應用程式的權限使用情況 (一般應用程式並不需要)。"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"開始檢視權限決定"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"允許擁有者啟用螢幕以查看權限決定。不建議一般應用程式使用。"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"開始查看應用程式功能"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"允許擁有者開始查看應用程式的功能資料。"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"以高取樣率存取感應器資料"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index a4e6fbba76d4..bd0b9a242d1a 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"允許應用程式讀取及寫入「零打擾」設定。"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"啟動檢視權限用途"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"允許應用程式開始使用其他應用程式 (一般應用程式並不需要)。"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"開始檢視權限決定"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"允許應用程式啟動檢視權限決定的畫面 (一般應用程式並不需要)。"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"開始查看應用程式功能"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"允許具有這項權限的應用程式開始查看其他應用程式的功能資訊。"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"以高取樣率存取感應器資料"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 4e8cfd42aecd..bf024528f3e3 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ivumela izinhlelo zokusebenza ukufunda nokubhala ukulungiswa kokuthi Ungaphazamisi."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"qala ukusetshenziswa kokubuka imvume"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Ivumela umphathi ukuthi aqale ukusetshenziswa kwemvume kohlelo lokusebenza. Akumele idingelwe izinhlelo zokusebenza ezijwayelekile."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"qala ukubuka izinqumo zemvume"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Ivumela obambile ukuthi aqale isikrini ukuze abuyekeze izinqumo zemvume. Akufanele idingeke ngama-app avamile."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"qala ukubuka izakhi ze-app"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Vumela isibambi ukuthi siqale ukubuka ulwazi lwezakhi lwe-app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"finyelela idatha yenzwa ngenani eliphezulu lokwenza isampuli"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 86c83c590b77..e232d858bb43 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3638,7 +3638,22 @@ to re-retrieve all resources (including view layouts, drawables, etc) to correctly handle any configuration change.--> <attr name="configChanges" /> - <!-- Specifies whether the IME supports Handwriting using stylus. Defaults to false. --> + <!-- Specifies whether the IME supports Handwriting using stylus. Defaults to false. + When IME implements support for stylus handwriting, on every ACTION_DOWN with stylus + on an editor, + {@link android.inputmethodservice.InputMethodService#onStartStylusHandwriting()} + is called. + If IME is ready for stylus input, it must return {@code true} for Handwriting sessions + to start. IME should attach it's View that renders Ink on screen to stylus handwriting + inking window + {@link android.inputmethodservice.InputMethodService#getStylusHandwritingWindow()}. + IME will then receive Stylus MotionEvent(s) on DecorView i.e. the Inking view + {@link android.view.View#onTouchEvent(MotionEvent)} attached by IME to Ink window. + Handwriting mode can be finished by calling + {@link android.inputmethodservice.InputMethodService#finishStylusHandwriting()} or will + be finished by framework on next + {@link android.inputmethodservice.InputMethodService#onFinishInput()}. + --> <attr name="supportsStylusHandwriting" format="boolean" /> </declare-styleable> @@ -5402,6 +5417,17 @@ ignores some hyphen character related typographic features, e.g. kerning. --> <enum name="fullFast" value="4" /> </attr> + <!-- Indicates the line break strategies can be used when calculating the text wrapping. --> + <attr name="lineBreakStyle"> + <!-- No line break style specific. --> + <enum name="none" value="0" /> + <!-- Use the least restrictive rule for line-breaking. --> + <enum name="loose" value="1" /> + <!-- Indicate breaking text with the most comment set of line-breaking rules. --> + <enum name="normal" value="2" /> + <!-- ndicates breaking text with the most strictest line-breaking rules. --> + <enum name="strict" value="3" /> + </attr> <!-- Specify the type of auto-size. Note that this feature is not supported by EditText, works only for TextView. --> <attr name="autoSizeTextType" format="enum"> @@ -6869,6 +6895,9 @@ <!-- Special option for window animations: whether window should have rounded corners. @see ScreenDecorationsUtils#getWindowCornerRadius(Resources) --> <attr name="hasRoundedCorners" format="boolean" /> + <!-- Special option for window animations: whether the window's background should be used as + a background to the animation. --> + <attr name="showBackground" format="boolean" /> </declare-styleable> <declare-styleable name="AnimationSet"> @@ -9650,4 +9679,11 @@ <attr name="iconfactoryBadgeSize" format="dimension"/> <!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. --> <attr name="lStar" format="float"/> -</resources> + + <!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. --> + <declare-styleable name="LocaleConfig_Locale"> + <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a> + of the supported locale. {@link android.app.LocaleConfig} --> + <attr name="name" /> + </declare-styleable> + </resources> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index db24475cf780..a5954338910d 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1519,6 +1519,9 @@ <!-- An XML resource with the application's Network Security Config. --> <attr name="networkSecurityConfig" format="reference" /> + <!-- An XML resource with the application's {@link android.app.LocaleConfig} --> + <attr name="localeConfig" format="reference" /> + <!-- When an application is partitioned into splits, this is the name of the split that contains the defined component. --> <attr name="splitName" format="string" /> @@ -1801,6 +1804,7 @@ <attr name="maxAspectRatio" /> <attr name="minAspectRatio" /> <attr name="networkSecurityConfig" /> + <attr name="localeConfig" /> <!-- Declare the category of this app. Categories are used to cluster multiple apps together into meaningful groups, such as when summarizing battery, network, or disk usage. Apps should only define this value when they fit well into one of diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2836c9816886..e9ba92fe9589 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2135,6 +2135,11 @@ <string name="config_systemAppProtectionService" translatable="false"></string> <!-- The name of the package that will hold the system calendar sync manager role. --> <string name="config_systemAutomotiveCalendarSyncManager" translatable="false"></string> + <!-- The name of the package that will hold the default automotive navigation role. --> + <string name="config_defaultAutomotiveNavigation" translatable="false"></string> + + <!-- The name of the package that will handle updating the device management role. --> + <string name="config_deviceManagerUpdater" translatable="false"></string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string> @@ -2695,10 +2700,27 @@ <!-- Configure mobile tcp buffer sizes in the form: rat-name:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max If no value is found for the rat-name in use, the system default will be applied. + + This is deprecated. Please use config_tcp_buffers. --> <string-array name="config_mobile_tcp_buffers"> </string-array> + <!-- Configure tcp buffer sizes in the form: + rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max + If this is configured as an empty string, the system default will be applied. + + For now this config is used by mobile data only. In the future it should be + used by Wi-Fi as well. + + Note that starting from Android 13, the TCP buffer size is fixed after boot up, and should + never be changed based on carriers or the network types. The value should be configured + appropriately based on the device's memory and performance. It is recommended to use lower + values if the device has low memory or doesn't support high-speed network such like LTE, + NR, or Wifi. + --> + <string name="config_tcp_buffers" translatable="false"></string> + <!-- Configure ethernet tcp buffersizes in the form: rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max --> <string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string> @@ -2783,6 +2805,16 @@ <item>350</item> </integer-array> + <!-- A vibration waveform for notifications that specify DEFAULT_VIBRATE. + This value is a float array with values grouped as + { targetAmplitude (within [0,1]), targetFrequency (in hertz), duration (in milliseconds) } + This is only applied on devices with vibration frequency control. If the device doesn't + support frequency control, then the vibration specified in + config_defaultNotificationVibePattern is used instead. + --> + <array name="config_defaultNotificationVibeWaveform"> + </array> + <!-- Vibrator pattern to be used as the default for notifications that do not specify vibration but vibrate anyway because the device is in vibrate mode. @@ -2794,6 +2826,16 @@ <item>100</item> </integer-array> + <!-- A vibration waveform for notifications that do not specify vibration but vibrate anyway, + because the device is in vibrate mode. This value is a float array with values grouped as + { targetAmplitude (within [0,1]), targetFrequency (in hertz), duration (in milliseconds) } + This is only applied on devices with vibration frequency control. If the device doesn't + support frequency control, then the vibration specified in + config_notificationFallbackVibePattern is used instead. + --> + <array name="config_notificationFallbackVibeWaveform"> + </array> + <!-- Flag indicating if the speed up audio on mt call code should be executed --> <bool name="config_speed_up_audio_on_mt_calls">false</bool> @@ -4205,7 +4247,7 @@ <string translatable="false" name="config_defaultRingtoneVibrationSound"></string> <!-- Default number of notifications from the same app before they are automatically grouped by the OS --> - <integer translatable="false" name="config_autoGroupAtCount">4</integer> + <integer translatable="false" name="config_autoGroupAtCount">2</integer> <!-- The OEM specified sensor type for the lift trigger to launch the camera app. --> <integer name="config_cameraLiftTriggerSensorType">-1</integer> @@ -5567,4 +5609,7 @@ <!-- Determines whether SafetyCenter feature is enabled. --> <bool name="config_enableSafetyCenter">true</bool> + + <!-- Flag indicating if help links for Settings app should be enabled. --> + <bool name="config_settingsHelpLinksEnabled">false</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 5fa7409150ab..d2ac8a3c5fbc 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3211,6 +3211,7 @@ </staging-public-group-final> <public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" /> + <public type="attr" name="lineBreakStyle" id="0x0101064d" /> <staging-public-group-final type="id" first-id="0x01fe0000"> <public name="accessibilityActionDragStart" /> @@ -3253,6 +3254,8 @@ <public name="showClockAndComplications" /> <!-- @hide @SystemApi --> <public name="gameSessionService" /> + <public name="localeConfig" /> + <public name="showBackground" /> </staging-public-group> <staging-public-group type="id" first-id="0x01de0000"> @@ -3276,6 +3279,8 @@ <public name="config_systemAppProtectionService" /> <!-- @hide @SystemApi @TestApi --> <public name="config_systemAutomotiveCalendarSyncManager" /> + <!-- @hide @SystemApi --> + <public name="config_defaultAutomotiveNavigation" /> </staging-public-group> <staging-public-group type="dimen" first-id="0x01db0000"> @@ -3317,8 +3322,6 @@ </staging-public-group> <staging-public-group type="bool" first-id="0x01cf0000"> - <!-- @hide @SystemApi --> - <public name="config_systemCaptionsServiceCallsEnabled" /> <!-- @hide @TestApi --> <public name="config_preventImeStartupUnlessTextEditor" /> </staging-public-group> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 2879759db521..6577ebc8cd3f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4046,11 +4046,16 @@ <!-- Description of an application permission that lets it ask user to ignore battery optimizations for that app--> <string name="permdesc_requestIgnoreBatteryOptimizations">Allows an app to ask for permission to ignore battery optimizations for that app.</string> - <!-- Title of an application permission that lets query all other packages. [CHAR LIMIT=NONE] --> + <!-- Title of an application permission that lets it query all other packages. [CHAR LIMIT=NONE] --> <string name="permlab_queryAllPackages">query all packages</string> <!-- Description of an application permission that lets it query all other packages. [CHAR LIMIT=NONE] --> <string name="permdesc_queryAllPackages">Allows an app to see all installed packages.</string> + <!-- Title of an application permission that lets it access SupplementalApis. [CHAR LIMIT=NONE] --> + <string name="permlab_accessSupplementalApi">access SupplementalApis</string> + <!-- Description of an application permission that lets it access SupplementalApis. [CHAR LIMIT=NONE]--> + <string name="permdesc_accessSupplementalApi">Allows an application to access SupplementalApis.</string> + <!-- Shown in the tutorial for tap twice for zoom control. --> <string name="tutorial_double_tap_to_zoom_message_short">Tap twice for zoom control</string> diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index dcfca8497ec6..3b2f24409a88 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -42,7 +42,6 @@ easier. <item name="outlineSpotShadowColor">@color/btn_colored_background_material</item> <item name="textAppearance">?attr/textAppearanceButton</item> <item name="textColor">@color/btn_colored_text_material</item> - <item name="drawableTint">@color/btn_colored_text_material</item> </style> <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView" /> <style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a4b5d3caaabf..5e8851976556 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -481,6 +481,7 @@ <java-symbol type="integer" name="config_safe_media_volume_usb_mB" /> <java-symbol type="integer" name="config_mobile_mtu" /> <java-symbol type="array" name="config_mobile_tcp_buffers" /> + <java-symbol type="string" name="config_tcp_buffers" /> <java-symbol type="integer" name="config_volte_replacement_rat"/> <java-symbol type="integer" name="config_valid_wappush_index" /> <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" /> @@ -1935,7 +1936,9 @@ <java-symbol type="array" name="config_locationExtraPackageNames" /> <java-symbol type="array" name="config_testLocationProviders" /> <java-symbol type="array" name="config_defaultNotificationVibePattern" /> + <java-symbol type="array" name="config_defaultNotificationVibeWaveform" /> <java-symbol type="array" name="config_notificationFallbackVibePattern" /> + <java-symbol type="array" name="config_notificationFallbackVibeWaveform" /> <java-symbol type="bool" name="config_enableServerNotificationEffectsForAutomotive" /> <java-symbol type="bool" name="config_useAttentionLight" /> <java-symbol type="bool" name="config_adaptive_sleep_available" /> @@ -2218,6 +2221,7 @@ <java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" /> <java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" /> <java-symbol type="string" name="config_dreamsDefaultComponent" /> + <java-symbol type="drawable" name="default_dream_preview" /> <java-symbol type="string" name="config_dozeComponent" /> <java-symbol type="string" name="enable_explore_by_touch_warning_title" /> <java-symbol type="string" name="enable_explore_by_touch_warning_message" /> @@ -2338,6 +2342,7 @@ <java-symbol type="string" name="nas_upgrade_notification_disable_action" /> <java-symbol type="string" name="nas_upgrade_notification_learn_more_action" /> <java-symbol type="string" name="nas_upgrade_notification_learn_more_content" /> + <java-symbol type="bool" name="config_settingsHelpLinksEnabled" /> <!-- ImfTest --> <java-symbol type="layout" name="auto_complete_list" /> @@ -3650,6 +3655,8 @@ <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> + <java-symbol type="bool" name="config_systemCaptionsServiceCallsEnabled" /> + <java-symbol type="string" name="notification_channel_foreground_service" /> <java-symbol type="string" name="foreground_service_app_in_background" /> <java-symbol type="string" name="foreground_service_apps_in_background" /> @@ -4639,4 +4646,6 @@ <java-symbol type="string" name="config_supervisedUserCreationPackage"/> <java-symbol type="bool" name="config_enableSafetyCenter" /> + + <java-symbol type="string" name="config_deviceManagerUpdater" /> </resources> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml index 1b1f64afcbae..bd987a03b51f 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml @@ -16,8 +16,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.frameworks.core.batterystatsviewer" - android:sharedUserId="android.uid.system"> + package="com.android.frameworks.core.batterystatsviewer"> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.BATTERY_STATS"/> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java index eb378b92dd0a..6a53f6843d48 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java +++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java @@ -130,23 +130,20 @@ public class BatteryConsumerData { || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) { addEntry(metricTitle, EntryType.UID_POWER_MODELED, requestedBatteryConsumer.getConsumedPower(component), - totalPowerByComponentMah[component] - ); + totalPowerByComponentMah[component]); + addProcessStateEntries(metricTitle, EntryType.UID_POWER_MODELED_PROCESS_STATE, + requestedBatteryConsumer, component); } else { addEntry(metricTitle + " (measured)", EntryType.UID_POWER_MEASURED, requestedBatteryConsumer.getConsumedPower(component), - totalPowerByComponentMah[component] - ); + totalPowerByComponentMah[component]); addProcessStateEntries(metricTitle, EntryType.UID_POWER_MEASURED_PROCESS_STATE, - requestedBatteryConsumer, component - ); + requestedBatteryConsumer, component); addEntry(metricTitle + " (modeled)", EntryType.UID_POWER_MODELED, requestedModeledBatteryConsumer.getConsumedPower(component), - totalModeledPowerByComponentMah[component] - ); + totalModeledPowerByComponentMah[component]); addProcessStateEntries(metricTitle, EntryType.UID_POWER_MODELED_PROCESS_STATE, - requestedModeledBatteryConsumer, component - ); + requestedModeledBatteryConsumer, component); } } diff --git a/core/tests/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml deleted file mode 100644 index 75583d5298cb..000000000000 --- a/core/tests/bluetoothtests/AndroidManifest.xml +++ /dev/null @@ -1,47 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2011 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" - package="com.android.bluetooth.tests" - android:sharedUserId="android.uid.bluetooth" > - - <uses-permission android:name="android.permission.BLUETOOTH" /> - <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> - <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/> - <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> - <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> - <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> - <uses-permission android:name="android.permission.BROADCAST_STICKY" /> - <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> - <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" /> - <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> - <uses-permission android:name="android.permission.RECEIVE_SMS" /> - <uses-permission android:name="android.permission.READ_SMS"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.WRITE_SETTINGS" /> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> - - <application > - <uses-library android:name="android.test.runner" /> - </application> - <instrumentation android:name="android.bluetooth.BluetoothTestRunner" - android:targetPackage="com.android.bluetooth.tests" - android:label="Bluetooth Tests" /> - <instrumentation android:name="android.bluetooth.BluetoothInstrumentation" - android:targetPackage="com.android.bluetooth.tests" - android:label="Bluetooth Test Utils" /> - -</manifest> diff --git a/core/tests/bluetoothtests/AndroidTest.xml b/core/tests/bluetoothtests/AndroidTest.xml deleted file mode 100644 index f93c4ebf5bf6..000000000000 --- a/core/tests/bluetoothtests/AndroidTest.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright 2020 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. ---> -<configuration description="Config for Bluetooth test cases"> - <option name="test-suite-tag" value="apct"/> - <option name="test-suite-tag" value="apct-instrumentation"/> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="BluetoothTests.apk" /> - </target_preparer> - - <option name="test-suite-tag" value="apct"/> - <option name="test-tag" value="BluetoothTests"/> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.android.bluetooth.tests" /> - <option name="hidden-api-checks" value="false"/> - <option name="runner" value="android.bluetooth.BluetoothTestRunner"/> - </test> -</configuration> diff --git a/core/tests/bluetoothtests/OWNERS b/core/tests/bluetoothtests/OWNERS deleted file mode 100644 index 98bb87716207..000000000000 --- a/core/tests/bluetoothtests/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /core/java/android/bluetooth/OWNERS diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java deleted file mode 100644 index bd55426601fc..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link BluetoothCodecConfig}. - * <p> - * To run this test, use: - * runtest --path core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java - */ -public class BluetoothCodecConfigTest extends TestCase { - private static final int[] kCodecTypeArray = new int[] { - BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID, - }; - private static final int[] kCodecPriorityArray = new int[] { - BluetoothCodecConfig.CODEC_PRIORITY_DISABLED, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, - }; - private static final int[] kSampleRateArray = new int[] { - BluetoothCodecConfig.SAMPLE_RATE_NONE, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.SAMPLE_RATE_88200, - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.SAMPLE_RATE_176400, - BluetoothCodecConfig.SAMPLE_RATE_192000, - }; - private static final int[] kBitsPerSampleArray = new int[] { - BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - }; - private static final int[] kChannelModeArray = new int[] { - BluetoothCodecConfig.CHANNEL_MODE_NONE, - BluetoothCodecConfig.CHANNEL_MODE_MONO, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - }; - private static final long[] kCodecSpecific1Array = new long[] { 1000, 1001, 1002, 1003, }; - private static final long[] kCodecSpecific2Array = new long[] { 2000, 2001, 2002, 2003, }; - private static final long[] kCodecSpecific3Array = new long[] { 3000, 3001, 3002, 3003, }; - private static final long[] kCodecSpecific4Array = new long[] { 4000, 4001, 4002, 4003, }; - - private static final int kTotalConfigs = kCodecTypeArray.length * kCodecPriorityArray.length * - kSampleRateArray.length * kBitsPerSampleArray.length * kChannelModeArray.length * - kCodecSpecific1Array.length * kCodecSpecific2Array.length * kCodecSpecific3Array.length * - kCodecSpecific4Array.length; - - private int selectCodecType(int configId) { - int left = kCodecTypeArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecTypeArray.length; - return kCodecTypeArray[index]; - } - - private int selectCodecPriority(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecPriorityArray.length; - return kCodecPriorityArray[index]; - } - - private int selectSampleRate(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kSampleRateArray.length; - return kSampleRateArray[index]; - } - - private int selectBitsPerSample(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kBitsPerSampleArray.length; - return kBitsPerSampleArray[index]; - } - - private int selectChannelMode(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kChannelModeArray.length; - return kChannelModeArray[index]; - } - - private long selectCodecSpecific1(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecSpecific1Array.length; - return kCodecSpecific1Array[index]; - } - - private long selectCodecSpecific2(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecSpecific2Array.length; - return kCodecSpecific2Array[index]; - } - - private long selectCodecSpecific3(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length * kCodecSpecific3Array.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecSpecific3Array.length; - return kCodecSpecific3Array[index]; - } - - private long selectCodecSpecific4(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length * kCodecSpecific3Array.length * - kCodecSpecific4Array.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecSpecific4Array.length; - return kCodecSpecific4Array[index]; - } - - @SmallTest - public void testBluetoothCodecConfig_valid_get_methods() { - - for (int config_id = 0; config_id < kTotalConfigs; config_id++) { - int codec_type = selectCodecType(config_id); - int codec_priority = selectCodecPriority(config_id); - int sample_rate = selectSampleRate(config_id); - int bits_per_sample = selectBitsPerSample(config_id); - int channel_mode = selectChannelMode(config_id); - long codec_specific1 = selectCodecSpecific1(config_id); - long codec_specific2 = selectCodecSpecific2(config_id); - long codec_specific3 = selectCodecSpecific3(config_id); - long codec_specific4 = selectCodecSpecific4(config_id); - - BluetoothCodecConfig bcc = buildBluetoothCodecConfig(codec_type, codec_priority, - sample_rate, bits_per_sample, - channel_mode, codec_specific1, - codec_specific2, codec_specific3, - codec_specific4); - - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC) { - assertTrue(bcc.isMandatoryCodec()); - } else { - assertFalse(bcc.isMandatoryCodec()); - } - - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC) { - assertEquals("SBC", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC) { - assertEquals("AAC", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX) { - assertEquals("aptX", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD) { - assertEquals("aptX HD", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) { - assertEquals("LDAC", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - assertEquals("INVALID CODEC", bcc.getCodecName()); - } - - assertEquals(codec_type, bcc.getCodecType()); - assertEquals(codec_priority, bcc.getCodecPriority()); - assertEquals(sample_rate, bcc.getSampleRate()); - assertEquals(bits_per_sample, bcc.getBitsPerSample()); - assertEquals(channel_mode, bcc.getChannelMode()); - assertEquals(codec_specific1, bcc.getCodecSpecific1()); - assertEquals(codec_specific2, bcc.getCodecSpecific2()); - assertEquals(codec_specific3, bcc.getCodecSpecific3()); - assertEquals(codec_specific4, bcc.getCodecSpecific4()); - } - } - - @SmallTest - public void testBluetoothCodecConfig_equals() { - BluetoothCodecConfig bcc1 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - BluetoothCodecConfig bcc2_same = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertTrue(bcc1.equals(bcc2_same)); - - BluetoothCodecConfig bcc3_codec_type = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc3_codec_type)); - - BluetoothCodecConfig bcc4_codec_priority = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc4_codec_priority)); - - BluetoothCodecConfig bcc5_sample_rate = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc5_sample_rate)); - - BluetoothCodecConfig bcc6_bits_per_sample = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc6_bits_per_sample)); - - BluetoothCodecConfig bcc7_channel_mode = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc7_channel_mode)); - - BluetoothCodecConfig bcc8_codec_specific1 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1001, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc8_codec_specific1)); - - BluetoothCodecConfig bcc9_codec_specific2 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2002, 3000, 4000); - assertFalse(bcc1.equals(bcc9_codec_specific2)); - - BluetoothCodecConfig bcc10_codec_specific3 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3003, 4000); - assertFalse(bcc1.equals(bcc10_codec_specific3)); - - BluetoothCodecConfig bcc11_codec_specific4 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4004); - assertFalse(bcc1.equals(bcc11_codec_specific4)); - } - - private BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType, - int codecPriority, int sampleRate, int bitsPerSample, int channelMode, - long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) { - return new BluetoothCodecConfig.Builder() - .setCodecType(sourceCodecType) - .setCodecPriority(codecPriority) - .setSampleRate(sampleRate) - .setBitsPerSample(bitsPerSample) - .setChannelMode(channelMode) - .setCodecSpecific1(codecSpecific1) - .setCodecSpecific2(codecSpecific2) - .setCodecSpecific3(codecSpecific3) - .setCodecSpecific4(codecSpecific4) - .build(); - - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java deleted file mode 100644 index 1cb2dcae865c..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Unit test cases for {@link BluetoothCodecStatus}. - * <p> - * To run this test, use: - * runtest --path core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java - */ -public class BluetoothCodecStatusTest extends TestCase { - - // Codec configs: A and B are same; C is different - private static final BluetoothCodecConfig config_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig config_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig config_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - // Local capabilities: A and B are same; C is different - private static final BluetoothCodecConfig local_capability1_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability1_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability1_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - - private static final BluetoothCodecConfig local_capability2_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability2_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability2_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - - // Selectable capabilities: A and B are same; C is different - private static final BluetoothCodecConfig selectable_capability1_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability1_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability1_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_A = - new ArrayList() {{ - add(local_capability1_A); - add(local_capability2_A); - add(local_capability3_A); - add(local_capability4_A); - add(local_capability5_A); - }}; - - private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B = - new ArrayList() {{ - add(local_capability1_B); - add(local_capability2_B); - add(local_capability3_B); - add(local_capability4_B); - add(local_capability5_B); - }}; - - private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B_REORDERED = - new ArrayList() {{ - add(local_capability5_B); - add(local_capability4_B); - add(local_capability2_B); - add(local_capability3_B); - add(local_capability1_B); - }}; - - private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_C = - new ArrayList() {{ - add(local_capability1_C); - add(local_capability2_C); - add(local_capability3_C); - add(local_capability4_C); - add(local_capability5_C); - }}; - - private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_A = - new ArrayList() {{ - add(selectable_capability1_A); - add(selectable_capability2_A); - add(selectable_capability3_A); - add(selectable_capability4_A); - add(selectable_capability5_A); - }}; - - private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B = - new ArrayList() {{ - add(selectable_capability1_B); - add(selectable_capability2_B); - add(selectable_capability3_B); - add(selectable_capability4_B); - add(selectable_capability5_B); - }}; - - private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B_REORDERED = - new ArrayList() {{ - add(selectable_capability5_B); - add(selectable_capability4_B); - add(selectable_capability2_B); - add(selectable_capability3_B); - add(selectable_capability1_B); - }}; - - private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_C = - new ArrayList() {{ - add(selectable_capability1_C); - add(selectable_capability2_C); - add(selectable_capability3_C); - add(selectable_capability4_C); - add(selectable_capability5_C); - }}; - - private static final BluetoothCodecStatus bcs_A = - new BluetoothCodecStatus(config_A, LOCAL_CAPABILITY_A, SELECTABLE_CAPABILITY_A); - private static final BluetoothCodecStatus bcs_B = - new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B, SELECTABLE_CAPABILITY_B); - private static final BluetoothCodecStatus bcs_B_reordered = - new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B_REORDERED, - SELECTABLE_CAPABILITY_B_REORDERED); - private static final BluetoothCodecStatus bcs_C = - new BluetoothCodecStatus(config_C, LOCAL_CAPABILITY_C, SELECTABLE_CAPABILITY_C); - - @SmallTest - public void testBluetoothCodecStatus_get_methods() { - - assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_A)); - assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_B)); - assertFalse(Objects.equals(bcs_A.getCodecConfig(), config_C)); - - assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_A)); - assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_B)); - assertFalse(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_C)); - - assertTrue(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_A)); - assertTrue(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_B)); - assertFalse(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_C)); - } - - @SmallTest - public void testBluetoothCodecStatus_equals() { - assertTrue(bcs_A.equals(bcs_B)); - assertTrue(bcs_B.equals(bcs_A)); - assertTrue(bcs_A.equals(bcs_B_reordered)); - assertTrue(bcs_B_reordered.equals(bcs_A)); - assertFalse(bcs_A.equals(bcs_C)); - assertFalse(bcs_C.equals(bcs_A)); - } - - private static BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType, - int codecPriority, int sampleRate, int bitsPerSample, int channelMode, - long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) { - return new BluetoothCodecConfig.Builder() - .setCodecType(sourceCodecType) - .setCodecPriority(codecPriority) - .setSampleRate(sampleRate) - .setBitsPerSample(bitsPerSample) - .setChannelMode(channelMode) - .setCodecSpecific1(codecSpecific1) - .setCodecSpecific2(codecSpecific2) - .setCodecSpecific3(codecSpecific3) - .setCodecSpecific4(codecSpecific4) - .build(); - - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java deleted file mode 100644 index 37b2a50ed670..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.bluetooth; - -import android.app.Activity; -import android.app.Instrumentation; -import android.content.Context; -import android.os.Bundle; - -import junit.framework.Assert; - -import java.util.Set; - -public class BluetoothInstrumentation extends Instrumentation { - - private BluetoothTestUtils mUtils = null; - private BluetoothAdapter mAdapter = null; - private Bundle mArgs = null; - private Bundle mSuccessResult = null; - - private BluetoothTestUtils getBluetoothTestUtils() { - if (mUtils == null) { - mUtils = new BluetoothTestUtils(getContext(), - BluetoothInstrumentation.class.getSimpleName()); - } - return mUtils; - } - - private BluetoothAdapter getBluetoothAdapter() { - if (mAdapter == null) { - mAdapter = ((BluetoothManager)getContext().getSystemService( - Context.BLUETOOTH_SERVICE)).getAdapter(); - } - return mAdapter; - } - - @Override - public void onCreate(Bundle arguments) { - super.onCreate(arguments); - mArgs = arguments; - // create the default result response, but only use it in success code path - mSuccessResult = new Bundle(); - mSuccessResult.putString("result", "SUCCESS"); - start(); - } - - @Override - public void onStart() { - String command = mArgs.getString("command"); - if ("enable".equals(command)) { - enable(); - } else if ("disable".equals(command)) { - disable(); - } else if ("unpairAll".equals(command)) { - unpairAll(); - } else if ("getName".equals(command)) { - getName(); - } else if ("getAddress".equals(command)) { - getAddress(); - } else if ("getBondedDevices".equals(command)) { - getBondedDevices(); - } else { - finish(null); - } - } - - public void enable() { - getBluetoothTestUtils().enable(getBluetoothAdapter()); - finish(mSuccessResult); - } - - public void disable() { - getBluetoothTestUtils().disable(getBluetoothAdapter()); - finish(mSuccessResult); - } - - public void unpairAll() { - getBluetoothTestUtils().unpairAll(getBluetoothAdapter()); - finish(mSuccessResult); - } - - public void getName() { - String name = getBluetoothAdapter().getName(); - mSuccessResult.putString("name", name); - finish(mSuccessResult); - } - - public void getAddress() { - String name = getBluetoothAdapter().getAddress(); - mSuccessResult.putString("address", name); - finish(mSuccessResult); - } - - public void getBondedDevices() { - Set<BluetoothDevice> devices = getBluetoothAdapter().getBondedDevices(); - int i = 0; - for (BluetoothDevice device : devices) { - mSuccessResult.putString(String.format("device-%02d", i), device.getAddress()); - i++; - } - finish(mSuccessResult); - } - - public void finish(Bundle result) { - if (result == null) { - result = new Bundle(); - } - finish(Activity.RESULT_OK, result); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java deleted file mode 100644 index c3d707cd7596..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link BluetoothLeAudioCodecConfig}. - */ -public class BluetoothLeAudioCodecConfigTest extends TestCase { - private int[] mCodecTypeArray = new int[] { - BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3, - BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID, - }; - - @SmallTest - public void testBluetoothLeAudioCodecConfig_valid_get_methods() { - - for (int codecIdx = 0; codecIdx < mCodecTypeArray.length; codecIdx++) { - int codecType = mCodecTypeArray[codecIdx]; - - BluetoothLeAudioCodecConfig leAudioCodecConfig = - buildBluetoothLeAudioCodecConfig(codecType); - - if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) { - assertEquals("LC3", leAudioCodecConfig.getCodecName()); - } - if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName()); - } - - assertEquals(codecType, leAudioCodecConfig.getCodecType()); - } - } - - private BluetoothLeAudioCodecConfig buildBluetoothLeAudioCodecConfig(int sourceCodecType) { - return new BluetoothLeAudioCodecConfig.Builder() - .setCodecType(sourceCodecType) - .build(); - - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java deleted file mode 100644 index 33e9dd7fabc6..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.content.Context; -import android.test.InstrumentationTestCase; - -/** - * Instrumentation test case for stress test involving rebooting the device. - * <p> - * This test case tests that bluetooth is enabled after a device reboot. Because - * the device will reboot, the instrumentation must be driven by a script on the - * host side. - */ -public class BluetoothRebootStressTest extends InstrumentationTestCase { - private static final String TAG = "BluetoothRebootStressTest"; - private static final String OUTPUT_FILE = "BluetoothRebootStressTestOutput.txt"; - - private BluetoothTestUtils mTestUtils; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - Context context = getInstrumentation().getTargetContext(); - mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - - mTestUtils.close(); - } - - /** - * Test method used to start the test by turning bluetooth on. - */ - public void testStart() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - mTestUtils.enable(adapter); - } - - /** - * Test method used in the middle iterations of the test to check if - * bluetooth is on. Does not toggle bluetooth after the check. Assumes that - * bluetooth has been turned on by {@code #testStart()} - */ - public void testMiddleNoToggle() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - - assertTrue(adapter.isEnabled()); - } - - /** - * Test method used in the middle iterations of the test to check if - * bluetooth is on. Toggles bluetooth after the check. Assumes that - * bluetooth has been turned on by {@code #testStart()} - */ - public void testMiddleToggle() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - - assertTrue(adapter.isEnabled()); - - mTestUtils.disable(adapter); - mTestUtils.enable(adapter); - } - - /** - * Test method used in the stop the test by turning bluetooth off. Assumes - * that bluetooth has been turned on by {@code #testStart()} - */ - public void testStop() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - - assertTrue(adapter.isEnabled()); - - mTestUtils.disable(adapter); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java deleted file mode 100644 index 89dbe3f75b56..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.content.Context; -import android.test.InstrumentationTestCase; - -/** - * Stress test suite for Bluetooth related functions. - * - * Includes tests for enabling/disabling bluetooth, enabling/disabling discoverable mode, - * starting/stopping scans, connecting/disconnecting to HFP, A2DP, HID, PAN profiles, and verifying - * that remote connections/disconnections occur for the PAN profile. - * <p> - * This test suite uses {@link android.bluetooth.BluetoothTestRunner} to for parameters such as the - * number of iterations and the addresses of remote Bluetooth devices. - */ -public class BluetoothStressTest extends InstrumentationTestCase { - private static final String TAG = "BluetoothStressTest"; - private static final String OUTPUT_FILE = "BluetoothStressTestOutput.txt"; - /** The amount of time to sleep between issuing start/stop SCO in ms. */ - private static final long SCO_SLEEP_TIME = 2 * 1000; - - private BluetoothAdapter mAdapter; - private BluetoothTestUtils mTestUtils; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - Context context = getInstrumentation().getTargetContext(); - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE); - - // Start all tests in a disabled state. - if (mAdapter.isEnabled()) { - mTestUtils.disable(mAdapter); - } - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - mTestUtils.close(); - } - - /** - * Stress test for enabling and disabling Bluetooth. - */ - public void testEnable() { - int iterations = BluetoothTestRunner.sEnableIterations; - if (iterations == 0) { - return; - } - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("enable iteration " + (i + 1) + " of " + iterations); - mTestUtils.enable(mAdapter); - mTestUtils.disable(mAdapter); - } - } - - /** - * Stress test for putting the device in and taking the device out of discoverable mode. - */ - public void testDiscoverable() { - int iterations = BluetoothTestRunner.sDiscoverableIterations; - if (iterations == 0) { - return; - } - - mTestUtils.enable(mAdapter); - mTestUtils.undiscoverable(mAdapter); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("discoverable iteration " + (i + 1) + " of " + iterations); - mTestUtils.discoverable(mAdapter); - mTestUtils.undiscoverable(mAdapter); - } - } - - /** - * Stress test for starting and stopping Bluetooth scans. - */ - public void testScan() { - int iterations = BluetoothTestRunner.sScanIterations; - if (iterations == 0) { - return; - } - - mTestUtils.enable(mAdapter); - mTestUtils.stopScan(mAdapter); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("scan iteration " + (i + 1) + " of " + iterations); - mTestUtils.startScan(mAdapter); - mTestUtils.stopScan(mAdapter); - } - } - - /** - * Stress test for enabling and disabling the PAN NAP profile. - */ - public void testEnablePan() { - int iterations = BluetoothTestRunner.sEnablePanIterations; - if (iterations == 0) { - return; - } - - mTestUtils.enable(mAdapter); - mTestUtils.disablePan(mAdapter); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("testEnablePan iteration " + (i + 1) + " of " - + iterations); - mTestUtils.enablePan(mAdapter); - mTestUtils.disablePan(mAdapter); - } - } - - /** - * Stress test for pairing and unpairing with a remote device. - * <p> - * In this test, the local device initiates pairing with a remote device, and then unpairs with - * the device after the pairing has successfully completed. - */ - public void testPair() { - int iterations = BluetoothTestRunner.sPairIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("pair iteration " + (i + 1) + " of " + iterations); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.unpair(mAdapter, device); - } - } - - /** - * Stress test for accepting a pairing request and unpairing with a remote device. - * <p> - * In this test, the local device waits for a pairing request from a remote device. It accepts - * the request and then unpairs after the paring has successfully completed. - */ - public void testAcceptPair() { - int iterations = BluetoothTestRunner.sPairIterations; - if (iterations == 0) { - return; - } - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("acceptPair iteration " + (i + 1) + " of " + iterations); - mTestUtils.acceptPair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.unpair(mAdapter, device); - } - } - - /** - * Stress test for connecting and disconnecting with an A2DP source. - * <p> - * In this test, the local device plays the role of an A2DP sink, and initiates connections and - * disconnections with an A2DP source. - */ - public void testConnectA2dp() { - int iterations = BluetoothTestRunner.sConnectA2dpIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.A2DP, null); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.A2DP, - String.format("connectA2dp(device=%s)", device)); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.A2DP, - String.format("disconnectA2dp(device=%s)", device)); - } - - mTestUtils.unpair(mAdapter, device); - } - - /** - * Stress test for connecting and disconnecting the HFP with a hands free device. - * <p> - * In this test, the local device plays the role of an HFP audio gateway, and initiates - * connections and disconnections with a hands free device. - */ - public void testConnectHeadset() { - int iterations = BluetoothTestRunner.sConnectHeadsetIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HEADSET, - String.format("connectHeadset(device=%s)", device)); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, - String.format("disconnectHeadset(device=%s)", device)); - } - - mTestUtils.unpair(mAdapter, device); - } - - /** - * Stress test for connecting and disconnecting with a HID device. - * <p> - * In this test, the local device plays the role of a HID host, and initiates connections and - * disconnections with a HID device. - */ - public void testConnectInput() { - int iterations = BluetoothTestRunner.sConnectInputIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST, null); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HID_HOST, - String.format("connectInput(device=%s)", device)); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST, - String.format("disconnectInput(device=%s)", device)); - } - - mTestUtils.unpair(mAdapter, device); - } - - /** - * Stress test for connecting and disconnecting with a PAN NAP. - * <p> - * In this test, the local device plays the role of a PANU, and initiates connections and - * disconnections with a NAP. - */ - public void testConnectPan() { - int iterations = BluetoothTestRunner.sConnectPanIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("connectPan iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectPan(mAdapter, device); - mTestUtils.disconnectPan(mAdapter, device); - } - - mTestUtils.unpair(mAdapter, device); - } - - /** - * Stress test for verifying a PANU connecting and disconnecting with the device. - * <p> - * In this test, the local device plays the role of a NAP which a remote PANU connects and - * disconnects from. - */ - public void testIncomingPanConnection() { - int iterations = BluetoothTestRunner.sConnectPanIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.disablePan(mAdapter); - mTestUtils.enablePan(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.acceptPair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("incomingPanConnection iteration " + (i + 1) + " of " - + iterations); - mTestUtils.incomingPanConnection(mAdapter, device); - mTestUtils.incomingPanDisconnection(mAdapter, device); - } - - mTestUtils.unpair(mAdapter, device); - mTestUtils.disablePan(mAdapter); - } - - /** - * Stress test for verifying that AudioManager can open and close SCO connections. - * <p> - * In this test, a HSP connection is opened with an external headset and the SCO connection is - * repeatibly opened and closed. - */ - public void testStartStopSco() { - int iterations = BluetoothTestRunner.sStartStopScoIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HEADSET, null); - mTestUtils.stopSco(mAdapter, device); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("startStopSco iteration " + (i + 1) + " of " + iterations); - mTestUtils.startSco(mAdapter, device); - sleep(SCO_SLEEP_TIME); - mTestUtils.stopSco(mAdapter, device); - sleep(SCO_SLEEP_TIME); - } - - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null); - mTestUtils.unpair(mAdapter, device); - } - - /* Make sure there is at least 1 unread message in the last week on remote device */ - public void testMceSetMessageStatus() { - int iterations = BluetoothTestRunner.sMceSetMessageStatusIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.MAP_CLIENT, null); - mTestUtils.mceGetUnreadMessage(mAdapter, device); - - for (int i = 0; i < iterations; i++) { - mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.READ); - mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.UNREAD); - } - - /** - * It is hard to find device to support set undeleted status, so just - * set deleted in 1 iteration - **/ - mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.DELETED); - } - - private void sleep(long time) { - try { - Thread.sleep(time); - } catch (InterruptedException e) { - } - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java deleted file mode 100644 index d19c2c3e7e24..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import junit.framework.TestSuite; - -import android.os.Bundle; -import android.test.InstrumentationTestRunner; -import android.test.InstrumentationTestSuite; -import android.util.Log; - -/** - * Instrumentation test runner for Bluetooth tests. - * <p> - * To run: - * <pre> - * {@code - * adb shell am instrument \ - * [-e enable_iterations <iterations>] \ - * [-e discoverable_iterations <iterations>] \ - * [-e scan_iterations <iterations>] \ - * [-e enable_pan_iterations <iterations>] \ - * [-e pair_iterations <iterations>] \ - * [-e connect_a2dp_iterations <iterations>] \ - * [-e connect_headset_iterations <iterations>] \ - * [-e connect_input_iterations <iterations>] \ - * [-e connect_pan_iterations <iterations>] \ - * [-e start_stop_sco_iterations <iterations>] \ - * [-e mce_set_message_status_iterations <iterations>] \ - * [-e pair_address <address>] \ - * [-e headset_address <address>] \ - * [-e a2dp_address <address>] \ - * [-e input_address <address>] \ - * [-e pan_address <address>] \ - * [-e pair_pin <pin>] \ - * [-e pair_passkey <passkey>] \ - * -w com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner - * } - * </pre> - */ -public class BluetoothTestRunner extends InstrumentationTestRunner { - private static final String TAG = "BluetoothTestRunner"; - - public static int sEnableIterations = 100; - public static int sDiscoverableIterations = 1000; - public static int sScanIterations = 1000; - public static int sEnablePanIterations = 1000; - public static int sPairIterations = 100; - public static int sConnectHeadsetIterations = 100; - public static int sConnectA2dpIterations = 100; - public static int sConnectInputIterations = 100; - public static int sConnectPanIterations = 100; - public static int sStartStopScoIterations = 100; - public static int sMceSetMessageStatusIterations = 100; - - public static String sDeviceAddress = ""; - public static byte[] sDevicePairPin = {'1', '2', '3', '4'}; - public static int sDevicePairPasskey = 123456; - - @Override - public TestSuite getAllTests() { - TestSuite suite = new InstrumentationTestSuite(this); - suite.addTestSuite(BluetoothStressTest.class); - return suite; - } - - @Override - public ClassLoader getLoader() { - return BluetoothTestRunner.class.getClassLoader(); - } - - @Override - public void onCreate(Bundle arguments) { - String val = arguments.getString("enable_iterations"); - if (val != null) { - try { - sEnableIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("discoverable_iterations"); - if (val != null) { - try { - sDiscoverableIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("scan_iterations"); - if (val != null) { - try { - sScanIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("enable_pan_iterations"); - if (val != null) { - try { - sEnablePanIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("pair_iterations"); - if (val != null) { - try { - sPairIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("connect_a2dp_iterations"); - if (val != null) { - try { - sConnectA2dpIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("connect_headset_iterations"); - if (val != null) { - try { - sConnectHeadsetIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("connect_input_iterations"); - if (val != null) { - try { - sConnectInputIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("connect_pan_iterations"); - if (val != null) { - try { - sConnectPanIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("start_stop_sco_iterations"); - if (val != null) { - try { - sStartStopScoIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("mce_set_message_status_iterations"); - if (val != null) { - try { - sMceSetMessageStatusIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("device_address"); - if (val != null) { - sDeviceAddress = val; - } - - val = arguments.getString("device_pair_pin"); - if (val != null) { - byte[] pin = BluetoothDevice.convertPinToBytes(val); - if (pin != null) { - sDevicePairPin = pin; - } - } - - val = arguments.getString("device_pair_passkey"); - if (val != null) { - try { - sDevicePairPasskey = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - Log.i(TAG, String.format("enable_iterations=%d", sEnableIterations)); - Log.i(TAG, String.format("discoverable_iterations=%d", sDiscoverableIterations)); - Log.i(TAG, String.format("scan_iterations=%d", sScanIterations)); - Log.i(TAG, String.format("pair_iterations=%d", sPairIterations)); - Log.i(TAG, String.format("connect_a2dp_iterations=%d", sConnectA2dpIterations)); - Log.i(TAG, String.format("connect_headset_iterations=%d", sConnectHeadsetIterations)); - Log.i(TAG, String.format("connect_input_iterations=%d", sConnectInputIterations)); - Log.i(TAG, String.format("connect_pan_iterations=%d", sConnectPanIterations)); - Log.i(TAG, String.format("start_stop_sco_iterations=%d", sStartStopScoIterations)); - Log.i(TAG, String.format("device_address=%s", sDeviceAddress)); - Log.i(TAG, String.format("device_pair_pin=%s", new String(sDevicePairPin))); - Log.i(TAG, String.format("device_pair_passkey=%d", sDevicePairPasskey)); - - // Call onCreate last since we want to set the static variables first. - super.onCreate(arguments); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java deleted file mode 100644 index 8eb6ebcda826..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java +++ /dev/null @@ -1,1651 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; -import android.os.Environment; -import android.util.Log; - -import junit.framework.Assert; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -public class BluetoothTestUtils extends Assert { - - /** Timeout for enable/disable in ms. */ - private static final int ENABLE_DISABLE_TIMEOUT = 20000; - /** Timeout for discoverable/undiscoverable in ms. */ - private static final int DISCOVERABLE_UNDISCOVERABLE_TIMEOUT = 5000; - /** Timeout for starting/stopping a scan in ms. */ - private static final int START_STOP_SCAN_TIMEOUT = 5000; - /** Timeout for pair/unpair in ms. */ - private static final int PAIR_UNPAIR_TIMEOUT = 20000; - /** Timeout for connecting/disconnecting a profile in ms. */ - private static final int CONNECT_DISCONNECT_PROFILE_TIMEOUT = 20000; - /** Timeout to start or stop a SCO channel in ms. */ - private static final int START_STOP_SCO_TIMEOUT = 10000; - /** Timeout to connect a profile proxy in ms. */ - private static final int CONNECT_PROXY_TIMEOUT = 5000; - /** Time between polls in ms. */ - private static final int POLL_TIME = 100; - /** Timeout to get map message in ms. */ - private static final int GET_UNREAD_MESSAGE_TIMEOUT = 10000; - /** Timeout to set map message status in ms. */ - private static final int SET_MESSAGE_STATUS_TIMEOUT = 2000; - - private abstract class FlagReceiver extends BroadcastReceiver { - private int mExpectedFlags = 0; - private int mFiredFlags = 0; - private long mCompletedTime = -1; - - public FlagReceiver(int expectedFlags) { - mExpectedFlags = expectedFlags; - } - - public int getFiredFlags() { - synchronized (this) { - return mFiredFlags; - } - } - - public long getCompletedTime() { - synchronized (this) { - return mCompletedTime; - } - } - - protected void setFiredFlag(int flag) { - synchronized (this) { - mFiredFlags |= flag; - if ((mFiredFlags & mExpectedFlags) == mExpectedFlags) { - mCompletedTime = System.currentTimeMillis(); - } - } - } - } - - private class BluetoothReceiver extends FlagReceiver { - private static final int DISCOVERY_STARTED_FLAG = 1; - private static final int DISCOVERY_FINISHED_FLAG = 1 << 1; - private static final int SCAN_MODE_NONE_FLAG = 1 << 2; - private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3; - private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4; - private static final int STATE_OFF_FLAG = 1 << 5; - private static final int STATE_TURNING_ON_FLAG = 1 << 6; - private static final int STATE_ON_FLAG = 1 << 7; - private static final int STATE_TURNING_OFF_FLAG = 1 << 8; - private static final int STATE_GET_MESSAGE_FINISHED_FLAG = 1 << 9; - private static final int STATE_SET_MESSAGE_STATUS_FINISHED_FLAG = 1 << 10; - - public BluetoothReceiver(int expectedFlags) { - super(expectedFlags); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) { - setFiredFlag(DISCOVERY_STARTED_FLAG); - } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) { - setFiredFlag(DISCOVERY_FINISHED_FLAG); - } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) { - int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1); - assertNotSame(-1, mode); - switch (mode) { - case BluetoothAdapter.SCAN_MODE_NONE: - setFiredFlag(SCAN_MODE_NONE_FLAG); - break; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE: - setFiredFlag(SCAN_MODE_CONNECTABLE_FLAG); - break; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: - setFiredFlag(SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG); - break; - } - } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); - assertNotSame(-1, state); - switch (state) { - case BluetoothAdapter.STATE_OFF: - setFiredFlag(STATE_OFF_FLAG); - break; - case BluetoothAdapter.STATE_TURNING_ON: - setFiredFlag(STATE_TURNING_ON_FLAG); - break; - case BluetoothAdapter.STATE_ON: - setFiredFlag(STATE_ON_FLAG); - break; - case BluetoothAdapter.STATE_TURNING_OFF: - setFiredFlag(STATE_TURNING_OFF_FLAG); - break; - } - } - } - } - - private class PairReceiver extends FlagReceiver { - private static final int STATE_BONDED_FLAG = 1; - private static final int STATE_BONDING_FLAG = 1 << 1; - private static final int STATE_NONE_FLAG = 1 << 2; - - private BluetoothDevice mDevice; - private int mPasskey; - private byte[] mPin; - - public PairReceiver(BluetoothDevice device, int passkey, byte[] pin, int expectedFlags) { - super(expectedFlags); - - mDevice = device; - mPasskey = passkey; - mPin = pin; - } - - @Override - public void onReceive(Context context, Intent intent) { - if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) { - return; - } - - if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { - int varient = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, -1); - assertNotSame(-1, varient); - switch (varient) { - case BluetoothDevice.PAIRING_VARIANT_PIN: - case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS: - mDevice.setPin(mPin); - break; - case BluetoothDevice.PAIRING_VARIANT_PASSKEY: - break; - case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: - case BluetoothDevice.PAIRING_VARIANT_CONSENT: - mDevice.setPairingConfirmation(true); - break; - case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: - break; - } - } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { - int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - assertNotSame(-1, state); - switch (state) { - case BluetoothDevice.BOND_NONE: - setFiredFlag(STATE_NONE_FLAG); - break; - case BluetoothDevice.BOND_BONDING: - setFiredFlag(STATE_BONDING_FLAG); - break; - case BluetoothDevice.BOND_BONDED: - setFiredFlag(STATE_BONDED_FLAG); - break; - } - } - } - } - - private class ConnectProfileReceiver extends FlagReceiver { - private static final int STATE_DISCONNECTED_FLAG = 1; - private static final int STATE_CONNECTING_FLAG = 1 << 1; - private static final int STATE_CONNECTED_FLAG = 1 << 2; - private static final int STATE_DISCONNECTING_FLAG = 1 << 3; - - private BluetoothDevice mDevice; - private int mProfile; - private String mConnectionAction; - - public ConnectProfileReceiver(BluetoothDevice device, int profile, int expectedFlags) { - super(expectedFlags); - - mDevice = device; - mProfile = profile; - - switch (mProfile) { - case BluetoothProfile.A2DP: - mConnectionAction = BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED; - break; - case BluetoothProfile.HEADSET: - mConnectionAction = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED; - break; - case BluetoothProfile.HID_HOST: - mConnectionAction = BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED; - break; - case BluetoothProfile.PAN: - mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED; - break; - case BluetoothProfile.MAP_CLIENT: - mConnectionAction = BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED; - break; - default: - mConnectionAction = null; - } - } - - @Override - public void onReceive(Context context, Intent intent) { - if (mConnectionAction != null && mConnectionAction.equals(intent.getAction())) { - if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) { - return; - } - - int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - assertNotSame(-1, state); - switch (state) { - case BluetoothProfile.STATE_DISCONNECTED: - setFiredFlag(STATE_DISCONNECTED_FLAG); - break; - case BluetoothProfile.STATE_CONNECTING: - setFiredFlag(STATE_CONNECTING_FLAG); - break; - case BluetoothProfile.STATE_CONNECTED: - setFiredFlag(STATE_CONNECTED_FLAG); - break; - case BluetoothProfile.STATE_DISCONNECTING: - setFiredFlag(STATE_DISCONNECTING_FLAG); - break; - } - } - } - } - - private class ConnectPanReceiver extends ConnectProfileReceiver { - private int mRole; - - public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) { - super(device, BluetoothProfile.PAN, expectedFlags); - - mRole = role; - } - - @Override - public void onReceive(Context context, Intent intent) { - if (mRole != intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, -1)) { - return; - } - - super.onReceive(context, intent); - } - } - - private class StartStopScoReceiver extends FlagReceiver { - private static final int STATE_CONNECTED_FLAG = 1; - private static final int STATE_DISCONNECTED_FLAG = 1 << 1; - - public StartStopScoReceiver(int expectedFlags) { - super(expectedFlags); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(intent.getAction())) { - int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, - AudioManager.SCO_AUDIO_STATE_ERROR); - assertNotSame(AudioManager.SCO_AUDIO_STATE_ERROR, state); - switch(state) { - case AudioManager.SCO_AUDIO_STATE_CONNECTED: - setFiredFlag(STATE_CONNECTED_FLAG); - break; - case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: - setFiredFlag(STATE_DISCONNECTED_FLAG); - break; - } - } - } - } - - - private class MceSetMessageStatusReceiver extends FlagReceiver { - private static final int MESSAGE_RECEIVED_FLAG = 1; - private static final int STATUS_CHANGED_FLAG = 1 << 1; - - public MceSetMessageStatusReceiver(int expectedFlags) { - super(expectedFlags); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (BluetoothMapClient.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { - String handle = intent.getStringExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE); - assertNotNull(handle); - setFiredFlag(MESSAGE_RECEIVED_FLAG); - mMsgHandle = handle; - } else if (BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED.equals(intent.getAction())) { - int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); - assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); - setFiredFlag(STATUS_CHANGED_FLAG); - } else if (BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED.equals(intent.getAction())) { - int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); - assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); - setFiredFlag(STATUS_CHANGED_FLAG); - } - } - } - - private BluetoothProfile.ServiceListener mServiceListener = - new BluetoothProfile.ServiceListener() { - @Override - public void onServiceConnected(int profile, BluetoothProfile proxy) { - synchronized (this) { - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = (BluetoothA2dp) proxy; - break; - case BluetoothProfile.HEADSET: - mHeadset = (BluetoothHeadset) proxy; - break; - case BluetoothProfile.HID_HOST: - mInput = (BluetoothHidHost) proxy; - break; - case BluetoothProfile.PAN: - mPan = (BluetoothPan) proxy; - break; - case BluetoothProfile.MAP_CLIENT: - mMce = (BluetoothMapClient) proxy; - break; - } - } - } - - @Override - public void onServiceDisconnected(int profile) { - synchronized (this) { - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = null; - break; - case BluetoothProfile.HEADSET: - mHeadset = null; - break; - case BluetoothProfile.HID_HOST: - mInput = null; - break; - case BluetoothProfile.PAN: - mPan = null; - break; - case BluetoothProfile.MAP_CLIENT: - mMce = null; - break; - } - } - } - }; - - private List<BroadcastReceiver> mReceivers = new ArrayList<BroadcastReceiver>(); - - private BufferedWriter mOutputWriter; - private String mTag; - private String mOutputFile; - - private Context mContext; - private BluetoothA2dp mA2dp = null; - private BluetoothHeadset mHeadset = null; - private BluetoothHidHost mInput = null; - private BluetoothPan mPan = null; - private BluetoothMapClient mMce = null; - private String mMsgHandle = null; - - /** - * Creates a utility instance for testing Bluetooth. - * - * @param context The context of the application using the utility. - * @param tag The log tag of the application using the utility. - */ - public BluetoothTestUtils(Context context, String tag) { - this(context, tag, null); - } - - /** - * Creates a utility instance for testing Bluetooth. - * - * @param context The context of the application using the utility. - * @param tag The log tag of the application using the utility. - * @param outputFile The path to an output file if the utility is to write results to a - * separate file. - */ - public BluetoothTestUtils(Context context, String tag, String outputFile) { - mContext = context; - mTag = tag; - mOutputFile = outputFile; - - if (mOutputFile == null) { - mOutputWriter = null; - } else { - try { - mOutputWriter = new BufferedWriter(new FileWriter(new File( - Environment.getExternalStorageDirectory(), mOutputFile), true)); - } catch (IOException e) { - Log.w(mTag, "Test output file could not be opened", e); - mOutputWriter = null; - } - } - } - - /** - * Closes the utility instance and unregisters any BroadcastReceivers. - */ - public void close() { - while (!mReceivers.isEmpty()) { - mContext.unregisterReceiver(mReceivers.remove(0)); - } - - if (mOutputWriter != null) { - try { - mOutputWriter.close(); - } catch (IOException e) { - Log.w(mTag, "Test output file could not be closed", e); - } - } - } - - /** - * Enables Bluetooth and checks to make sure that Bluetooth was turned on and that the correct - * actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void enable(BluetoothAdapter adapter) { - writeOutput("Enabling Bluetooth adapter."); - assertFalse(adapter.isEnabled()); - int btState = adapter.getState(); - final Semaphore completionSemaphore = new Semaphore(0); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { - return; - } - final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - if (state == BluetoothAdapter.STATE_ON) { - completionSemaphore.release(); - } - } - }; - - final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - mContext.registerReceiver(receiver, filter); - // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to - // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable. - // So no assertion applied here. - adapter.enable(); - boolean success = false; - try { - success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS); - writeOutput(String.format("enable() completed in 0 ms")); - } catch (final InterruptedException e) { - // This should never happen but just in case it does, the test will fail anyway. - } - mContext.unregisterReceiver(receiver); - if (!success) { - fail(String.format("enable() timeout: state=%d (expected %d)", btState, - BluetoothAdapter.STATE_ON)); - } - } - - /** - * Disables Bluetooth and checks to make sure that Bluetooth was turned off and that the correct - * actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void disable(BluetoothAdapter adapter) { - writeOutput("Disabling Bluetooth adapter."); - assertTrue(adapter.isEnabled()); - int btState = adapter.getState(); - final Semaphore completionSemaphore = new Semaphore(0); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { - return; - } - final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - if (state == BluetoothAdapter.STATE_OFF) { - completionSemaphore.release(); - } - } - }; - - final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - mContext.registerReceiver(receiver, filter); - // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to - // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable. - // So no assertion applied here. - adapter.disable(); - boolean success = false; - try { - success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS); - writeOutput(String.format("disable() completed in 0 ms")); - } catch (final InterruptedException e) { - // This should never happen but just in case it does, the test will fail anyway. - } - mContext.unregisterReceiver(receiver); - if (!success) { - fail(String.format("disable() timeout: state=%d (expected %d)", btState, - BluetoothAdapter.STATE_OFF)); - } - } - - /** - * Puts the local device into discoverable mode and checks to make sure that the local device - * is in discoverable mode and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void discoverable(BluetoothAdapter adapter) { - if (!adapter.isEnabled()) { - fail("discoverable() bluetooth not enabled"); - } - - int scanMode = adapter.getScanMode(); - if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE) { - return; - } - - final Semaphore completionSemaphore = new Semaphore(0); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (!BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) { - return; - } - final int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, - BluetoothAdapter.SCAN_MODE_NONE); - if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - completionSemaphore.release(); - } - } - }; - - final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); - mContext.registerReceiver(receiver, filter); - assertEquals(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE), - BluetoothStatusCodes.SUCCESS); - boolean success = false; - try { - success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT, - TimeUnit.MILLISECONDS); - writeOutput(String.format("discoverable() completed in 0 ms")); - } catch (final InterruptedException e) { - // This should never happen but just in case it does, the test will fail anyway. - } - mContext.unregisterReceiver(receiver); - if (!success) { - fail(String.format("discoverable() timeout: scanMode=%d (expected %d)", scanMode, - BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)); - } - } - - /** - * Puts the local device into connectable only mode and checks to make sure that the local - * device is in in connectable mode and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void undiscoverable(BluetoothAdapter adapter) { - if (!adapter.isEnabled()) { - fail("undiscoverable() bluetooth not enabled"); - } - - int scanMode = adapter.getScanMode(); - if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - return; - } - - final Semaphore completionSemaphore = new Semaphore(0); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (!BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) { - return; - } - final int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, - BluetoothAdapter.SCAN_MODE_NONE); - if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) { - completionSemaphore.release(); - } - } - }; - - final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); - mContext.registerReceiver(receiver, filter); - assertEquals(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE), - BluetoothStatusCodes.SUCCESS); - boolean success = false; - try { - success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT, - TimeUnit.MILLISECONDS); - writeOutput(String.format("undiscoverable() completed in 0 ms")); - } catch (InterruptedException e) { - // This should never happen but just in case it does, the test will fail anyway. - } - mContext.unregisterReceiver(receiver); - if (!success) { - fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d)", scanMode, - BluetoothAdapter.SCAN_MODE_CONNECTABLE)); - } - } - - /** - * Starts a scan for remote devices and checks to make sure that the local device is scanning - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void startScan(BluetoothAdapter adapter) { - int mask = BluetoothReceiver.DISCOVERY_STARTED_FLAG; - - if (!adapter.isEnabled()) { - fail("startScan() bluetooth not enabled"); - } - - if (adapter.isDiscovering()) { - return; - } - - BluetoothReceiver receiver = getBluetoothReceiver(mask); - - long start = System.currentTimeMillis(); - assertTrue(adapter.startDiscovery()); - - while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { - if (adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { - writeOutput(String.format("startScan() completed in %d ms", - (receiver.getCompletedTime() - start))); - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", - adapter.isDiscovering(), firedFlags, mask)); - } - - /** - * Stops a scan for remote devices and checks to make sure that the local device is not scanning - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void stopScan(BluetoothAdapter adapter) { - int mask = BluetoothReceiver.DISCOVERY_FINISHED_FLAG; - - if (!adapter.isEnabled()) { - fail("stopScan() bluetooth not enabled"); - } - - if (!adapter.isDiscovering()) { - return; - } - - BluetoothReceiver receiver = getBluetoothReceiver(mask); - - long start = System.currentTimeMillis(); - assertTrue(adapter.cancelDiscovery()); - - while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { - if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { - writeOutput(String.format("stopScan() completed in %d ms", - (receiver.getCompletedTime() - start))); - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", - adapter.isDiscovering(), firedFlags, mask)); - - } - - /** - * Enables PAN tethering on the local device and checks to make sure that tethering is enabled. - * - * @param adapter The BT adapter. - */ - public void enablePan(BluetoothAdapter adapter) { - if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); - assertNotNull(mPan); - - long start = System.currentTimeMillis(); - mPan.setBluetoothTethering(true); - long stop = System.currentTimeMillis(); - assertTrue(mPan.isTetheringOn()); - - writeOutput(String.format("enablePan() completed in %d ms", (stop - start))); - } - - /** - * Disables PAN tethering on the local device and checks to make sure that tethering is - * disabled. - * - * @param adapter The BT adapter. - */ - public void disablePan(BluetoothAdapter adapter) { - if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); - assertNotNull(mPan); - - long start = System.currentTimeMillis(); - mPan.setBluetoothTethering(false); - long stop = System.currentTimeMillis(); - assertFalse(mPan.isTetheringOn()); - - writeOutput(String.format("disablePan() completed in %d ms", (stop - start))); - } - - /** - * Initiates a pairing with a remote device and checks to make sure that the devices are paired - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. - * @param pin The pairing pin if pairing requires a pin. Any value if not. - */ - public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) { - pairOrAcceptPair(adapter, device, passkey, pin, true); - } - - /** - * Accepts a pairing with a remote device and checks to make sure that the devices are paired - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. - * @param pin The pairing pin if pairing requires a pin. Any value if not. - */ - public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, - byte[] pin) { - pairOrAcceptPair(adapter, device, passkey, pin, false); - } - - /** - * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and - * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept - * a pairing request. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. - * @param pin The pairing pin if pairing requires a pin. Any value if not. - * @param shouldPair Whether to pair or accept the pair. - */ - private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, - byte[] pin, boolean shouldPair) { - int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG; - long start = -1; - String methodName; - if (shouldPair) { - methodName = String.format("pair(device=%s)", device); - } else { - methodName = String.format("acceptPair(device=%s)", device); - } - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - PairReceiver receiver = getPairReceiver(device, passkey, pin, mask); - - int state = device.getBondState(); - switch (state) { - case BluetoothDevice.BOND_NONE: - assertFalse(adapter.getBondedDevices().contains(device)); - start = System.currentTimeMillis(); - if (shouldPair) { - assertTrue(device.createBond()); - } - break; - case BluetoothDevice.BOND_BONDING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - case BluetoothDevice.BOND_BONDED: - assertTrue(adapter.getBondedDevices().contains(device)); - return; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { - state = device.getBondState(); - if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) { - assertTrue(adapter.getBondedDevices().contains(device)); - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", - methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); - } - - /** - * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void unpair(BluetoothAdapter adapter, BluetoothDevice device) { - int mask = PairReceiver.STATE_NONE_FLAG; - long start = -1; - String methodName = String.format("unpair(device=%s)", device); - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - PairReceiver receiver = getPairReceiver(device, 0, null, mask); - - int state = device.getBondState(); - switch (state) { - case BluetoothDevice.BOND_NONE: - assertFalse(adapter.getBondedDevices().contains(device)); - removeReceiver(receiver); - return; - case BluetoothDevice.BOND_BONDING: - start = System.currentTimeMillis(); - assertTrue(device.removeBond()); - break; - case BluetoothDevice.BOND_BONDED: - assertTrue(adapter.getBondedDevices().contains(device)); - start = System.currentTimeMillis(); - assertTrue(device.removeBond()); - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { - if (device.getBondState() == BluetoothDevice.BOND_NONE - && (receiver.getFiredFlags() & mask) == mask) { - assertFalse(adapter.getBondedDevices().contains(device)); - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", - methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); - } - - /** - * Deletes all pairings of remote devices - * @param adapter the BT adapter - */ - public void unpairAll(BluetoothAdapter adapter) { - Set<BluetoothDevice> devices = adapter.getBondedDevices(); - for (BluetoothDevice device : devices) { - unpair(adapter, device); - } - } - - /** - * Connects a profile from the local device to a remote device and checks to make sure that the - * profile is connected and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#HID_HOST} or {@link BluetoothProfile#MAP_CLIENT}.. - * @param methodName The method name to printed in the logs. If null, will be - * "connectProfile(profile=<profile>, device=<device>)" - */ - public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, - String methodName) { - if (methodName == null) { - methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device); - } - int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG - | ConnectProfileReceiver.STATE_CONNECTED_FLAG); - long start = -1; - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - BluetoothProfile proxy = connectProxy(adapter, profile); - assertNotNull(proxy); - - ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); - - int state = proxy.getConnectionState(device); - switch (state) { - case BluetoothProfile.STATE_CONNECTED: - removeReceiver(receiver); - return; - case BluetoothProfile.STATE_CONNECTING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - case BluetoothProfile.STATE_DISCONNECTED: - case BluetoothProfile.STATE_DISCONNECTING: - start = System.currentTimeMillis(); - if (profile == BluetoothProfile.A2DP) { - assertTrue(((BluetoothA2dp)proxy).connect(device)); - } else if (profile == BluetoothProfile.HEADSET) { - assertTrue(((BluetoothHeadset)proxy).connect(device)); - } else if (profile == BluetoothProfile.HID_HOST) { - assertTrue(((BluetoothHidHost)proxy).connect(device)); - } else if (profile == BluetoothProfile.MAP_CLIENT) { - assertTrue(((BluetoothMapClient)proxy).connect(device)); - } - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { - state = proxy.getConnectionState(device); - if (state == BluetoothProfile.STATE_CONNECTED - && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", - methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask)); - } - - /** - * Disconnects a profile between the local device and a remote device and checks to make sure - * that the profile is disconnected and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}. - * @param methodName The method name to printed in the logs. If null, will be - * "connectProfile(profile=<profile>, device=<device>)" - */ - public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, - String methodName) { - if (methodName == null) { - methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device); - } - int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG - | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG); - long start = -1; - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - BluetoothProfile proxy = connectProxy(adapter, profile); - assertNotNull(proxy); - - ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); - - int state = proxy.getConnectionState(device); - switch (state) { - case BluetoothProfile.STATE_CONNECTED: - case BluetoothProfile.STATE_CONNECTING: - start = System.currentTimeMillis(); - if (profile == BluetoothProfile.A2DP) { - assertTrue(((BluetoothA2dp)proxy).disconnect(device)); - } else if (profile == BluetoothProfile.HEADSET) { - assertTrue(((BluetoothHeadset)proxy).disconnect(device)); - } else if (profile == BluetoothProfile.HID_HOST) { - assertTrue(((BluetoothHidHost)proxy).disconnect(device)); - } else if (profile == BluetoothProfile.MAP_CLIENT) { - assertTrue(((BluetoothMapClient)proxy).disconnect(device)); - } - break; - case BluetoothProfile.STATE_DISCONNECTED: - removeReceiver(receiver); - return; - case BluetoothProfile.STATE_DISCONNECTING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { - state = proxy.getConnectionState(device); - if (state == BluetoothProfile.STATE_DISCONNECTED - && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", - methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask)); - } - - /** - * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that - * the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) { - connectPanOrIncomingPanConnection(adapter, device, true); - } - - /** - * Checks that a remote PANU connects to the local NAP correctly and that the correct actions - * were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) { - connectPanOrIncomingPanConnection(adapter, device, false); - } - - /** - * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and - * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a - * remote NAP or verify that a remote device connected to the local NAP. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param connect If the method should initiate the connection (is PANU) - */ - private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device, - boolean connect) { - long start = -1; - int mask, role; - String methodName; - - if (connect) { - methodName = String.format("connectPan(device=%s)", device); - mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG | - ConnectProfileReceiver.STATE_CONNECTING_FLAG); - role = BluetoothPan.LOCAL_PANU_ROLE; - } else { - methodName = String.format("incomingPanConnection(device=%s)", device); - mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG; - role = BluetoothPan.LOCAL_NAP_ROLE; - } - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); - assertNotNull(mPan); - ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); - - int state = mPan.getConnectionState(device); - switch (state) { - case BluetoothPan.STATE_CONNECTED: - removeReceiver(receiver); - return; - case BluetoothPan.STATE_CONNECTING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - case BluetoothPan.STATE_DISCONNECTED: - case BluetoothPan.STATE_DISCONNECTING: - start = System.currentTimeMillis(); - if (role == BluetoothPan.LOCAL_PANU_ROLE) { - Log.i("BT", "connect to pan"); - assertTrue(mPan.connect(device)); - } - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { - state = mPan.getConnectionState(device); - if (state == BluetoothPan.STATE_CONNECTED - && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask)); - } - - /** - * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) { - disconnectFromRemoteOrVerifyConnectNap(adapter, device, true); - } - - /** - * Checks that a remote PANU disconnects from the local NAP correctly and that the correct - * actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) { - disconnectFromRemoteOrVerifyConnectNap(adapter, device, false); - } - - /** - * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and - * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect - * from a remote NAP or verify that a remote device disconnected from the local NAP. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param disconnect Whether the method should connect or verify. - */ - private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter, - BluetoothDevice device, boolean disconnect) { - long start = -1; - int mask, role; - String methodName; - - if (disconnect) { - methodName = String.format("disconnectPan(device=%s)", device); - mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG | - ConnectProfileReceiver.STATE_DISCONNECTING_FLAG); - role = BluetoothPan.LOCAL_PANU_ROLE; - } else { - methodName = String.format("incomingPanDisconnection(device=%s)", device); - mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG; - role = BluetoothPan.LOCAL_NAP_ROLE; - } - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); - assertNotNull(mPan); - ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); - - int state = mPan.getConnectionState(device); - switch (state) { - case BluetoothPan.STATE_CONNECTED: - case BluetoothPan.STATE_CONNECTING: - start = System.currentTimeMillis(); - if (role == BluetoothPan.LOCAL_PANU_ROLE) { - assertTrue(mPan.disconnect(device)); - } - break; - case BluetoothPan.STATE_DISCONNECTED: - removeReceiver(receiver); - return; - case BluetoothPan.STATE_DISCONNECTING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { - state = mPan.getConnectionState(device); - if (state == BluetoothHidHost.STATE_DISCONNECTED - && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, state, BluetoothHidHost.STATE_DISCONNECTED, firedFlags, mask)); - } - - /** - * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks - * to make sure that the channel is opened and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void startSco(BluetoothAdapter adapter, BluetoothDevice device) { - startStopSco(adapter, device, true); - } - - /** - * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks - * to make sure that the channel is closed and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) { - startStopSco(adapter, device, false); - } - /** - * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and - * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param isStart Whether the SCO channel should be opened. - */ - private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) { - long start = -1; - int mask; - String methodName; - - if (isStart) { - methodName = String.format("startSco(device=%s)", device); - mask = StartStopScoReceiver.STATE_CONNECTED_FLAG; - } else { - methodName = String.format("stopSco(device=%s)", device); - mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG; - } - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - assertNotNull(manager); - - if (!manager.isBluetoothScoAvailableOffCall()) { - fail(String.format("%s device does not support SCO", methodName)); - } - - boolean isScoOn = manager.isBluetoothScoOn(); - if (isStart == isScoOn) { - return; - } - - StartStopScoReceiver receiver = getStartStopScoReceiver(mask); - start = System.currentTimeMillis(); - if (isStart) { - manager.startBluetoothSco(); - } else { - manager.stopBluetoothSco(); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) { - isScoOn = manager.isBluetoothScoOn(); - if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)", - methodName, isScoOn, isStart, firedFlags, mask)); - } - - /** - * Writes a string to the logcat and a file if a file has been specified in the constructor. - * - * @param s The string to be written. - */ - public void writeOutput(String s) { - Log.i(mTag, s); - if (mOutputWriter == null) { - return; - } - try { - mOutputWriter.write(s + "\n"); - mOutputWriter.flush(); - } catch (IOException e) { - Log.w(mTag, "Could not write to output file", e); - } - } - - public void mceGetUnreadMessage(BluetoothAdapter adapter, BluetoothDevice device) { - int mask; - String methodName = "getUnreadMessage"; - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT); - assertNotNull(mMce); - - if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { - fail(String.format("%s device is not connected", methodName)); - } - - mMsgHandle = null; - mask = MceSetMessageStatusReceiver.MESSAGE_RECEIVED_FLAG; - MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask); - assertTrue(mMce.getUnreadMessages(device)); - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < GET_UNREAD_MESSAGE_TIMEOUT) { - if ((receiver.getFiredFlags() & mask) == mask) { - writeOutput(String.format("%s completed", methodName)); - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, mMce.getConnectionState(device), BluetoothMapClient.STATE_CONNECTED, firedFlags, mask)); - } - - /** - * Set a message to read/unread/deleted/undeleted - */ - public void mceSetMessageStatus(BluetoothAdapter adapter, BluetoothDevice device, int status) { - int mask; - String methodName = "setMessageStatus"; - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT); - assertNotNull(mMce); - - if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { - fail(String.format("%s device is not connected", methodName)); - } - - assertNotNull(mMsgHandle); - mask = MceSetMessageStatusReceiver.STATUS_CHANGED_FLAG; - MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask); - - assertTrue(mMce.setMessageStatus(device, mMsgHandle, status)); - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < SET_MESSAGE_STATUS_TIMEOUT) { - if ((receiver.getFiredFlags() & mask) == mask) { - writeOutput(String.format("%s completed", methodName)); - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, mMce.getConnectionState(device), BluetoothPan.STATE_CONNECTED, firedFlags, mask)); - } - - private void addReceiver(BroadcastReceiver receiver, String[] actions) { - IntentFilter filter = new IntentFilter(); - for (String action: actions) { - filter.addAction(action); - } - mContext.registerReceiver(receiver, filter); - mReceivers.add(receiver); - } - - private BluetoothReceiver getBluetoothReceiver(int expectedFlags) { - String[] actions = { - BluetoothAdapter.ACTION_DISCOVERY_FINISHED, - BluetoothAdapter.ACTION_DISCOVERY_STARTED, - BluetoothAdapter.ACTION_SCAN_MODE_CHANGED, - BluetoothAdapter.ACTION_STATE_CHANGED}; - BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin, - int expectedFlags) { - String[] actions = { - BluetoothDevice.ACTION_PAIRING_REQUEST, - BluetoothDevice.ACTION_BOND_STATE_CHANGED}; - PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile, - int expectedFlags) { - String[] actions = { - BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, - BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, - BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED, - BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED}; - ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile, - expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role, - int expectedFlags) { - String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED}; - ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) { - String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED}; - StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private MceSetMessageStatusReceiver getMceSetMessageStatusReceiver(BluetoothDevice device, - int expectedFlags) { - String[] actions = {BluetoothMapClient.ACTION_MESSAGE_RECEIVED, - BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED, - BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED}; - MceSetMessageStatusReceiver receiver = new MceSetMessageStatusReceiver(expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private void removeReceiver(BroadcastReceiver receiver) { - mContext.unregisterReceiver(receiver); - mReceivers.remove(receiver); - } - - private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) { - switch (profile) { - case BluetoothProfile.A2DP: - if (mA2dp != null) { - return mA2dp; - } - break; - case BluetoothProfile.HEADSET: - if (mHeadset != null) { - return mHeadset; - } - break; - case BluetoothProfile.HID_HOST: - if (mInput != null) { - return mInput; - } - break; - case BluetoothProfile.PAN: - if (mPan != null) { - return mPan; - } - case BluetoothProfile.MAP_CLIENT: - if (mMce != null) { - return mMce; - } - break; - default: - return null; - } - adapter.getProfileProxy(mContext, mServiceListener, profile); - long s = System.currentTimeMillis(); - switch (profile) { - case BluetoothProfile.A2DP: - while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mA2dp; - case BluetoothProfile.HEADSET: - while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mHeadset; - case BluetoothProfile.HID_HOST: - while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mInput; - case BluetoothProfile.PAN: - while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mPan; - case BluetoothProfile.MAP_CLIENT: - while (mMce == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mMce; - default: - return null; - } - } - - private void sleep(long time) { - try { - Thread.sleep(time); - } catch (InterruptedException e) { - } - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java deleted file mode 100644 index 536d722679b6..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link BluetoothUuid}. - * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.BluetoothUuidTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class BluetoothUuidTest extends TestCase { - - @SmallTest - public void testUuidParser() { - byte[] uuid16 = new byte[] { - 0x0B, 0x11 }; - assertEquals(ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"), - BluetoothUuid.parseUuidFrom(uuid16)); - - byte[] uuid32 = new byte[] { - 0x0B, 0x11, 0x33, (byte) 0xFE }; - assertEquals(ParcelUuid.fromString("FE33110B-0000-1000-8000-00805F9B34FB"), - BluetoothUuid.parseUuidFrom(uuid32)); - - byte[] uuid128 = new byte[] { - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, (byte) 0xFF }; - assertEquals(ParcelUuid.fromString("FF0F0E0D-0C0B-0A09-0807-0060504030201"), - BluetoothUuid.parseUuidFrom(uuid128)); - } - - @SmallTest - public void testUuidType() { - assertTrue(BluetoothUuid.is16BitUuid( - ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"))); - assertFalse(BluetoothUuid.is32BitUuid( - ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"))); - - assertFalse(BluetoothUuid.is16BitUuid( - ParcelUuid.fromString("FE33110B-0000-1000-8000-00805F9B34FB"))); - assertTrue(BluetoothUuid.is32BitUuid( - ParcelUuid.fromString("FE33110B-0000-1000-8000-00805F9B34FB"))); - assertFalse(BluetoothUuid.is32BitUuid( - ParcelUuid.fromString("FE33110B-1000-1000-8000-00805F9B34FB"))); - - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/AdvertiseDataTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/AdvertiseDataTest.java deleted file mode 100644 index e58d905357e6..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/AdvertiseDataTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.os.Parcel; -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link AdvertiseData}. - * <p> - * To run the test, use adb shell am instrument -e class 'android.bluetooth.le.AdvertiseDataTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class AdvertiseDataTest extends TestCase { - - private AdvertiseData.Builder mAdvertiseDataBuilder; - - @Override - protected void setUp() throws Exception { - mAdvertiseDataBuilder = new AdvertiseData.Builder(); - } - - @SmallTest - public void testEmptyData() { - Parcel parcel = Parcel.obtain(); - AdvertiseData data = mAdvertiseDataBuilder.build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyServiceUuid() { - Parcel parcel = Parcel.obtain(); - AdvertiseData data = mAdvertiseDataBuilder.setIncludeDeviceName(true).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyManufacturerData() { - Parcel parcel = Parcel.obtain(); - int manufacturerId = 50; - byte[] manufacturerData = new byte[0]; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addManufacturerData(manufacturerId, manufacturerData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyServiceData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - byte[] serviceData = new byte[0]; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceData(uuid, serviceData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testServiceUuid() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceUuid(uuid).addServiceUuid(uuid2).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testManufacturerData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - - int manufacturerId = 50; - byte[] manufacturerData = new byte[] { - (byte) 0xF0, 0x00, 0x02, 0x15 }; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceUuid(uuid).addServiceUuid(uuid2) - .addManufacturerData(manufacturerId, manufacturerData).build(); - - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testServiceData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - byte[] serviceData = new byte[] { - (byte) 0xF0, 0x00, 0x02, 0x15 }; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceData(uuid, serviceData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java deleted file mode 100644 index 35da4bceb620..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanRecord; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for Bluetooth LE scan filters. - * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanFilterTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class ScanFilterTest extends TestCase { - - private static final String DEVICE_MAC = "01:02:03:04:05:AB"; - private ScanResult mScanResult; - private ScanFilter.Builder mFilterBuilder; - - @Override - protected void setUp() throws Exception { - byte[] scanRecord = new byte[] { - 0x02, 0x01, 0x1a, // advertising flags - 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids - 0x04, 0x09, 0x50, 0x65, 0x64, // setName - 0x02, 0x0A, (byte) 0xec, // tx power level - 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data - 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data - 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble - }; - - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(DEVICE_MAC); - mScanResult = new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), - -10, 1397545200000000L); - mFilterBuilder = new ScanFilter.Builder(); - } - - @SmallTest - public void testsetNameFilter() { - ScanFilter filter = mFilterBuilder.setDeviceName("Ped").build(); - assertTrue("setName filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setDeviceName("Pem").build(); - assertFalse("setName filter fails", filter.matches(mScanResult)); - - } - - @SmallTest - public void testDeviceFilter() { - ScanFilter filter = mFilterBuilder.setDeviceAddress(DEVICE_MAC).build(); - assertTrue("device filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build(); - assertFalse("device filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testsetServiceUuidFilter() { - ScanFilter filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB")).build(); - assertTrue("uuid filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); - assertFalse("uuid filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder - .setServiceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), - ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) - .build(); - assertTrue("uuid filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testsetServiceDataFilter() { - byte[] setServiceData = new byte[] { - 0x50, 0x64 }; - ParcelUuid serviceDataUuid = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - ScanFilter filter = mFilterBuilder.setServiceData(serviceDataUuid, setServiceData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] emptyData = new byte[0]; - filter = mFilterBuilder.setServiceData(serviceDataUuid, emptyData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] prefixData = new byte[] { - 0x50 }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, prefixData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] nonMatchData = new byte[] { - 0x51, 0x64 }; - byte[] mask = new byte[] { - (byte) 0x00, (byte) 0xFF }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData, mask).build(); - assertTrue("partial service data filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData).build(); - assertFalse("service data filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testManufacturerSpecificData() { - byte[] setManufacturerData = new byte[] { - 0x02, 0x15 }; - int manufacturerId = 0xE0; - ScanFilter filter = - mFilterBuilder.setManufacturerData(manufacturerId, setManufacturerData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - byte[] emptyData = new byte[0]; - filter = mFilterBuilder.setManufacturerData(manufacturerId, emptyData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - byte[] prefixData = new byte[] { - 0x02 }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, prefixData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - // Test data mask - byte[] nonMatchData = new byte[] { - 0x02, 0x14 }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData).build(); - assertFalse("manufacturer data filter fails", filter.matches(mScanResult)); - byte[] mask = new byte[] { - (byte) 0xFF, (byte) 0x00 - }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData, mask).build(); - assertTrue("partial setManufacturerData filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testReadWriteParcel() { - ScanFilter filter = mFilterBuilder.build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setDeviceName("Ped").build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), - ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")).build(); - testReadWriteParcelForFilter(filter); - - byte[] serviceData = new byte[] { - 0x50, 0x64 }; - - ParcelUuid serviceDataUuid = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - filter = mFilterBuilder.setServiceData(serviceDataUuid, serviceData).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, new byte[0]).build(); - testReadWriteParcelForFilter(filter); - - byte[] serviceDataMask = new byte[] { - (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, serviceData, serviceDataMask) - .build(); - testReadWriteParcelForFilter(filter); - - byte[] manufacturerData = new byte[] { - 0x02, 0x15 }; - int manufacturerId = 0xE0; - filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, new byte[0]).build(); - testReadWriteParcelForFilter(filter); - - byte[] manufacturerDataMask = new byte[] { - (byte) 0xFF, (byte) 0xFF - }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData, - manufacturerDataMask).build(); - testReadWriteParcelForFilter(filter); - } - - private void testReadWriteParcelForFilter(ScanFilter filter) { - Parcel parcel = Parcel.obtain(); - filter.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - ScanFilter filterFromParcel = - ScanFilter.CREATOR.createFromParcel(parcel); - assertEquals(filter, filterFromParcel); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java deleted file mode 100644 index 4e817d4a0d91..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.internal.util.HexDump; -import com.android.modules.utils.BytesMatcher; - -import junit.framework.TestCase; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -/** - * Unit test cases for {@link ScanRecord}. - * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanRecordTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class ScanRecordTest extends TestCase { - /** - * Example raw beacons captured from a Blue Charm BC011 - */ - private static final String RECORD_URL = "0201060303AAFE1716AAFE10EE01626C7565636861726D626561636F6E730009168020691E0EFE13551109426C7565436861726D5F313639363835000000"; - private static final String RECORD_UUID = "0201060303AAFE1716AAFE00EE626C7565636861726D31000000000001000009168020691E0EFE13551109426C7565436861726D5F313639363835000000"; - private static final String RECORD_TLM = "0201060303AAFE1116AAFE20000BF017000008874803FB93540916802069080EFE13551109426C7565436861726D5F313639363835000000000000000000"; - private static final String RECORD_IBEACON = "0201061AFF4C000215426C7565436861726D426561636F6E730EFE1355C509168020691E0EFE13551109426C7565436861726D5F31363936383500000000"; - - @SmallTest - public void testMatchesAnyField_Eddystone_Parser() { - final List<String> found = new ArrayList<>(); - final Predicate<byte[]> matcher = (v) -> { - found.add(HexDump.toHexString(v)); - return false; - }; - ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(RECORD_URL)) - .matchesAnyField(matcher); - - assertEquals(Arrays.asList( - "020106", - "0303AAFE", - "1716AAFE10EE01626C7565636861726D626561636F6E7300", - "09168020691E0EFE1355", - "1109426C7565436861726D5F313639363835"), found); - } - - @SmallTest - public void testMatchesAnyField_Eddystone() { - final BytesMatcher matcher = BytesMatcher.decode("⊆0016AAFE/00FFFFFF"); - assertMatchesAnyField(RECORD_URL, matcher); - assertMatchesAnyField(RECORD_UUID, matcher); - assertMatchesAnyField(RECORD_TLM, matcher); - assertNotMatchesAnyField(RECORD_IBEACON, matcher); - } - - @SmallTest - public void testMatchesAnyField_iBeacon_Parser() { - final List<String> found = new ArrayList<>(); - final Predicate<byte[]> matcher = (v) -> { - found.add(HexDump.toHexString(v)); - return false; - }; - ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(RECORD_IBEACON)) - .matchesAnyField(matcher); - - assertEquals(Arrays.asList( - "020106", - "1AFF4C000215426C7565436861726D426561636F6E730EFE1355C5", - "09168020691E0EFE1355", - "1109426C7565436861726D5F313639363835"), found); - } - - @SmallTest - public void testMatchesAnyField_iBeacon() { - final BytesMatcher matcher = BytesMatcher.decode("⊆00FF4C0002/00FFFFFFFF"); - assertNotMatchesAnyField(RECORD_URL, matcher); - assertNotMatchesAnyField(RECORD_UUID, matcher); - assertNotMatchesAnyField(RECORD_TLM, matcher); - assertMatchesAnyField(RECORD_IBEACON, matcher); - } - - @SmallTest - public void testParser() { - byte[] scanRecord = new byte[] { - 0x02, 0x01, 0x1a, // advertising flags - 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids - 0x04, 0x09, 0x50, 0x65, 0x64, // name - 0x02, 0x0A, (byte) 0xec, // tx power level - 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data - 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data - 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble - }; - ScanRecord data = ScanRecord.parseFromBytes(scanRecord); - assertEquals(0x1a, data.getAdvertiseFlags()); - ParcelUuid uuid1 = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - assertTrue(data.getServiceUuids().contains(uuid1)); - assertTrue(data.getServiceUuids().contains(uuid2)); - - assertEquals("Ped", data.getDeviceName()); - assertEquals(-20, data.getTxPowerLevel()); - - assertTrue(data.getManufacturerSpecificData().get(0x00E0) != null); - assertArrayEquals(new byte[] { - 0x02, 0x15 }, data.getManufacturerSpecificData().get(0x00E0)); - - assertTrue(data.getServiceData().containsKey(uuid2)); - assertArrayEquals(new byte[] { - 0x50, 0x64 }, data.getServiceData().get(uuid2)); - } - - // Assert two byte arrays are equal. - private static void assertArrayEquals(byte[] expected, byte[] actual) { - if (!Arrays.equals(expected, actual)) { - fail("expected:<" + Arrays.toString(expected) + - "> but was:<" + Arrays.toString(actual) + ">"); - } - - } - - private static void assertMatchesAnyField(String record, BytesMatcher matcher) { - assertTrue(ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(record)) - .matchesAnyField(matcher)); - } - - private static void assertNotMatchesAnyField(String record, BytesMatcher matcher) { - assertFalse(ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(record)) - .matchesAnyField(matcher)); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java deleted file mode 100644 index 01d5c593bf27..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.os.Parcel; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for Bluetooth LE scans. - * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanResultTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class ScanResultTest extends TestCase { - - /** - * Test read and write parcel of ScanResult - */ - @SmallTest - public void testScanResultParceling() { - BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( - "01:02:03:04:05:06"); - byte[] scanRecord = new byte[] { - 1, 2, 3 }; - int rssi = -10; - long timestampMicros = 10000L; - - ScanResult result = new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), rssi, - timestampMicros); - Parcel parcel = Parcel.obtain(); - result.writeToParcel(parcel, 0); - // Need to reset parcel data position to the beginning. - parcel.setDataPosition(0); - ScanResult resultFromParcel = ScanResult.CREATOR.createFromParcel(parcel); - assertEquals(result, resultFromParcel); - } - -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java deleted file mode 100644 index 7c42c3b46775..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth.le; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Test for Bluetooth LE {@link ScanSettings}. - */ -public class ScanSettingsTest extends TestCase { - - @SmallTest - public void testCallbackType() { - ScanSettings.Builder builder = new ScanSettings.Builder(); - builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES); - builder.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH); - builder.setCallbackType(ScanSettings.CALLBACK_TYPE_MATCH_LOST); - builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST); - try { - builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES | ScanSettings.CALLBACK_TYPE_MATCH_LOST); - fail("should have thrown IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // nothing to do - } - - try { - builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES | - ScanSettings.CALLBACK_TYPE_FIRST_MATCH); - fail("should have thrown IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // nothing to do - } - - try { - builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES | - ScanSettings.CALLBACK_TYPE_FIRST_MATCH | - ScanSettings.CALLBACK_TYPE_MATCH_LOST); - fail("should have thrown IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // nothing to do - } - - } -} diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index c18a70c5dd2a..c1f3c4fc12c7 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -93,33 +93,9 @@ android_test { java_genrule { name: "FrameworksCoreTests_apks_as_resources", srcs: [ - ":FrameworksCoreTests_install", - ":FrameworksCoreTests_install_bad_dex", - ":FrameworksCoreTests_install_complete_package_info", - ":FrameworksCoreTests_install_decl_perm", ":FrameworksCoreTests_install_jni_lib_open_from_apk", - ":FrameworksCoreTests_install_loc_auto", - ":FrameworksCoreTests_install_loc_internal", - ":FrameworksCoreTests_install_loc_sdcard", - ":FrameworksCoreTests_install_loc_unspecified", - ":FrameworksCoreTests_install_use_perm_good", - ":FrameworksCoreTests_install_uses_feature", ":FrameworksCoreTests_install_verifier_bad", ":FrameworksCoreTests_install_verifier_good", - ":FrameworksCoreTests_keyset_permdef_sa_unone", - ":FrameworksCoreTests_keyset_permuse_sa_ua_ub", - ":FrameworksCoreTests_keyset_permuse_sb_ua_ub", - ":FrameworksCoreTests_keyset_sab_ua", - ":FrameworksCoreTests_keyset_sa_ua", - ":FrameworksCoreTests_keyset_sa_uab", - ":FrameworksCoreTests_keyset_sa_ua_ub", - ":FrameworksCoreTests_keyset_sa_ub", - ":FrameworksCoreTests_keyset_sa_unone", - ":FrameworksCoreTests_keyset_sau_ub", - ":FrameworksCoreTests_keyset_sb_ua", - ":FrameworksCoreTests_keyset_sb_ub", - ":FrameworksCoreTests_keyset_splata_api", - ":FrameworksCoreTests_keyset_splat_api", ":FrameworksCoreTests_locales", ":FrameworksCoreTests_overlay_config", ":FrameworksCoreTests_version_1", @@ -173,4 +149,4 @@ android_library { "framework", "framework-res", ], -}
\ No newline at end of file +} diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 14a3a01630ab..f2b35c72a567 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1670,11 +1670,4 @@ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.coretests" android:label="Frameworks Core Tests" /> - <key-sets> - <key-set android:name="A" > - <public-key android:name="keyA" - android:value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMpNthdOxud7roPDZMMomOqXgJJdRfIWpkKEqmC61Mv+Nf6QY3TorEwJeghjSmqj7IbBKrtvfQq4E2XJO1HuspmQO4Ng2gvn+r+6EwNfKc9k55d6s+27SR867jKurBbHNtZMG+tjL1yH4r+tNzcuJCsgyAFqLmxFdcxEwzNvREyRpoYc5RDR0mmTwkMCUhJ6CId1EYEKiCEdNzxv+fWPEb21u+/MWpleGCILs8kglRVb2q/WOzAAvGr4FY5plfaE6N+lr7+UschQ+aMi1+uqewo2o0qPFVmZP5hnwj55K4UMzu/NhhDqQQsX4cSGES1KgHo5MTqRqZjN/I7emw5pFQIDAQAB"/> - </key-set> - <upgrade-key-set android:name="A"/> - </key-sets> </manifest> diff --git a/core/tests/coretests/apks/install_loc_sdcard/Android.bp b/core/tests/coretests/apks/install_loc_sdcard/Android.bp deleted file mode 100644 index 708e655e07db..000000000000 --- a/core/tests/coretests/apks/install_loc_sdcard/Android.bp +++ /dev/null @@ -1,15 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_install_loc_sdcard", - defaults: ["FrameworksCoreTests_apks_defaults"], - - srcs: ["**/*.java"], -} diff --git a/core/tests/coretests/apks/install_loc_unspecified/Android.bp b/core/tests/coretests/apks/install_loc_unspecified/Android.bp deleted file mode 100644 index 76869e9b9ed5..000000000000 --- a/core/tests/coretests/apks/install_loc_unspecified/Android.bp +++ /dev/null @@ -1,15 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_install_loc_unspecified", - defaults: ["FrameworksCoreTests_apks_defaults"], - - srcs: ["**/*.java"], -} diff --git a/core/tests/coretests/apks/install_use_perm_good/Android.bp b/core/tests/coretests/apks/install_use_perm_good/Android.bp deleted file mode 100644 index 89700ddb94be..000000000000 --- a/core/tests/coretests/apks/install_use_perm_good/Android.bp +++ /dev/null @@ -1,15 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_install_use_perm_good", - defaults: ["FrameworksCoreTests_apks_defaults"], - - srcs: ["**/*.java"], -} diff --git a/core/tests/coretests/apks/install_uses_feature/Android.bp b/core/tests/coretests/apks/install_uses_feature/Android.bp deleted file mode 100644 index 913a96a1cd27..000000000000 --- a/core/tests/coretests/apks/install_uses_feature/Android.bp +++ /dev/null @@ -1,15 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_install_uses_feature", - defaults: ["FrameworksCoreTests_apks_defaults"], - - srcs: ["**/*.java"], -} diff --git a/core/tests/coretests/apks/keyset/Android.bp b/core/tests/coretests/apks/keyset/Android.bp deleted file mode 100644 index 93c3b1f60327..000000000000 --- a/core/tests/coretests/apks/keyset/Android.bp +++ /dev/null @@ -1,129 +0,0 @@ -//apks signed by keyset_A -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_unone", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uNone/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_ua", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uA/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uB/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_uab", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uAB/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_ua_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uAuB/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_permdef_sa_unone", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "permDef/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_permuse_sa_ua_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "permUse/AndroidManifest.xml", -} - -//apks signed by keyset_B -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sb_ua", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_B_cert", - manifest: "uA/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sb_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_B_cert", - manifest: "uB/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_permuse_sb_ua_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_B_cert", - manifest: "permUse/AndroidManifest.xml", -} - -//apks signed by keyset_A and keyset_B -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sab_ua", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - additional_certificates: [":FrameworksCoreTests_keyset_B_cert"], - manifest: "uA/AndroidManifest.xml", -} - -//apks signed by keyset_A and unit_test -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sau_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - additional_certificates: [":FrameworksCoreTests_keyset_B_cert"], - manifest: "uB/AndroidManifest.xml", -} - -//apks signed by platform only -android_test_helper_app { - name: "FrameworksCoreTests_keyset_splat_api", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: "platform", - manifest: "api_test/AndroidManifest.xml", -} - -//apks signed by platform and keyset_A -android_test_helper_app { - name: "FrameworksCoreTests_keyset_splata_api", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: "platform", - additional_certificates: [":FrameworksCoreTests_keyset_A_cert"], - manifest: "api_test/AndroidManifest.xml", -} diff --git a/core/tests/coretests/certs/Android.bp b/core/tests/coretests/certs/Android.bp index 8411183c3335..8d4ecf4253c3 100644 --- a/core/tests/coretests/certs/Android.bp +++ b/core/tests/coretests/certs/Android.bp @@ -10,16 +10,6 @@ package { } android_app_certificate { - name: "FrameworksCoreTests_keyset_A_cert", - certificate: "keyset_A", -} - -android_app_certificate { - name: "FrameworksCoreTests_keyset_B_cert", - certificate: "keyset_B", -} - -android_app_certificate { name: "FrameworksCoreTests_unit_test_cert", certificate: "unit_test", } diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index fd3079fd295d..d3e8bb0ec317 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -17,6 +17,8 @@ package android.app; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import androidx.test.filters.SmallTest; @@ -25,7 +27,9 @@ import org.junit.Test; /** * Test for verifying the behavior of {@link PropertyInvalidatedCache}. This test does - * not use any actual binder calls - it is entirely self-contained. + * not use any actual binder calls - it is entirely self-contained. This test also relies + * on the test mode of {@link PropertyInvalidatedCache} because Android SELinux rules do + * not grant test processes the permission to set system properties. * <p> * Build/Install/Run: * atest FrameworksCoreTests:PropertyInvalidatedCacheTests @@ -33,6 +37,8 @@ import org.junit.Test; @SmallTest public class PropertyInvalidatedCacheTests { + // This property is never set. The test process does not have permission to set any + // properties. static final String CACHE_PROPERTY = "cache_key.cache_test_a"; // This class is a proxy for binder calls. It contains a counter that increments @@ -58,7 +64,8 @@ public class PropertyInvalidatedCacheTests { } } - // Clear the test mode after every test, in case this process is used for other tests. + // Clear the test mode after every test, in case this process is used for other + // tests. This also resets the test property map. @After public void tearDown() throws Exception { PropertyInvalidatedCache.setTestMode(false); @@ -176,5 +183,161 @@ public class PropertyInvalidatedCacheTests { } }; assertEquals(true, cache1.getDisabledState()); + + // Remove the record of caches being locally disabled. This is a clean-up step. + cache1.clearDisableLocal(); + assertEquals(true, cache1.getDisabledState()); + assertEquals(true, cache2.getDisabledState()); + assertEquals(false, cache3.getDisabledState()); + + // Create a new cache1. Verify that the new instance is not disabled. + cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { + @Override + public Boolean recompute(Integer x) { + return tester.query(x); + } + }; + assertEquals(false, cache1.getDisabledState()); + } + + private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; + + private static class TestCache extends PropertyInvalidatedCache<Integer, String> { + TestCache() { + this(CACHE_PROPERTY); + } + + TestCache(String key) { + super(4, key); + setTestMode(true); + testPropertyName(key); + } + + @Override + public String recompute(Integer qv) { + mRecomputeCount += 1; + return "foo" + qv.toString(); + } + + int getRecomputeCount() { + return mRecomputeCount; + } + + private int mRecomputeCount = 0; + } + + @Test + public void testCacheRecompute() { + TestCache cache = new TestCache(); + cache.invalidateCache(); + assertEquals(cache.isDisabledLocal(), false); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo6", cache.query(6)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } + + @Test + public void testCacheInitialState() { + TestCache cache = new TestCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } + + @Test + public void testCachePropertyUnset() { + TestCache cache = new TestCache(UNSET_KEY); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + } + + @Test + public void testCacheDisableState() { + TestCache cache = new TestCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + cache.disableSystemWide(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(5, cache.getRecomputeCount()); + cache.invalidateCache(); // Should not reenable + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(7, cache.getRecomputeCount()); + } + + @Test + public void testRefreshSameObject() { + int[] refreshCount = new int[1]; + TestCache cache = new TestCache() { + @Override + public String refresh(String oldResult, Integer query) { + refreshCount[0] += 1; + return oldResult; + } + }; + cache.invalidateCache(); + String result1 = cache.query(5); + assertEquals("foo5", result1); + String result2 = cache.query(5); + assertSame(result1, result2); + assertEquals(1, cache.getRecomputeCount()); + assertEquals(1, refreshCount[0]); + assertEquals("foo5", cache.query(5)); + assertEquals(2, refreshCount[0]); + } + + @Test + public void testRefreshInvalidateRace() { + int[] refreshCount = new int[1]; + TestCache cache = new TestCache() { + @Override + public String refresh(String oldResult, Integer query) { + refreshCount[0] += 1; + invalidateCache(); + return new String(oldResult); + } + }; + cache.invalidateCache(); + String result1 = cache.query(5); + assertEquals("foo5", result1); + String result2 = cache.query(5); + assertEquals(result1, result2); + assertNotSame(result1, result2); + assertEquals(2, cache.getRecomputeCount()); + } + + @Test + public void testLocalProcessDisable() { + TestCache cache = new TestCache(); + assertEquals(cache.isDisabledLocal(), false); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals(cache.isDisabledLocal(), false); + cache.disableLocal(); + assertEquals(cache.isDisabledLocal(), true); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); } } diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index d6a7682475f2..045e746281a6 100644 --- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -136,8 +136,8 @@ public class MeasuredParagraphTest { MeasuredParagraph mt = null; mt = MeasuredParagraph.buildForStaticLayout( - PAINT, "XXX", 0, 3, LTR, MeasuredText.Builder.HYPHENATION_MODE_NONE, false, - null /* no hint */, null); + PAINT, null /* line break config */, "XXX", 0, 3, LTR, + MeasuredText.Builder.HYPHENATION_MODE_NONE, false, null /* no hint */, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -152,8 +152,8 @@ public class MeasuredParagraphTest { // Recycle it MeasuredParagraph mt2 = MeasuredParagraph.buildForStaticLayout( - PAINT, "_VVV_", 1, 4, RTL, MeasuredText.Builder.HYPHENATION_MODE_NONE, false, - null /* no hint */, mt); + PAINT, null /* line break config */, "_VVV_", 1, 4, RTL, + MeasuredText.Builder.HYPHENATION_MODE_NONE, false, null /* no hint */, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java index 90ce305b3dab..412d6ec975ac 100644 --- a/core/tests/coretests/src/android/text/TextLineTest.java +++ b/core/tests/coretests/src/android/text/TextLineTest.java @@ -48,7 +48,7 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */, - 0, 0 /* no ellipsis */); + 0, 0 /* no ellipsis */, false /* useFallbackLinespace */); final float originalWidth = tl.metrics(null); final float expandedWidth = 2 * originalWidth; @@ -105,7 +105,7 @@ public class TextLineTest { tl.set(paint, str, 0, str.length(), TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(str, 0, str.length()) ? -1 : 1, layout.getLineDirections(0), tabStops != null, tabStops, - 0, 0 /* no ellipsis */); + 0, 0 /* no ellipsis */, false /* useFallbackLineSpacing */); return tl; } @@ -276,7 +276,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertFalse(span.mIsUsed); @@ -292,7 +293,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertTrue(span.mIsUsed); @@ -308,7 +310,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertTrue(span.mIsUsed); } diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java index 2dd3f69852c1..ba9c8d92e173 100644 --- a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java @@ -64,12 +64,12 @@ public class SparseDoubleArrayTest { } @Test - public void testAdd() { + public void testIncrementValue() { final SparseDoubleArray sda = new SparseDoubleArray(); sda.put(4, 6.1); - sda.add(4, -1.2); - sda.add(2, -1.2); + sda.incrementValue(4, -1.2); + sda.incrementValue(2, -1.2); assertEquals(6.1 - 1.2, sda.get(4), PRECISION); assertEquals(-1.2, sda.get(2), PRECISION); diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java index df2d752e04b9..b29b6f1f8e9d 100644 --- a/core/tests/coretests/src/android/util/SparseLongArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java @@ -154,4 +154,16 @@ public class SparseLongArrayTest { assertRemoved(startIndex, endIndex); assertTrue(isSame(sparseLongArray2, mSparseLongArray)); } + + @Test + public void testIncrementValue() { + final SparseLongArray sla = new SparseLongArray(); + + sla.put(4, 6); + sla.incrementValue(4, 4); + sla.incrementValue(2, 5); + + assertEquals(6 + 4, sla.get(4)); + assertEquals(5, sla.get(2)); + } } diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java index 2c31b08bebdc..187803c90084 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java @@ -20,8 +20,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; -import android.content.pm.parsing.ParsingPackageRead; -import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.os.Build; import android.text.TextUtils; import android.util.ArrayMap; @@ -80,7 +79,7 @@ public class OverlayConfigIterationRule implements TestRule { } public boolean isMatchRequiredSystemProperty() { - return ParsingPackageUtils.checkRequiredSystemProperties( + return FrameworkParsingPackageUtils.checkRequiredSystemProperties( requiredSystemPropertyName, requiredSystemPropertyValue); } } @@ -174,11 +173,12 @@ public class OverlayConfigIterationRule implements TestRule { mIteration = Iteration.SYSTEM_SERVER; doAnswer((InvocationOnMock invocation) -> { final Object[] args = invocation.getArguments(); - final TriConsumer<ParsingPackageRead, Boolean, File> f = - (TriConsumer<ParsingPackageRead, Boolean, File>) args[0]; + final TriConsumer<PackageProvider.Package, Boolean, File> f = + (TriConsumer<PackageProvider.Package, Boolean, File>) args[0]; for (Map.Entry<File, TestOverlayInfo> overlay : mTestOverlayInfos.entrySet()) { - final ParsingPackageRead a = Mockito.mock(ParsingPackageRead.class); + final PackageProvider.Package a = + Mockito.mock(PackageProvider.Package.class); final TestOverlayInfo info = overlay.getValue(); if ((!TextUtils.isEmpty(info.requiredSystemPropertyName) || !TextUtils.isEmpty(info.requiredSystemPropertyValue)) diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index 0d2d047b7f0b..a4091294aa70 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -176,7 +176,6 @@ public class InteractionJankMonitorTest { private InteractionJankMonitor createMockedInteractionJankMonitor() { InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); doReturn(true).when(monitor).shouldMonitor(anyInt()); - doNothing().when(monitor).notifyEvents(any(), any(), any()); return monitor; } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 69e617aaac19..8cc4c348111c 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -83,7 +83,7 @@ public class BatteryUsageStatsTest { final Parcel parcel = Parcel.obtain(); parcel.writeParcelable(outBatteryUsageStats, 0); - assertThat(parcel.dataSize()).isLessThan(5500); + assertThat(parcel.dataSize()).isLessThan(7000); parcel.setDataPosition(0); diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java index d361da95a1b9..d0a13fc79ce2 100644 --- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java @@ -25,18 +25,20 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; +import android.os.UidBatteryConsumer; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.google.common.collect.ImmutableList; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; - @RunWith(AndroidJUnit4.class) @SmallTest +@SuppressWarnings("GuardedBy") public class BluetoothPowerCalculatorTest { private static final double PRECISION = 0.00001; private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; @@ -62,6 +64,69 @@ public class BluetoothPowerCalculatorTest { } @Test + public void testTimerBasedModel_byProcessState() { + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + info1.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); + + batteryStats.updateBluetoothStateLocked(info1, + 0/*1_000_000*/, 2000, 2000); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000); + info2.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000), + new UidTraffic(APP_UID, 7000, 8000))); + + batteryStats.updateBluetoothStateLocked(info2, + 0 /*5_000_000 */, 4000, 4000); + + BluetoothPowerCalculator calculator = + new BluetoothPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .powerProfileModeledOnly() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(6166); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isWithin(PRECISION).of(0.1226666); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.081); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.0416666); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); + } + + @Test public void testReportedEnergyBasedModel() { setupBluetoothEnergyInfo(4000000, BatteryStats.POWER_DATA_UNAVAILABLE); @@ -90,6 +155,70 @@ public class BluetoothPowerCalculatorTest { } @Test + public void testMeasuredEnergyBasedModel_byProcessState() { + mStatsRule.initMeasuredEnergyStatsLocked(); + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + info1.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); + + batteryStats.updateBluetoothStateLocked(info1, + 1_000_000, 2000, 2000); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000); + info2.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000), + new UidTraffic(APP_UID, 7000, 8000))); + + batteryStats.updateBluetoothStateLocked(info2, + 5_000_000, 4000, 4000); + + BluetoothPowerCalculator calculator = + new BluetoothPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(6166); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isWithin(PRECISION).of(0.8220561); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.4965352); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.3255208); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); + } + + + @Test public void testIgnoreMeasuredEnergyBasedModel() { mStatsRule.initMeasuredEnergyStatsLocked(); setupBluetoothEnergyInfo(4000000, 1200000); @@ -107,10 +236,9 @@ public class BluetoothPowerCalculatorTest { final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000, BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0, reportedEnergyUc); - info.setUidTraffic(new ArrayList<UidTraffic>(){{ - add(new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000)); - add(new UidTraffic(APP_UID, 3000, 4000)); - }}); + info.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); mStatsRule.getBatteryStats().updateBluetoothStateLocked(info, consumedEnergyUc, 1000, 1000); } diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java index e7ce9a03f18a..a7873576e728 100644 --- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java @@ -25,6 +25,8 @@ import android.app.usage.NetworkStatsManager; import android.net.NetworkCapabilities; import android.net.NetworkStats; import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; import android.os.WorkSource; @@ -40,6 +42,7 @@ import org.mockito.Mock; @RunWith(AndroidJUnit4.class) @SmallTest +@SuppressWarnings("GuardedBy") public class WifiPowerCalculatorTest { private static final double PRECISION = 0.00001; @@ -66,14 +69,18 @@ public class WifiPowerCalculatorTest { batteryStats.noteNetworkInterfaceForTransports("wifi", new int[]{NetworkCapabilities.TRANSPORT_WIFI}); - NetworkStats networkStats = new NetworkStats(10000, 1) - .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) - .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); - mStatsRule.setNetworkStats(networkStats); + mStatsRule.setNetworkStats(buildNetworkStats(10000, 1000, 100, 2000, 20)); return batteryStats; } + private NetworkStats buildNetworkStats(long elapsedRealtime, int rxBytes, int rxPackets, + int txBytes, int txPackets) { + return new NetworkStats(elapsedRealtime, 1) + .insertEntry("wifi", APP_UID, 0, 0, rxBytes, rxPackets, txBytes, txPackets, 100) + .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); + } + /** Sets up an WifiActivityEnergyInfo for ActivityController-model-based tests. */ private WifiActivityEnergyInfo setupPowerControllerBasedModelEnergyNumbersInfo() { return new WifiActivityEnergyInfo(10000, @@ -115,6 +122,61 @@ public class WifiPowerCalculatorTest { } @Test + public void testPowerControllerBasedModel_powerProfile_byProcessState() { + final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); + + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + batteryStats.updateWifiState(new WifiActivityEnergyInfo(2000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000), + POWER_DATA_UNAVAILABLE, 2000, 2000, + mNetworkStatsManager); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + mStatsRule.setNetworkStats(buildNetworkStats(4000, 5000, 200, 7000, 80)); + + batteryStats.updateWifiState(new WifiActivityEnergyInfo(4000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000), + POWER_DATA_UNAVAILABLE, 4000, 4000, + mNetworkStatsManager); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .powerProfileModeledOnly() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isEqualTo(12423); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(2.0214666); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(1.1214666); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.9); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); + } + + @Test public void testPowerControllerBasedModel_measured() { final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo(); @@ -148,6 +210,60 @@ public class WifiPowerCalculatorTest { .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); } + @Test + public void testPowerControllerBasedModel_measured_byProcessState() { + final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); + + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + batteryStats.updateWifiState(new WifiActivityEnergyInfo(2000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000), + 1_000_000, 2000, 2000, + mNetworkStatsManager); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + mStatsRule.setNetworkStats(buildNetworkStats(4000, 5000, 200, 7000, 80)); + + batteryStats.updateWifiState(new WifiActivityEnergyInfo(4000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000), + 5_000_000, 4000, 4000, + mNetworkStatsManager); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isEqualTo(12423); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(1.0325211); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.5517519); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.4807691); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); + } + /** Sets up batterystats object with prepopulated network & timer data for Timer-model tests. */ private BatteryStatsImpl setupTimerBasedModelTestNumbers() { final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java new file mode 100644 index 000000000000..875ab3862354 --- /dev/null +++ b/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.hdmi; + +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; + +import com.google.common.testing.EqualsTester; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link DeviceFeatures} */ +@RunWith(JUnit4.class) +@SmallTest +public class DeviceFeaturesTest { + + @Test + public void testEquals() { + new EqualsTester() + .addEqualityGroup(DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN) + .addEqualityGroup(DeviceFeatures.NO_FEATURES_SUPPORTED) + .addEqualityGroup( + DeviceFeatures.fromOperand( + new byte[]{(byte) 0b0111_0000}), + DeviceFeatures.fromOperand( + new byte[]{(byte) 0b1111_0000}), + DeviceFeatures.fromOperand( + new byte[]{(byte) 0b1111_0000, (byte) 0b0101_0101}), + DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder() + .setRecordTvScreenSupport(FEATURE_SUPPORTED) + .setSetOsdStringSupport(FEATURE_SUPPORTED) + .setDeckControlSupport(FEATURE_SUPPORTED) + .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED) + .setArcTxSupport(FEATURE_NOT_SUPPORTED) + .setArcRxSupport(FEATURE_NOT_SUPPORTED) + .setSetAudioVolumeLevelSupport(FEATURE_NOT_SUPPORTED) + .build() + ) + .testEquals(); + } + + @Test + public void testDeviceFeaturesOperandConversion() { + DeviceFeatures info = DeviceFeatures.fromOperand( + new byte[]{(byte) 0b0111_0000}); + + assertThat(info.getRecordTvScreenSupport()).isEqualTo(FEATURE_SUPPORTED); + assertThat(info.getSetOsdStringSupport()).isEqualTo(FEATURE_SUPPORTED); + assertThat(info.getDeckControlSupport()).isEqualTo(FEATURE_SUPPORTED); + assertThat(info.getSetAudioRateSupport()).isEqualTo(FEATURE_NOT_SUPPORTED); + assertThat(info.getArcTxSupport()).isEqualTo(FEATURE_NOT_SUPPORTED); + assertThat(info.getArcRxSupport()).isEqualTo(FEATURE_NOT_SUPPORTED); + assertThat(info.getSetAudioVolumeLevelSupport()).isEqualTo(FEATURE_NOT_SUPPORTED); + + assertThat(info.toOperand()).isEqualTo(new byte[]{(byte) 0b0111_0000}); + } + + @Test + public void testUpdate() { + DeviceFeatures oldFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder() + .setRecordTvScreenSupport(FEATURE_SUPPORTED) + .setSetOsdStringSupport(FEATURE_SUPPORTED) + .setDeckControlSupport(FEATURE_NOT_SUPPORTED) + .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED) + .setArcTxSupport(FEATURE_SUPPORT_UNKNOWN) + .setArcRxSupport(FEATURE_SUPPORT_UNKNOWN) + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN) + .build(); + + DeviceFeatures newFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder() + .setRecordTvScreenSupport(FEATURE_NOT_SUPPORTED) + .setSetOsdStringSupport(FEATURE_SUPPORT_UNKNOWN) + .setDeckControlSupport(FEATURE_SUPPORTED) + .setSetAudioRateSupport(FEATURE_SUPPORT_UNKNOWN) + .setArcTxSupport(FEATURE_SUPPORTED) + .setArcRxSupport(FEATURE_NOT_SUPPORTED) + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN) + .build(); + + // Always take the field from newFeatures, unless it's FEATURE_SUPPORT_UNKNOWN + DeviceFeatures updatedFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder() + .setRecordTvScreenSupport(FEATURE_NOT_SUPPORTED) + .setSetOsdStringSupport(FEATURE_SUPPORTED) + .setDeckControlSupport(FEATURE_SUPPORTED) + .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED) + .setArcTxSupport(FEATURE_SUPPORTED) + .setArcRxSupport(FEATURE_NOT_SUPPORTED) + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN) + .build(); + + assertThat(oldFeatures.toBuilder().update(newFeatures).build()).isEqualTo(updatedFeatures); + } +} diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java index 4ce072ccc894..5f7468e084bf 100755 --- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java +++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java @@ -43,44 +43,32 @@ public class HdmiDeviceInfoTest { int adopterId = 2; new EqualsTester() - .addEqualityGroup(new HdmiDeviceInfo()) + .addEqualityGroup(HdmiDeviceInfo.INACTIVE_DEVICE) .addEqualityGroup( - new HdmiDeviceInfo(phyAddr, portId), new HdmiDeviceInfo(phyAddr, portId)) + HdmiDeviceInfo.hardwarePort(phyAddr, portId), + HdmiDeviceInfo.hardwarePort(phyAddr, portId)) .addEqualityGroup( - new HdmiDeviceInfo(phyAddr, portId, adopterId, deviceId), - new HdmiDeviceInfo(phyAddr, portId, adopterId, deviceId)) + HdmiDeviceInfo.mhlDevice(phyAddr, portId, adopterId, deviceId), + HdmiDeviceInfo.mhlDevice(phyAddr, portId, adopterId, deviceId)) .addEqualityGroup( - new HdmiDeviceInfo( - logicalAddr, phyAddr, portId, deviceType, vendorId, displayName), - new HdmiDeviceInfo( - logicalAddr, phyAddr, portId, deviceType, vendorId, displayName)) - .addEqualityGroup( - new HdmiDeviceInfo( - logicalAddr, - phyAddr, - portId, - deviceType, - vendorId, - displayName, - powerStatus), - new HdmiDeviceInfo( - logicalAddr, - phyAddr, - portId, - deviceType, - vendorId, - displayName, - powerStatus)) - .addEqualityGroup( - new HdmiDeviceInfo( - logicalAddr, - phyAddr, - portId, - deviceType, - vendorId, - displayName, - powerStatus, - cecVersion)) + HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(logicalAddr) + .setPhysicalAddress(phyAddr) + .setPortId(portId) + .setDeviceType(deviceType) + .setVendorId(vendorId) + .setDisplayName(displayName) + .setDevicePowerStatus(powerStatus) + .setCecVersion(cecVersion).build(), + HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(logicalAddr) + .setPhysicalAddress(phyAddr) + .setPortId(portId) + .setDeviceType(deviceType) + .setVendorId(vendorId) + .setDisplayName(displayName) + .setDevicePowerStatus(powerStatus) + .setCecVersion(cecVersion).build()) .testEquals(); } } diff --git a/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java b/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java deleted file mode 100644 index 182bf6d165a0..000000000000 --- a/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java +++ /dev/null @@ -1,168 +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 android.os; - -import android.app.PropertyInvalidatedCache; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -public class PropertyInvalidatedCacheTest extends TestCase { - private static final String KEY = "sys.testkey"; - private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; - - private static class TestCache extends PropertyInvalidatedCache<Integer, String> { - TestCache() { - this(KEY); - } - - TestCache(String key) { - super(4, key); - } - - @Override - public String recompute(Integer qv) { - mRecomputeCount += 1; - return "foo" + qv.toString(); - } - - int getRecomputeCount() { - return mRecomputeCount; - } - - private int mRecomputeCount = 0; - } - - @Override - protected void setUp() { - SystemProperties.set(KEY, ""); - } - - @SmallTest - public void testCacheRecompute() throws Exception { - TestCache cache = new TestCache(); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals(1, cache.getRecomputeCount()); - assertEquals("foo5", cache.query(5)); - assertEquals(1, cache.getRecomputeCount()); - assertEquals("foo6", cache.query(6)); - assertEquals(2, cache.getRecomputeCount()); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(3, cache.getRecomputeCount()); - } - - @SmallTest - public void testCacheInitialState() throws Exception { - TestCache cache = new TestCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(2, cache.getRecomputeCount()); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(3, cache.getRecomputeCount()); - } - - @SmallTest - public void testCachePropertyUnset() throws Exception { - TestCache cache = new TestCache(UNSET_KEY); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(2, cache.getRecomputeCount()); - } - - @SmallTest - public void testCacheDisableState() throws Exception { - TestCache cache = new TestCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(2, cache.getRecomputeCount()); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(3, cache.getRecomputeCount()); - cache.disableSystemWide(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(5, cache.getRecomputeCount()); - cache.invalidateCache(); // Should not reenable - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(7, cache.getRecomputeCount()); - } - - @SmallTest - public void testRefreshSameObject() throws Exception { - int[] refreshCount = new int[1]; - TestCache cache = new TestCache() { - @Override - protected String refresh(String oldResult, Integer query) { - refreshCount[0] += 1; - return oldResult; - } - }; - cache.invalidateCache(); - String result1 = cache.query(5); - assertEquals("foo5", result1); - String result2 = cache.query(5); - assertSame(result1, result2); - assertEquals(1, cache.getRecomputeCount()); - assertEquals(1, refreshCount[0]); - assertEquals("foo5", cache.query(5)); - assertEquals(2, refreshCount[0]); - } - - @SmallTest - public void testRefreshInvalidateRace() throws Exception { - int[] refreshCount = new int[1]; - TestCache cache = new TestCache() { - @Override - protected String refresh(String oldResult, Integer query) { - refreshCount[0] += 1; - invalidateCache(); - return new String(oldResult); - } - }; - cache.invalidateCache(); - String result1 = cache.query(5); - assertEquals("foo5", result1); - String result2 = cache.query(5); - assertEquals(result1, result2); - assertNotSame(result1, result2); - assertEquals(2, cache.getRecomputeCount()); - } - - @SmallTest - public void testLocalProcessDisable() throws Exception { - TestCache cache = new TestCache(); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals(1, cache.getRecomputeCount()); - assertEquals("foo5", cache.query(5)); - assertEquals(1, cache.getRecomputeCount()); - assertEquals(cache.isDisabledLocal(), false); - cache.disableLocal(); - assertEquals(cache.isDisabledLocal(), true); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(3, cache.getRecomputeCount()); - } - -} diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java index 50e8474e8d52..b659f37690a3 100644 --- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java @@ -21,13 +21,16 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; +import android.os.RemoteException; import android.os.UserManager; import android.provider.Settings; import android.test.mock.MockContentResolver; @@ -38,12 +41,16 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.widget.ILockSettings; +import com.android.internal.widget.IWeakEscrowTokenActivatedListener; +import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import java.nio.charset.StandardCharsets; + @RunWith(AndroidJUnit4.class) @SmallTest public class LockPatternUtilsTest { @@ -102,4 +109,84 @@ public class LockPatternUtilsTest { configureTest(false, true, 0); assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); } + + @Test + public void testAddWeakEscrowToken() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8); + int testUserId = 10; + IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener(); + mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener); + verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener)); + } + + @Test + public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener(); + mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener); + verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener)); + } + + @Test + public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener(); + mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener); + verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener)); + } + + @Test + public void testRemoveAutoEscrowToken() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + int testUserId = 10; + long testHandle = 100L; + mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId); + verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId)); + } + + @Test + public void testIsAutoEscrowTokenActive() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + int testUserId = 10; + long testHandle = 100L; + mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId); + verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId)); + } + + @Test + public void testIsAutoEscrowTokenValid() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + int testUserId = 10; + byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8); + long testHandle = 100L; + mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId); + verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId)); + } + + private ILockSettings createTestLockSettings() { + final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + mLockPatternUtils = spy(new LockPatternUtils(context)); + final ILockSettings ils = Mockito.mock(ILockSettings.class); + when(mLockPatternUtils.getLockSettings()).thenReturn(ils); + return ils; + } + + private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() { + return new IWeakEscrowTokenActivatedListener.Stub() { + @Override + public void onWeakEscrowTokenActivated(long handle, int userId) { + // Do nothing. + } + }; + } + + private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() { + return new IWeakEscrowTokenRemovedListener.Stub() { + @Override + public void onWeakEscrowTokenRemoved(long handle, int userId) { + // Do nothing. + } + }; + } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 878d302d481e..6f5951bdaca6 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -275,6 +275,7 @@ applications that come with the platform <privapp-permissions package="com.android.server.telecom"> <permission name="android.permission.BIND_CONNECTION_SERVICE"/> <permission name="android.permission.BIND_INCALL_SERVICE"/> + <permission name="android.permission.BLUETOOTH_PRIVILEGED"/> <permission name="android.permission.CALL_PRIVILEGED"/> <permission name="android.permission.HANDLE_CAR_MODE_CHANGES"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> @@ -391,6 +392,7 @@ applications that come with the platform <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/> <permission name="android.permission.SET_WALLPAPER" /> <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> + <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT" /> <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> <!-- Permissions required for Incremental CTS tests --> <permission name="com.android.permission.USE_INSTALLER_V2"/> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 9584994774a9..535d656462f4 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3691,6 +3691,18 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "1874559932": { + "message": "The TaskDisplayArea with %s does not exist.", + "level": "WARN", + "group": "WM_DEBUG_WINDOW_ORGANIZER", + "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java" + }, + "1884961873": { + "message": "Sleep still need to stop %d activities", + "level": "VERBOSE", + "group": "WM_DEBUG_STATES", + "at": "com\/android\/server\/wm\/Task.java" + }, "1891501279": { "message": "cancelAnimation(): reason=%s", "level": "DEBUG", diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index a612265793a3..425a37891afb 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -67,7 +67,7 @@ public abstract class BaseCanvas { * @hide */ protected int mDensity = Bitmap.DENSITY_NONE; - private boolean mAllowHwBitmapsInSwMode = false; + private boolean mAllowHwFeaturesInSwMode = false; protected void throwIfCannotDraw(Bitmap bitmap) { if (bitmap.isRecycled()) { @@ -101,14 +101,14 @@ public abstract class BaseCanvas { public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle, useCenter, paint.getNativeInstance()); } public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter, paint); } @@ -119,14 +119,14 @@ public abstract class BaseCanvas { public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) { throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity, bitmap.mDensity); } public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(), paint != null ? paint.getNativeInstance() : 0); } @@ -137,7 +137,7 @@ public abstract class BaseCanvas { throw new NullPointerException(); } throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); int left, top, right, bottom; @@ -163,7 +163,7 @@ public abstract class BaseCanvas { throw new NullPointerException(); } throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); float left, top, right, bottom; @@ -202,7 +202,7 @@ public abstract class BaseCanvas { || (lastScanline + width > length)) { throw new ArrayIndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); // quick escape if there's nothing to draw if (width == 0 || height == 0) { return; @@ -226,7 +226,7 @@ public abstract class BaseCanvas { if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) { throw new ArrayIndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (meshWidth == 0 || meshHeight == 0) { return; } @@ -243,7 +243,7 @@ public abstract class BaseCanvas { } public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance()); } @@ -275,23 +275,23 @@ public abstract class BaseCanvas { public void drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance()); } public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance()); } public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawLines(pts, 0, pts.length, paint); } public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance()); } @@ -299,18 +299,19 @@ public abstract class BaseCanvas { if (oval == null) { throw new NullPointerException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawOval(oval.left, oval.top, oval.right, oval.bottom, paint); } public void drawPaint(@NonNull Paint paint) { + throwIfHasHwFeaturesInSwMode(paint); nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance()); } public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint, @@ -320,7 +321,7 @@ public abstract class BaseCanvas { public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint, @@ -328,7 +329,7 @@ public abstract class BaseCanvas { } public void drawPath(@NonNull Path path, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (path.isSimplePath && path.rects != null) { nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance()); } else { @@ -337,18 +338,18 @@ public abstract class BaseCanvas { } public void drawPoint(float x, float y, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance()); } public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance()); } public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawPoints(pts, 0, pts.length, paint); } @@ -359,7 +360,7 @@ public abstract class BaseCanvas { if (index < 0 || index + count > text.length || count * 2 > pos.length) { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); for (int i = 0; i < count; i++) { drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint); } @@ -368,22 +369,22 @@ public abstract class BaseCanvas { @Deprecated public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawPosText(text.toCharArray(), 0, text.length(), pos, paint); } public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance()); } public void drawRect(@NonNull Rect r, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawRect(r.left, r.top, r.right, r.bottom, paint); } public void drawRect(@NonNull RectF rect, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance()); } @@ -394,13 +395,13 @@ public abstract class BaseCanvas { public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, paint.getNativeInstance()); } public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint); } @@ -410,7 +411,7 @@ public abstract class BaseCanvas { */ public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy, @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); float outerLeft = outer.left; float outerTop = outer.top; float outerRight = outer.right; @@ -431,7 +432,7 @@ public abstract class BaseCanvas { */ public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii, @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (innerRadii == null || outerRadii == null || innerRadii.length != 8 || outerRadii.length != 8) { throw new IllegalArgumentException("Both inner and outer radii arrays must contain " @@ -509,7 +510,7 @@ public abstract class BaseCanvas { (text.length - index - count)) < 0) { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags, paint.getNativeInstance()); } @@ -519,7 +520,7 @@ public abstract class BaseCanvas { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y, @@ -537,7 +538,7 @@ public abstract class BaseCanvas { } public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags, paint.getNativeInstance()); } @@ -547,7 +548,7 @@ public abstract class BaseCanvas { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags, paint.getNativeInstance()); } @@ -557,7 +558,7 @@ public abstract class BaseCanvas { if (index < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawTextOnPath(mNativeCanvasWrapper, text, index, count, path.readOnlyNI(), hOffset, vOffset, paint.mBidiFlags, paint.getNativeInstance()); @@ -566,7 +567,7 @@ public abstract class BaseCanvas { public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset, float vOffset, @NonNull Paint paint) { if (text.length() > 0) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset, paint.mBidiFlags, paint.getNativeInstance()); } @@ -587,7 +588,7 @@ public abstract class BaseCanvas { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount, x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */); } @@ -606,7 +607,7 @@ public abstract class BaseCanvas { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart, @@ -664,7 +665,7 @@ public abstract class BaseCanvas { if (indices != null) { checkRange(indices.length, indexOffset, indexCount); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts, vertOffset, texs, texOffset, colors, colorOffset, indices, indexOffset, indexCount, paint.getNativeInstance()); @@ -680,50 +681,52 @@ public abstract class BaseCanvas { /** * @hide */ - public void setHwBitmapsInSwModeEnabled(boolean enabled) { - mAllowHwBitmapsInSwMode = enabled; + public void setHwFeaturesInSwModeEnabled(boolean enabled) { + mAllowHwFeaturesInSwMode = enabled; } /** * @hide */ - public boolean isHwBitmapsInSwModeEnabled() { - return mAllowHwBitmapsInSwMode; + public boolean isHwFeaturesInSwModeEnabled() { + return mAllowHwFeaturesInSwMode; } /** + * If true throw an exception * @hide */ - protected void onHwBitmapInSwMode() { - if (!mAllowHwBitmapsInSwMode) { - throw new IllegalArgumentException( - "Software rendering doesn't support hardware bitmaps"); - } + protected boolean onHwFeatureInSwMode() { + return !mAllowHwFeaturesInSwMode; } private void throwIfHwBitmapInSwMode(Bitmap bitmap) { - if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) { - onHwBitmapInSwMode(); + if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE + && onHwFeatureInSwMode()) { + throw new IllegalArgumentException( + "Software rendering doesn't support hardware bitmaps"); } } - private void throwIfHasHwBitmapInSwMode(Paint p) { + private void throwIfHasHwFeaturesInSwMode(Paint p) { if (isHardwareAccelerated() || p == null) { return; } - throwIfHasHwBitmapInSwMode(p.getShader()); + throwIfHasHwFeaturesInSwMode(p.getShader()); } - private void throwIfHasHwBitmapInSwMode(Shader shader) { + private void throwIfHasHwFeaturesInSwMode(Shader shader) { if (shader == null) { return; } if (shader instanceof BitmapShader) { throwIfHwBitmapInSwMode(((BitmapShader) shader).mBitmap); - } - if (shader instanceof ComposeShader) { - throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderA); - throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderB); + } else if (shader instanceof RuntimeShader && onHwFeatureInSwMode()) { + throw new IllegalArgumentException( + "Software rendering doesn't support RuntimeShader"); + } else if (shader instanceof ComposeShader) { + throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderA); + throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderB); } } diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index e6ff187bd99d..43cb5ee8b5c0 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -16,8 +16,12 @@ package android.graphics; +import android.annotation.IntDef; import android.annotation.NonNull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Shader used to draw a bitmap as a texture. The bitmap can be repeated or * mirrored by setting the tiling mode. @@ -31,6 +35,47 @@ public class BitmapShader extends Shader { private int mTileX; private int mTileY; + /** @hide */ + @IntDef(prefix = {"FILTER_MODE"}, value = { + FILTER_MODE_DEFAULT, + FILTER_MODE_NEAREST, + FILTER_MODE_LINEAR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FilterMode {} + + /** + * This FilterMode value will respect the value of the Paint#isFilterBitmap flag while the + * shader is attached to the Paint. + * + * <p>The exception to this rule is when a Shader is attached as input to a RuntimeShader. In + * that case this mode will default to FILTER_MODE_NEAREST.</p> + * + * @see #setFilterMode(int) + */ + public static final int FILTER_MODE_DEFAULT = 0; + /** + * This FilterMode value will cause the shader to sample from the nearest pixel to the requested + * sample point. + * + * <p>This value will override the effect of Paint#isFilterBitmap.</p> + * + * @see #setFilterMode(int) + */ + public static final int FILTER_MODE_NEAREST = 1; + /** + * This FilterMode value will cause the shader to interpolate the output of the shader from a + * 2x2 grid of pixels nearest to the sample point (i.e. bilinear interpolation). + * + * <p>This value will override the effect of Paint#isFilterBitmap.</p> + * + * @see #setFilterMode(int) + */ + public static final int FILTER_MODE_LINEAR = 2; + + @FilterMode + private int mFilterMode; + /* * This is cache of the last value from the Paint of bitmap-filtering. * In the future, BitmapShaders will carry their own (expanded) data for this @@ -49,6 +94,15 @@ public class BitmapShader extends Shader { private boolean mFilterFromPaint; /** + * Stores whether or not the contents of this shader's bitmap will be sampled + * without modification or if the bitmap's properties, like colorspace and + * premultiplied alpha, will be respected when sampling from the bitmap's buffer. + */ + private boolean mIsDirectSampled; + + private boolean mRequestDirectSampling; + + /** * Call this to create a new shader that will draw with a bitmap. * * @param bitmap The bitmap to use inside the shader @@ -66,24 +120,60 @@ public class BitmapShader extends Shader { mBitmap = bitmap; mTileX = tileX; mTileY = tileY; + mFilterMode = FILTER_MODE_DEFAULT; mFilterFromPaint = false; + mIsDirectSampled = false; + mRequestDirectSampling = false; + } + + /** + * Returns the filter mode used when sampling from this shader + */ + @FilterMode + public int getFilterMode() { + return mFilterMode; + } + + /** + * Set the filter mode to be used when sampling from this shader + */ + public void setFilterMode(@FilterMode int mode) { + if (mode != mFilterMode) { + mFilterMode = mode; + discardNativeInstance(); + } + } + + /** @hide */ + /* package */ synchronized long getNativeInstanceWithDirectSampling() { + mRequestDirectSampling = true; + return getNativeInstance(); } /** @hide */ @Override protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) { - mFilterFromPaint = filterFromPaint; + boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR; + if (mFilterMode == FILTER_MODE_DEFAULT) { + mFilterFromPaint = filterFromPaint; + enableLinearFilter = mFilterFromPaint; + } + + mIsDirectSampled = mRequestDirectSampling; + mRequestDirectSampling = false; + return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY, - mFilterFromPaint); + enableLinearFilter, mIsDirectSampled); } /** @hide */ @Override protected boolean shouldDiscardNativeInstance(boolean filterFromPaint) { - return mFilterFromPaint != filterFromPaint; + return mIsDirectSampled != mRequestDirectSampling + || (mFilterMode == FILTER_MODE_DEFAULT && mFilterFromPaint != filterFromPaint); } private static native long nativeCreate(long nativeMatrix, long bitmapHandle, - int shaderTileModeX, int shaderTileModeY, boolean filter); + int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 42e470b7f660..eefad8d0e4de 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -46,6 +46,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Locale; +import java.util.Objects; /** * The Paint class holds the style and color information about how to draw @@ -2131,6 +2132,116 @@ public class Paint { } /** + * Returns the font metrics value for the given text. + * + * If the text is rendered with multiple font files, this function returns the large ascent and + * descent that are enough for drawing all font files. + * + * The context range is used for shaping context. Some script, e.g. Arabic or Devanagari, + * changes letter shape based on its location or surrounding characters. + * + * @param text a text to be measured. + * @param start a starting offset in the text. + * @param count a length of the text to be measured. + * @param contextStart a context starting offset in the text. + * @param contextCount a length of the context to be used. + * @param isRtl true if measuring on RTL context, otherwise false. + * @param outMetrics the output font metrics. + */ + public void getFontMetricsInt( + @NonNull CharSequence text, + @IntRange(from = 0) int start, @IntRange(from = 0) int count, + @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount, + boolean isRtl, + @NonNull FontMetricsInt outMetrics) { + + if (text == null) { + throw new IllegalArgumentException("text must not be null"); + } + if (start < 0 || start >= text.length()) { + throw new IllegalArgumentException("start argument is out of bounds."); + } + if (count < 0 || start + count > text.length()) { + throw new IllegalArgumentException("count argument is out of bounds."); + } + if (contextStart < 0 || contextStart >= text.length()) { + throw new IllegalArgumentException("ctxStart argument is out of bounds."); + } + if (contextCount < 0 || contextStart + contextCount > text.length()) { + throw new IllegalArgumentException("ctxCount argument is out of bounds."); + } + if (outMetrics == null) { + throw new IllegalArgumentException("outMetrics must not be null."); + } + + if (count == 0) { + getFontMetricsInt(outMetrics); + return; + } + + if (text instanceof String) { + nGetFontMetricsIntForText(mNativePaint, (String) text, start, count, contextStart, + contextCount, isRtl, outMetrics); + } else { + char[] buf = TemporaryBuffer.obtain(contextCount); + TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0); + nGetFontMetricsIntForText(mNativePaint, buf, start - contextStart, count, 0, + contextCount, isRtl, outMetrics); + } + + } + + /** + * Returns the font metrics value for the given text. + * + * If the text is rendered with multiple font files, this function returns the large ascent and + * descent that are enough for drawing all font files. + * + * The context range is used for shaping context. Some script, e.g. Arabic or Devanagari, + * changes letter shape based on its location or surrounding characters. + * + * @param text a text to be measured. + * @param start a starting offset in the text. + * @param count a length of the text to be measured. + * @param contextStart a context starting offset in the text. + * @param contextCount a length of the context to be used. + * @param isRtl true if measuring on RTL context, otherwise false. + * @param outMetrics the output font metrics. + */ + public void getFontMetricsInt(@NonNull char[] text, + @IntRange(from = 0) int start, @IntRange(from = 0) int count, + @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount, + boolean isRtl, + @NonNull FontMetricsInt outMetrics) { + if (text == null) { + throw new IllegalArgumentException("text must not be null"); + } + if (start < 0 || start >= text.length) { + throw new IllegalArgumentException("start argument is out of bounds."); + } + if (count < 0 || start + count > text.length) { + throw new IllegalArgumentException("count argument is out of bounds."); + } + if (contextStart < 0 || contextStart >= text.length) { + throw new IllegalArgumentException("ctxStart argument is out of bounds."); + } + if (contextCount < 0 || contextStart + contextCount > text.length) { + throw new IllegalArgumentException("ctxCount argument is out of bounds."); + } + if (outMetrics == null) { + throw new IllegalArgumentException("outMetrics must not be null."); + } + + if (count == 0) { + getFontMetricsInt(outMetrics); + return; + } + + nGetFontMetricsIntForText(mNativePaint, text, start, count, contextStart, contextCount, + isRtl, outMetrics); + } + + /** * Convenience method for callers that want to have FontMetrics values as * integers. */ @@ -2163,6 +2274,23 @@ public class Paint { " descent=" + descent + " bottom=" + bottom + " leading=" + leading; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FontMetricsInt)) return false; + FontMetricsInt that = (FontMetricsInt) o; + return top == that.top + && ascent == that.ascent + && descent == that.descent + && bottom == that.bottom + && leading == that.leading; + } + + @Override + public int hashCode() { + return Objects.hash(top, ascent, descent, bottom, leading); + } } /** @@ -3117,6 +3245,13 @@ public class Paint { int contextStart, int contextEnd, boolean isRtl, int offset); private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance); + private static native void nGetFontMetricsIntForText(long paintPtr, char[] text, + int start, int count, int ctxStart, int ctxCount, boolean isRtl, + FontMetricsInt outMetrics); + private static native void nGetFontMetricsIntForText(long paintPtr, String text, + int start, int count, int ctxStart, int ctxCount, boolean isRtl, + FontMetricsInt outMetrics); + // ---------------- @FastNative ------------------------ @@ -3130,7 +3265,6 @@ public class Paint { @FastNative private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi); - // ---------------- @CriticalNative ------------------------ @CriticalNative diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index 390d3d414346..ee4165b8da05 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -124,7 +124,7 @@ public class Picture { public void endRecording() { verifyValid(); if (mRecordingCanvas != null) { - mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap; + mRequiresHwAcceleration = mRecordingCanvas.mUsesHwFeature; mRecordingCanvas = null; nativeEndRecording(mNativePicture); } @@ -182,8 +182,10 @@ public class Picture { if (mRecordingCanvas != null) { endRecording(); } - if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) { - canvas.onHwBitmapInSwMode(); + if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated() + && canvas.onHwFeatureInSwMode()) { + throw new IllegalArgumentException("Software rendering not supported for Pictures that" + + " require hardware acceleration"); } nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture); } @@ -242,7 +244,7 @@ public class Picture { private static class PictureCanvas extends Canvas { private final Picture mPicture; - boolean mHoldsHwBitmap; + boolean mUsesHwFeature; public PictureCanvas(Picture pict, long nativeCanvas) { super(nativeCanvas); @@ -265,8 +267,9 @@ public class Picture { } @Override - protected void onHwBitmapInSwMode() { - mHoldsHwBitmap = true; + protected boolean onHwFeatureInSwMode() { + mUsesHwFeature = true; + return false; } } } diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index ef57f4a76007..57046f5de61d 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -279,11 +279,11 @@ public class RuntimeShader extends Shader { } /** - * Sets the uniform shader that is declares as input to this shader. If the shader does not + * Assigns the uniform shader to the provided shader parameter. If the shader program does not * have a uniform shader with that name then an IllegalArgumentException is thrown. * - * @param shaderName name matching the uniform declared in the SKSL shader - * @param shader shader passed into the SKSL shader for sampling + * @param shaderName name matching the uniform declared in the AGSL shader program + * @param shader shader passed into the AGSL shader program for sampling */ public void setInputShader(@NonNull String shaderName, @NonNull Shader shader) { if (shaderName == null) { @@ -297,6 +297,28 @@ public class RuntimeShader extends Shader { discardNativeInstance(); } + /** + * Assigns the uniform shader to the provided shader parameter. If the shader program does not + * have a uniform shader with that name then an IllegalArgumentException is thrown. + * + * Unlike setInputShader this method returns samples directly from the bitmap's buffer. This + * means that there will be no transformation of the sampled pixels, such as colorspace + * conversion or alpha premultiplication. + */ + public void setInputBuffer(@NonNull String shaderName, @NonNull BitmapShader shader) { + if (shaderName == null) { + throw new NullPointerException("The shaderName parameter must not be null"); + } + if (shader == null) { + throw new NullPointerException("The shader parameter must not be null"); + } + + nativeUpdateShader(mNativeInstanceRuntimeShaderBuilder, shaderName, + shader.getNativeInstanceWithDirectSampling()); + discardNativeInstance(); + } + + /** @hide */ @Override protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) { diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index b843589376c4..ffaa4ea51452 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -868,7 +868,7 @@ public class RippleDrawable extends LayerDrawable { private void drawPatterned(@NonNull Canvas canvas) { final Rect bounds = mHotspotBounds; final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); - boolean useCanvasProps = shouldUseCanvasProps(canvas); + boolean useCanvasProps = !mForceSoftware; if (isBounded()) { canvas.clipRect(getDirtyBounds()); } @@ -914,7 +914,11 @@ public class RippleDrawable extends LayerDrawable { } for (int i = 0; i < mRunningAnimations.size(); i++) { RippleAnimationSession s = mRunningAnimations.get(i); - if (useCanvasProps) { + if (!canvas.isHardwareAccelerated()) { + Log.e(TAG, "The RippleDrawable.STYLE_PATTERNED animation is not supported for a " + + "non-hardware accelerated Canvas. Skipping animation."); + break; + } else if (useCanvasProps) { RippleAnimationSession.AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> p = s.getCanvasProperties(); @@ -1002,10 +1006,6 @@ public class RippleDrawable extends LayerDrawable { return color; } - private boolean shouldUseCanvasProps(Canvas c) { - return !mForceSoftware && c.isHardwareAccelerated(); - } - @Override public void invalidateSelf() { invalidateSelf(true); diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java new file mode 100644 index 000000000000..4d818583fd23 --- /dev/null +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -0,0 +1,110 @@ +/* + * 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 android.graphics.text; + +import android.annotation.IntDef; +import android.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Indicates the strategies can be used when calculating the text wrapping. + * + * See <a href="https://drafts.csswg.org/css-text/#line-break-property">the line-break property</a> + */ +public final class LineBreakConfig { + + /** + * No line break style specified. + */ + public static final int LINE_BREAK_STYLE_NONE = 0; + + /** + * Use the least restrictive rule for line-breaking. This is usually used for short lines. + */ + public static final int LINE_BREAK_STYLE_LOOSE = 1; + + /** + * Indicate breaking text with the most comment set of line-breaking rules. + */ + public static final int LINE_BREAK_STYLE_NORMAL = 2; + + /** + * Indicates breaking text with the most strictest line-breaking rules. + */ + public static final int LINE_BREAK_STYLE_STRICT = 3; + + /** @hide */ + @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = { + LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL, + LINE_BREAK_STYLE_STRICT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LineBreakStyle {} + + private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE; + + public LineBreakConfig() { + } + + /** + * Set the line break configuration. + * + * @param config the new line break configuration. + */ + public void set(@Nullable LineBreakConfig config) { + if (config != null) { + mLineBreakStyle = config.getLineBreakStyle(); + } else { + mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE; + } + } + + /** + * Get the line break style. + * + * @return The current line break style to be used for the text wrapping. + */ + public @LineBreakStyle int getLineBreakStyle() { + return mLineBreakStyle; + } + + /** + * Set the line break style. + * + * @param lineBreakStyle the new line break style. + */ + public void setLineBreakStyle(@LineBreakStyle int lineBreakStyle) { + mLineBreakStyle = lineBreakStyle; + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (this == o) return true; + if (!(o instanceof LineBreakConfig)) return false; + LineBreakConfig that = (LineBreakConfig) o; + return mLineBreakStyle == that.mLineBreakStyle; + } + + @Override + public int hashCode() { + return Objects.hash(mLineBreakStyle); + } +} diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index a34d0abcf753..5f4afb7b9888 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -239,11 +239,33 @@ public class MeasuredText { */ public @NonNull Builder appendStyleRun(@NonNull Paint paint, @IntRange(from = 0) int length, boolean isRtl) { + return appendStyleRun(paint, null, length, isRtl); + } + + /** + * Apply styles to the given length. + * + * Keeps an internal offset which increases at every append. The initial value for this + * offset is zero. After the style is applied the internal offset is moved to {@code offset + * + length}, and next call will start from this new position. + * + * @param paint a paint + * @param lineBreakConfig a line break configuration. + * @param length a length to be applied with a given paint, can not exceed the length of the + * text + * @param isRtl true if the text is in RTL context, otherwise false. + */ + public @NonNull Builder appendStyleRun(@NonNull Paint paint, + @Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length, + boolean isRtl) { Preconditions.checkNotNull(paint); Preconditions.checkArgument(length > 0, "length can not be negative"); final int end = mCurrentOffset + length; Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length"); - nAddStyleRun(mNativePtr, paint.getNativeInstance(), mCurrentOffset, end, isRtl); + int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() : + LineBreakConfig.LINE_BREAK_STYLE_NONE; + nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, mCurrentOffset, end, + isRtl); mCurrentOffset = end; return this; } @@ -423,12 +445,14 @@ public class MeasuredText { * * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. * @param paintPtr The native paint pointer to be applied. + * @param lineBreakStyle The line break style of the text. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param isRtl True if the text is RTL. */ private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, + int lineBreakStyle, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); diff --git a/identity/java/android/security/identity/CredentialDataRequest.java b/identity/java/android/security/identity/CredentialDataRequest.java new file mode 100644 index 000000000000..2a47a02405e0 --- /dev/null +++ b/identity/java/android/security/identity/CredentialDataRequest.java @@ -0,0 +1,232 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * An object representing a request for credential data. + */ +public class CredentialDataRequest { + CredentialDataRequest() {} + + /** + * Gets the device-signed entries to request. + * + * @return the device-signed entries to request. + */ + public @NonNull Map<String, Collection<String>> getDeviceSignedEntriesToRequest() { + return mDeviceSignedEntriesToRequest; + } + + /** + * Gets the issuer-signed entries to request. + * + * @return the issuer-signed entries to request. + */ + public @NonNull Map<String, Collection<String>> getIssuerSignedEntriesToRequest() { + return mIssuerSignedEntriesToRequest; + } + + /** + * Gets whether to allow using an authentication key which use count has been exceeded. + * + * <p>By default this is set to true. + * + * @return whether to allow using an authentication key which use + * count has been exceeded if no other key is available. + */ + public boolean isAllowUsingExhaustedKeys() { + return mAllowUsingExhaustedKeys; + } + + /** + * Gets whether to allow using an authentication key which is expired. + * + * <p>By default this is set to false. + * + * @return whether to allow using an authentication key which is + * expired if no other key is available. + */ + public boolean isAllowUsingExpiredKeys() { + return mAllowUsingExpiredKeys; + } + + /** + * Gets whether to increment the use-count for the authentication key used. + * + * <p>By default this is set to true. + * + * @return whether to increment the use count of the authentication key used. + */ + public boolean isIncrementUseCount() { + return mIncrementUseCount; + } + + /** + * Gets the request message CBOR. + * + * <p>This data structure is described in the documentation for the + * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method. + * + * @return the request message CBOR as described above. + */ + public @Nullable byte[] getRequestMessage() { + return mRequestMessage; + } + + /** + * Gets the reader signature. + * + * <p>This data structure is described in the documentation for the + * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method. + * + * @return a {@code COSE_Sign1} structure as described above. + */ + public @Nullable byte[] getReaderSignature() { + return mReaderSignature; + } + + Map<String, Collection<String>> mDeviceSignedEntriesToRequest = new LinkedHashMap<>(); + Map<String, Collection<String>> mIssuerSignedEntriesToRequest = new LinkedHashMap<>(); + boolean mAllowUsingExhaustedKeys = true; + boolean mAllowUsingExpiredKeys = false; + boolean mIncrementUseCount = true; + byte[] mRequestMessage = null; + byte[] mReaderSignature = null; + + /** + * A builder for {@link CredentialDataRequest}. + */ + public static final class Builder { + private CredentialDataRequest mData; + + /** + * Creates a new builder. + */ + public Builder() { + mData = new CredentialDataRequest(); + } + + /** + * Sets the device-signed entries to request. + * + * @param entriesToRequest the device-signed entries to request. + */ + public @NonNull Builder setDeviceSignedEntriesToRequest( + @NonNull Map<String, Collection<String>> entriesToRequest) { + mData.mDeviceSignedEntriesToRequest = entriesToRequest; + return this; + } + + /** + * Sets the issuer-signed entries to request. + * + * @param entriesToRequest the issuer-signed entries to request. + * @return the builder. + */ + public @NonNull Builder setIssuerSignedEntriesToRequest( + @NonNull Map<String, Collection<String>> entriesToRequest) { + mData.mIssuerSignedEntriesToRequest = entriesToRequest; + return this; + } + + /** + * Sets whether to allow using an authentication key which use count has been exceeded. + * + * By default this is set to true. + * + * @param allowUsingExhaustedKeys whether to allow using an authentication key which use + * count has been exceeded if no other key is available. + * @return the builder. + */ + public @NonNull Builder setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) { + mData.mAllowUsingExhaustedKeys = allowUsingExhaustedKeys; + return this; + } + + /** + * Sets whether to allow using an authentication key which is expired. + * + * By default this is set to false. + * + * @param allowUsingExpiredKeys whether to allow using an authentication key which is + * expired if no other key is available. + * @return the builder. + */ + public @NonNull Builder setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) { + mData.mAllowUsingExpiredKeys = allowUsingExpiredKeys; + return this; + } + + /** + * Sets whether to increment the use-count for the authentication key used. + * + * By default this is set to true. + * + * @param incrementUseCount whether to increment the use count of the authentication + * key used. + * @return the builder. + */ + public @NonNull Builder setIncrementUseCount(boolean incrementUseCount) { + mData.mIncrementUseCount = incrementUseCount; + return this; + } + + /** + * Sets the request message CBOR. + * + * <p>This data structure is described in the documentation for the + * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method. + * + * @param requestMessage the request message CBOR as described above. + * @return the builder. + */ + public @NonNull Builder setRequestMessage(@NonNull byte[] requestMessage) { + mData.mRequestMessage = requestMessage; + return this; + } + + /** + * Sets the reader signature. + * + * <p>This data structure is described in the documentation for the + * {@link PresentationSession#getCredentialData(String, CredentialDataRequest)} method. + * + * @param readerSignature a {@code COSE_Sign1} structure as described above. + * @return the builder. + */ + public @NonNull Builder setReaderSignature(@NonNull byte[] readerSignature) { + mData.mReaderSignature = readerSignature; + return this; + } + + /** + * Finishes building a {@link CredentialDataRequest}. + * + * @return the {@link CredentialDataRequest} object. + */ + public @NonNull CredentialDataRequest build() { + return mData; + } + } +} diff --git a/identity/java/android/security/identity/CredentialDataResult.java b/identity/java/android/security/identity/CredentialDataResult.java new file mode 100644 index 000000000000..beb03af46303 --- /dev/null +++ b/identity/java/android/security/identity/CredentialDataResult.java @@ -0,0 +1,232 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.util.Collection; + + +/** + * An object that contains the result of retrieving data from a credential. This is used to return + * data requested in a {@link PresentationSession}. + */ +public abstract class CredentialDataResult { + /** + * @hide + */ + protected CredentialDataResult() {} + + /** + * Returns a CBOR structure containing the retrieved device-signed data. + * + * <p>This structure - along with the session transcript - may be cryptographically + * authenticated to prove to the reader that the data is from a trusted credential and + * {@link #getDeviceMac()} can be used to get a MAC. + * + * <p>The CBOR structure which is cryptographically authenticated is the + * {@code DeviceAuthenticationBytes} structure according to the following + * <a href="https://tools.ietf.org/html/rfc8610">CDDL</a> schema: + * + * <pre> + * DeviceAuthentication = [ + * "DeviceAuthentication", + * SessionTranscript, + * DocType, + * DeviceNameSpacesBytes + * ] + * + * DocType = tstr + * SessionTranscript = any + * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) + * DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication) + * </pre> + * + * <p>where + * + * <pre> + * DeviceNameSpaces = { + * * NameSpace => DeviceSignedItems + * } + * + * DeviceSignedItems = { + * + DataItemName => DataItemValue + * } + * + * NameSpace = tstr + * DataItemName = tstr + * DataItemValue = any + * </pre> + * + * <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure + * as defined above. + * + * @return The bytes of the {@code DeviceNameSpaces} CBOR structure. + */ + public abstract @NonNull byte[] getDeviceNameSpaces(); + + /** + * Returns a message authentication code over the {@code DeviceAuthenticationBytes} CBOR + * specified in {@link #getDeviceNameSpaces()}, to prove to the reader that the data + * is from a trusted credential. + * + * <p>The MAC proves to the reader that the data is from a trusted credential. This code is + * produced by using the key agreement and key derivation function from the ciphersuite + * with the authentication private key and the reader ephemeral public key to compute a + * shared message authentication code (MAC) key, then using the MAC function from the + * ciphersuite to compute a MAC of the authenticated data. See section 9.2.3.5 of + * ISO/IEC 18013-5 for details of this operation. + * + * <p>If the session transcript or reader ephemeral key wasn't set on the {@link + * PresentationSession} used to obtain this data no message authencation code will be produced + * and this method will return {@code null}. + * + * @return A COSE_Mac0 structure with the message authentication code as described above + * or {@code null} if the conditions specified above are not met. + */ + public abstract @Nullable byte[] getDeviceMac(); + + /** + * Returns the static authentication data associated with the dynamic authentication + * key used to MAC the data returned by {@link #getDeviceNameSpaces()}. + * + * @return The static authentication data associated with dynamic authentication key used to + * MAC the data. + */ + public abstract @NonNull byte[] getStaticAuthenticationData(); + + /** + * Gets the device-signed entries that was returned. + * + * @return an object to examine the entries returned. + */ + public abstract @NonNull Entries getDeviceSignedEntries(); + + /** + * Gets the issuer-signed entries that was returned. + * + * @return an object to examine the entries returned. + */ + public abstract @NonNull Entries getIssuerSignedEntries(); + + /** + * A class for representing data elements returned. + */ + public interface Entries { + /** Value was successfully retrieved. */ + int STATUS_OK = 0; + + /** The entry does not exist. */ + int STATUS_NO_SUCH_ENTRY = 1; + + /** The entry was not requested. */ + int STATUS_NOT_REQUESTED = 2; + + /** The entry wasn't in the request message. */ + int STATUS_NOT_IN_REQUEST_MESSAGE = 3; + + /** The entry was not retrieved because user authentication failed. */ + int STATUS_USER_AUTHENTICATION_FAILED = 4; + + /** The entry was not retrieved because reader authentication failed. */ + int STATUS_READER_AUTHENTICATION_FAILED = 5; + + /** + * The entry was not retrieved because it was configured without any access + * control profile. + */ + int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; + + /** + * Gets the names of namespaces with retrieved entries. + * + * @return collection of name of namespaces containing retrieved entries. May be empty if no + * data was retrieved. + */ + @NonNull Collection<String> getNamespaces(); + + /** + * Get the names of all requested entries in a name space. + * + * <p>This includes the name of entries that wasn't successfully retrieved. + * + * @param namespaceName the namespace name to get entries for. + * @return A collection of names for the given namespace or the empty collection if no + * entries was returned for the given name space. + */ + @NonNull Collection<String> getEntryNames(@NonNull String namespaceName); + + /** + * Get the names of all entries that was successfully retrieved from a name space. + * + * <p>This only return entries for which {@link #getStatus(String, String)} will return + * {@link #STATUS_OK}. + * + * @param namespaceName the namespace name to get entries for. + * @return The entries in the given namespace that were successfully rerieved or the + * empty collection if no entries was returned for the given name space. + */ + @NonNull Collection<String> getRetrievedEntryNames(@NonNull String namespaceName); + + /** + * Gets the status of an entry. + * + * <p>This returns {@link #STATUS_OK} if the value was retrieved, {@link + * #STATUS_NO_SUCH_ENTRY} if the given entry wasn't retrieved, {@link + * #STATUS_NOT_REQUESTED} if it wasn't requested, {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if + * the request message was set but the entry wasn't present in the request message, {@link + * #STATUS_USER_AUTHENTICATION_FAILED} if the value wasn't retrieved because the necessary + * user authentication wasn't performed, {@link #STATUS_READER_AUTHENTICATION_FAILED} if + * the supplied reader certificate chain didn't match the set of certificates the entry was + * provisioned with, or {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was + * configured without any access control profiles. + * + * @param namespaceName the namespace name of the entry. + * @param name the name of the entry to get the value for. + * @return the status indicating whether the value was retrieved and if not, why. + */ + @Status int getStatus(@NonNull String namespaceName, @NonNull String name); + + /** + * Gets the raw CBOR data for the value of an entry. + * + * <p>This should only be called on an entry for which the {@link #getStatus(String, + * String)} method returns {@link #STATUS_OK}. + * + * @param namespaceName the namespace name of the entry. + * @param name the name of the entry to get the value for. + * @return the raw CBOR data or {@code null} if no entry with the given name exists. + */ + @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name); + + /** + * The type of the entry status. + * @hide + */ + @Retention(SOURCE) + @IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED, + STATUS_NOT_IN_REQUEST_MESSAGE, STATUS_USER_AUTHENTICATION_FAILED, + STATUS_READER_AUTHENTICATION_FAILED, STATUS_NO_ACCESS_CONTROL_PROFILES}) + @interface Status {} + } + +} diff --git a/identity/java/android/security/identity/CredstoreCredentialDataResult.java b/identity/java/android/security/identity/CredstoreCredentialDataResult.java new file mode 100644 index 000000000000..7afe3d448bf9 --- /dev/null +++ b/identity/java/android/security/identity/CredstoreCredentialDataResult.java @@ -0,0 +1,106 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.Collection; +import java.util.LinkedList; + +class CredstoreCredentialDataResult extends CredentialDataResult { + + ResultData mDeviceSignedResult; + ResultData mIssuerSignedResult; + CredstoreEntries mDeviceSignedEntries; + CredstoreEntries mIssuerSignedEntries; + + CredstoreCredentialDataResult(ResultData deviceSignedResult, ResultData issuerSignedResult) { + mDeviceSignedResult = deviceSignedResult; + mIssuerSignedResult = issuerSignedResult; + mDeviceSignedEntries = new CredstoreEntries(deviceSignedResult); + mIssuerSignedEntries = new CredstoreEntries(issuerSignedResult); + } + + @Override + public @NonNull byte[] getDeviceNameSpaces() { + return mDeviceSignedResult.getAuthenticatedData(); + } + + @Override + public @Nullable byte[] getDeviceMac() { + return mDeviceSignedResult.getMessageAuthenticationCode(); + } + + @Override + public @NonNull byte[] getStaticAuthenticationData() { + return mDeviceSignedResult.getStaticAuthenticationData(); + } + + @Override + public @NonNull CredentialDataResult.Entries getDeviceSignedEntries() { + return mDeviceSignedEntries; + } + + @Override + public @NonNull CredentialDataResult.Entries getIssuerSignedEntries() { + return mIssuerSignedEntries; + } + + static class CredstoreEntries implements CredentialDataResult.Entries { + ResultData mResultData; + + CredstoreEntries(ResultData resultData) { + mResultData = resultData; + } + + @Override + public @NonNull Collection<String> getNamespaces() { + return mResultData.getNamespaces(); + } + + @Override + public @NonNull Collection<String> getEntryNames(@NonNull String namespaceName) { + Collection<String> ret = mResultData.getEntryNames(namespaceName); + if (ret == null) { + ret = new LinkedList<String>(); + } + return ret; + } + + @Override + public @NonNull Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) { + Collection<String> ret = mResultData.getRetrievedEntryNames(namespaceName); + if (ret == null) { + ret = new LinkedList<String>(); + } + return ret; + } + + @Override + @Status + public int getStatus(@NonNull String namespaceName, @NonNull String name) { + return mResultData.getStatus(namespaceName, name); + } + + @Override + public @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name) { + return mResultData.getEntry(namespaceName, name); + } + } + +} diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java index 6398cee74cba..8e011053d2a7 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredential.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java @@ -58,14 +58,17 @@ class CredstoreIdentityCredential extends IdentityCredential { private @IdentityCredentialStore.Ciphersuite int mCipherSuite; private Context mContext; private ICredential mBinder; + private CredstorePresentationSession mSession; CredstoreIdentityCredential(Context context, String credentialName, @IdentityCredentialStore.Ciphersuite int cipherSuite, - ICredential binder) { + ICredential binder, + @Nullable CredstorePresentationSession session) { mContext = context; mCredentialName = credentialName; mCipherSuite = cipherSuite; mBinder = binder; + mSession = session; } private KeyPair mEphemeralKeyPair = null; @@ -239,6 +242,7 @@ class CredstoreIdentityCredential extends IdentityCredential { private boolean mAllowUsingExhaustedKeys = true; private boolean mAllowUsingExpiredKeys = false; + private boolean mIncrementKeyUsageCount = true; @Override public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) { @@ -250,6 +254,11 @@ class CredstoreIdentityCredential extends IdentityCredential { mAllowUsingExpiredKeys = allowUsingExpiredKeys; } + @Override + public void setIncrementKeyUsageCount(boolean incrementKeyUsageCount) { + mIncrementKeyUsageCount = incrementKeyUsageCount; + } + private boolean mOperationHandleSet = false; private long mOperationHandle = 0; @@ -264,7 +273,8 @@ class CredstoreIdentityCredential extends IdentityCredential { if (!mOperationHandleSet) { try { mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys, - mAllowUsingExpiredKeys); + mAllowUsingExpiredKeys, + mIncrementKeyUsageCount); mOperationHandleSet = true; } catch (android.os.RemoteException e) { throw new RuntimeException("Unexpected RemoteException ", e); @@ -315,7 +325,8 @@ class CredstoreIdentityCredential extends IdentityCredential { sessionTranscript != null ? sessionTranscript : new byte[0], readerSignature != null ? readerSignature : new byte[0], mAllowUsingExhaustedKeys, - mAllowUsingExpiredKeys); + mAllowUsingExpiredKeys, + mIncrementKeyUsageCount); } catch (android.os.RemoteException e) { throw new RuntimeException("Unexpected RemoteException ", e); } catch (android.os.ServiceSpecificException e) { diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java index d8d47424e2e8..fb0880ce3521 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -126,7 +126,8 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore { ICredential credstoreCredential; credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite); return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite, - credstoreCredential); + credstoreCredential, + null); } catch (android.os.RemoteException e) { throw new RuntimeException("Unexpected RemoteException ", e); } catch (android.os.ServiceSpecificException e) { @@ -162,4 +163,23 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore { + e.errorCode, e); } } + + @Override + public @NonNull PresentationSession createPresentationSession(@Ciphersuite int cipherSuite) + throws CipherSuiteNotSupportedException { + try { + ISession credstoreSession = mStore.createPresentationSession(cipherSuite); + return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) { + throw new CipherSuiteNotSupportedException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + } diff --git a/identity/java/android/security/identity/CredstorePresentationSession.java b/identity/java/android/security/identity/CredstorePresentationSession.java new file mode 100644 index 000000000000..e3c6689a8914 --- /dev/null +++ b/identity/java/android/security/identity/CredstorePresentationSession.java @@ -0,0 +1,214 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.LinkedHashMap; +import java.util.Map; + +class CredstorePresentationSession extends PresentationSession { + private static final String TAG = "CredstorePresentationSession"; + + private @IdentityCredentialStore.Ciphersuite int mCipherSuite; + private Context mContext; + private CredstoreIdentityCredentialStore mStore; + private ISession mBinder; + private Map<String, CredstoreIdentityCredential> mCredentialCache = new LinkedHashMap<>(); + private KeyPair mEphemeralKeyPair = null; + private byte[] mSessionTranscript = null; + private boolean mOperationHandleSet = false; + private long mOperationHandle = 0; + + CredstorePresentationSession(Context context, + @IdentityCredentialStore.Ciphersuite int cipherSuite, + CredstoreIdentityCredentialStore store, + ISession binder) { + mContext = context; + mCipherSuite = cipherSuite; + mStore = store; + mBinder = binder; + } + + private void ensureEphemeralKeyPair() { + if (mEphemeralKeyPair != null) { + return; + } + try { + // This PKCS#12 blob is generated in credstore, using BoringSSL. + // + // The main reason for this convoluted approach and not just sending the decomposed + // key-pair is that this would require directly using (device-side) BouncyCastle which + // is tricky due to various API hiding efforts. So instead we have credstore generate + // this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL + // doesn't support not using encryption when building a PKCS#12 blob). + // + byte[] pkcs12 = mBinder.getEphemeralKeyPair(); + String alias = "ephemeralKey"; + char[] password = {}; + + KeyStore ks = KeyStore.getInstance("PKCS12"); + ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12); + ks.load(bais, password); + PrivateKey privKey = (PrivateKey) ks.getKey(alias, password); + + Certificate cert = ks.getCertificate(alias); + PublicKey pubKey = cert.getPublicKey(); + + mEphemeralKeyPair = new KeyPair(pubKey, privKey); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } catch (android.os.RemoteException + | KeyStoreException + | CertificateException + | UnrecoverableKeyException + | NoSuchAlgorithmException + | IOException e) { + throw new RuntimeException("Unexpected exception ", e); + } + } + + @Override + public @NonNull KeyPair getEphemeralKeyPair() { + ensureEphemeralKeyPair(); + return mEphemeralKeyPair; + } + + @Override + public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey) + throws InvalidKeyException { + try { + byte[] uncompressedForm = + Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey); + mBinder.setReaderEphemeralPublicKey(uncompressedForm); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override + public void setSessionTranscript(@NonNull byte[] sessionTranscript) { + try { + mBinder.setSessionTranscript(sessionTranscript); + mSessionTranscript = sessionTranscript; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override + public @Nullable CredentialDataResult getCredentialData(@NonNull String credentialName, + @NonNull CredentialDataRequest request) + throws NoAuthenticationKeyAvailableException, InvalidReaderSignatureException, + InvalidRequestMessageException, EphemeralPublicKeyNotFoundException { + try { + // Cache the IdentityCredential to satisfy the property that AuthKey usage counts are + // incremented on only the _first_ getCredentialData() call. + // + CredstoreIdentityCredential credential = mCredentialCache.get(credentialName); + if (credential == null) { + ICredential credstoreCredential = + mBinder.getCredentialForPresentation(credentialName); + credential = new CredstoreIdentityCredential(mContext, credentialName, + mCipherSuite, credstoreCredential, + this); + mCredentialCache.put(credentialName, credential); + + credential.setAllowUsingExhaustedKeys(request.isAllowUsingExhaustedKeys()); + credential.setAllowUsingExpiredKeys(request.isAllowUsingExpiredKeys()); + credential.setIncrementKeyUsageCount(request.isIncrementUseCount()); + } + + ResultData deviceSignedResult = credential.getEntries( + request.getRequestMessage(), + request.getDeviceSignedEntriesToRequest(), + mSessionTranscript, + request.getReaderSignature()); + + // By design this second getEntries() call consumes the same auth-key. + + ResultData issuerSignedResult = credential.getEntries( + request.getRequestMessage(), + request.getIssuerSignedEntriesToRequest(), + mSessionTranscript, + request.getReaderSignature()); + + return new CredstoreCredentialDataResult(deviceSignedResult, issuerSignedResult); + + } catch (SessionTranscriptMismatchException e) { + throw new RuntimeException("Unexpected ", e); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) { + return null; + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + /** + * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an + * operation handle. + * + * @hide + */ + @Override + public long getCredstoreOperationHandle() { + if (!mOperationHandleSet) { + try { + mOperationHandle = mBinder.getAuthChallenge(); + mOperationHandleSet = true; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) { + // The NoAuthenticationKeyAvailableException will be thrown when + // the caller proceeds to call getEntries(). + } + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + return mOperationHandle; + } + +} diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java index 1e685856d011..cdf746fc9900 100644 --- a/identity/java/android/security/identity/IdentityCredential.java +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -48,7 +48,9 @@ public abstract class IdentityCredential { * encryption". * * @return ephemeral key pair to use to establish a secure channel with a reader. + * @deprecated Use {@link PresentationSession} instead. */ + @Deprecated public @NonNull abstract KeyPair createEphemeralKeyPair(); /** @@ -58,7 +60,9 @@ public abstract class IdentityCredential { * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to * establish a secure session. * @throws InvalidKeyException if the given key is invalid. + * @deprecated Use {@link PresentationSession} instead. */ + @Deprecated public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey) throws InvalidKeyException; @@ -72,7 +76,10 @@ public abstract class IdentityCredential { * * @param messagePlaintext unencrypted message to encrypt. * @return encrypted message. + * @deprecated Applications should use {@link PresentationSession} and + * implement encryption/decryption themselves. */ + @Deprecated public @NonNull abstract byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext); /** @@ -86,7 +93,10 @@ public abstract class IdentityCredential { * @param messageCiphertext encrypted message to decrypt. * @return decrypted message. * @throws MessageDecryptionException if the ciphertext couldn't be decrypted. + * @deprecated Applications should use {@link PresentationSession} and + * implement encryption/decryption themselves. */ + @Deprecated public @NonNull abstract byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext) throws MessageDecryptionException; @@ -111,7 +121,9 @@ public abstract class IdentityCredential { * * @param allowUsingExhaustedKeys whether to allow using an authentication key which use count * has been exceeded if no other key is available. + * @deprecated Use {@link PresentationSession} instead. */ + @Deprecated public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys); /** @@ -128,12 +140,36 @@ public abstract class IdentityCredential { * * @param allowUsingExpiredKeys whether to allow using an authentication key which use count * has been exceeded if no other key is available. + * @deprecated Use {@link PresentationSession} instead. */ + @Deprecated public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) { throw new UnsupportedOperationException(); } /** + * @hide + * + * Sets whether the usage count of an authentication key should be increased. This must be + * called prior to calling + * {@link #getEntries(byte[], Map, byte[], byte[])} or using a + * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this object. + * + * <p>By default this is set to true. + * + * <p>This is only implemented in feature version 202201 or later. If not implemented, the call + * fails with {@link UnsupportedOperationException}. See + * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known + * feature versions. + * + * @param incrementKeyUsageCount whether the usage count of the key should be increased. + * @deprecated Use {@link PresentationSession} instead. + */ + public void setIncrementKeyUsageCount(boolean incrementKeyUsageCount) { + throw new UnsupportedOperationException(); + } + + /** * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an * operation handle. * @@ -149,15 +185,19 @@ public abstract class IdentityCredential { * by using the {@link ResultData#getStatus(String, String)} method on each of the requested * entries. * - * <p>It is the responsibility of the calling application to know if authentication is needed - * and use e.g. {@link android.hardware.biometrics.BiometricPrompt} to make the user - * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which - * references this object. If needed, this must be done before calling - * {@link #getEntries(byte[], Map, byte[], byte[])}. - * * <p>It is permissible to call this method multiple times using the same instance but if this * is done, the {@code sessionTranscript} parameter must be identical for each call. If this is * not the case, the {@link SessionTranscriptMismatchException} exception is thrown. + * Additionally, if this is done the same auth-key will be used. + * + * <p>The application should not make any assumptions on whether user authentication is needed. + * Instead, the application should request the data elements values first and then examine + * the returned {@link ResultData}. If {@link ResultData#STATUS_USER_AUTHENTICATION_FAILED} + * is returned the application should get a + * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this + * object and use it with a {@link android.hardware.biometrics.BiometricPrompt}. Upon successful + * authentication the application may call {@link #getEntries(byte[], Map, byte[], byte[])} + * again. * * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request * from the verifier. The content can be defined in the way appropriate for the credential, but @@ -269,7 +309,9 @@ public abstract class IdentityCredential { * @throws InvalidRequestMessageException if the requestMessage is malformed. * @throws EphemeralPublicKeyNotFoundException if the ephemeral public key was not found in * the session transcript. + * @deprecated Use {@link PresentationSession} instead. */ + @Deprecated public abstract @NonNull ResultData getEntries( @Nullable byte[] requestMessage, @NonNull Map<String, Collection<String>> entriesToRequest, diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java index 6ccd0e892141..dbb8aaa7796e 100644 --- a/identity/java/android/security/identity/IdentityCredentialStore.java +++ b/identity/java/android/security/identity/IdentityCredentialStore.java @@ -209,6 +209,25 @@ public abstract class IdentityCredentialStore { @Deprecated public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName); + /** + * Creates a new presentation session. + * + * <p>This method gets an object to be used for interaction with a remote verifier for + * presentation of one or more credentials. + * + * <p>This is only implemented in feature version 202201 or later. If not implemented, the call + * fails with {@link UnsupportedOperationException}. See + * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known + * feature versions. + * + * @param cipherSuite the cipher suite to use for communicating with the verifier. + * @return The presentation session. + */ + public @NonNull PresentationSession createPresentationSession(@Ciphersuite int cipherSuite) + throws CipherSuiteNotSupportedException { + throw new UnsupportedOperationException(); + } + /** @hide */ @IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256}) @Retention(RetentionPolicy.SOURCE) diff --git a/identity/java/android/security/identity/PresentationSession.java b/identity/java/android/security/identity/PresentationSession.java new file mode 100644 index 000000000000..afaafce32798 --- /dev/null +++ b/identity/java/android/security/identity/PresentationSession.java @@ -0,0 +1,173 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.PublicKey; + +/** + * Class for presenting multiple documents to a remote verifier. + * + * Use {@link IdentityCredentialStore#createPresentationSession(int)} to create a {@link + * PresentationSession} instance. + */ +public abstract class PresentationSession { + /** + * @hide + */ + protected PresentationSession() {} + + /** + * Gets the ephemeral key pair to use to establish a secure channel with the verifier. + * + * <p>Applications should use this key-pair for the communications channel with the verifier + * using a protocol / cipher-suite appropriate for the application. One example of such a + * protocol is the one used for Mobile Driving Licenses, see ISO 18013-5. + * + * <p>The ephemeral key pair is tied to the {@link PresentationSession} instance so subsequent + * calls to this method will return the same key-pair. + * + * @return ephemeral key pair to use to establish a secure channel with a reader. + */ + public @NonNull abstract KeyPair getEphemeralKeyPair(); + + /** + * Set the ephemeral public key provided by the verifier. + * + * <p>If called, this must be called before any calls to + * {@link #getCredentialData(String, CredentialDataRequest)}. + * + * <p>This method can only be called once per {@link PresentationSession} instance. + * + * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to + * establish a secure session. + * @throws InvalidKeyException if the given key is invalid. + */ + public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey) + throws InvalidKeyException; + + /** + * Set the session transcript. + * + * <p>If called, this must be called before any calls to + * {@link #getCredentialData(String, CredentialDataRequest)}. + * + * <p>The X and Y coordinates of the public part of the key-pair returned by {@link + * #getEphemeralKeyPair()} must appear somewhere in the bytes of the passed in CBOR. Each of + * these coordinates must appear encoded with the most significant bits first and use the exact + * amount of bits indicated by the key size of the ephemeral keys. For example, if the + * ephemeral key is using the P-256 curve then the 32 bytes for the X coordinate encoded with + * the most significant bits first must appear somewhere and ditto for the 32 bytes for the Y + * coordinate. + * + * <p>This method can only be called once per {@link PresentationSession} instance. + * + * @param sessionTranscript the session transcript. + */ + public abstract void setSessionTranscript(@NonNull byte[] sessionTranscript); + + /** + * Retrieves data from a named credential in the current presentation session. + * + * <p>If an access control check fails for one of the requested entries or if the entry + * doesn't exist, the entry is simply not returned. The application can detect this + * by using the {@link CredentialDataResult.Entries#getStatus(String, String)} method on + * each of the requested entries. + * + * <p>The application should not make any assumptions on whether user authentication is needed. + * Instead, the application should request the data elements values first and then examine + * the returned {@link CredentialDataResult.Entries}. If + * {@link CredentialDataResult.Entries#STATUS_USER_AUTHENTICATION_FAILED} is returned the + * application should get a + * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this + * object and use it with a {@link android.hardware.biometrics.BiometricPrompt}. Upon successful + * authentication the application may call + * {@link #getCredentialData(String, CredentialDataRequest)} again. + * + * <p>It is permissible to call this method multiple times using the same credential name. + * If this is done the same auth-key will be used. + * + * <p>If the reader signature is set in the request parameter (via the + * {@link CredentialDataRequest.Builder#setReaderSignature(byte[])} method) it must contain + * the bytes of a {@code COSE_Sign1} structure as defined in RFC 8152. For the payload + * {@code nil} shall be used and the detached payload is the {@code ReaderAuthenticationBytes} + * CBOR described below. + * <pre> + * ReaderAuthentication = [ + * "ReaderAuthentication", + * SessionTranscript, + * ItemsRequestBytes + * ] + * + * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) + * + * ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication) + * </pre> + * + * <p>where {@code ItemsRequestBytes} are the bytes of the request message set in + * the request parameter (via the + * {@link CredentialDataRequest.Builder#setRequestMessage(byte[])} method). + * + * <p>The public key corresponding to the key used to make the signature, can be found in the + * {@code x5chain} unprotected header element of the {@code COSE_Sign1} structure (as as + * described in + * <a href="https://tools.ietf.org/html/draft-ietf-cose-x509-08">draft-ietf-cose-x509-08</a>). + * There will be at least one certificate in said element and there may be more (and if so, + * each certificate must be signed by its successor). + * + * <p>Data elements protected by reader authentication are returned if, and only if, + * {@code requestMessage} is signed by the top-most certificate in the reader's certificate + * chain, and the data element is configured with an {@link AccessControlProfile} configured + * with an X.509 certificate for a key which appear in the certificate chain. + * + * <p>Note that the request message CBOR is used only for enforcing reader authentication, it's + * not used for determining which entries this API will return. The application is expected to + * have parsed the request message and filtered it according to user preference and/or consent. + * + * @param credentialName the name of the credential to retrieve. + * @param request the data to retrieve from the credential + * @return If the credential wasn't found, returns null. Otherwise a + * {@link CredentialDataResult} object containing entry data organized by namespace and + * a cryptographically authenticated representation of the same data, bound to the + * current session. + * @throws NoAuthenticationKeyAvailableException if authentication keys were never + * provisioned for the credential or if they + * are expired or exhausted their use-count. + * @throws InvalidRequestMessageException if the requestMessage is malformed. + * @throws InvalidReaderSignatureException if the reader signature is invalid, or it + * doesn't contain a certificate chain, or if + * the signature failed to validate. + * @throws EphemeralPublicKeyNotFoundException if the ephemeral public key was not found in + * the session transcript. + */ + public abstract @Nullable CredentialDataResult getCredentialData( + @NonNull String credentialName, @NonNull CredentialDataRequest request) + throws NoAuthenticationKeyAvailableException, InvalidReaderSignatureException, + InvalidRequestMessageException, EphemeralPublicKeyNotFoundException; + + /** + * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an + * operation handle. + * + * @hide + */ + public abstract long getCredstoreOperationHandle(); +} diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java index 71860d261285..d46f9854b278 100644 --- a/identity/java/android/security/identity/ResultData.java +++ b/identity/java/android/security/identity/ResultData.java @@ -28,7 +28,10 @@ import java.util.Collection; /** * An object that contains the result of retrieving data from a credential. This is used to return * data requested from a {@link IdentityCredential}. + * + * @deprecated Use {@link PresentationSession} instead. */ +@Deprecated public abstract class ResultData { /** Value was successfully retrieved. */ diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 919a93b8f107..05fb4c3cf76f 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.security.keystore.KeyProperties; import android.security.maintenance.IKeystoreMaintenance; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; @@ -157,6 +158,11 @@ public class AndroidKeyStoreMaintenance { * Migrates a key given by the source descriptor to the location designated by the destination * descriptor. * + * If Domain::APP is selected in either source or destination, nspace must be set to + * {@link KeyProperties#NAMESPACE_APPLICATION}, implying the caller's UID. + * If the caller has the MIGRATE_ANY_KEY permission, Domain::APP may be used with + * other nspace values which then indicates the UID of a different application. + * * @param source - The key to migrate may be specified by Domain.APP, Domain.SELINUX, or * Domain.KEY_ID. The caller needs the permissions use, delete, and grant for the * source namespace. @@ -183,4 +189,20 @@ public class AndroidKeyStoreMaintenance { return SYSTEM_ERROR; } } + + /** + * @see IKeystoreMaintenance#listEntries(int, long) + */ + @Nullable + public static KeyDescriptor[] listEntries(int domain, long nspace) { + try { + return getService().listEntries(domain, nspace); + } catch (ServiceSpecificException e) { + Log.e(TAG, "listEntries failed", e); + return null; + } catch (Exception e) { + Log.e(TAG, "Can not connect to keystore", e); + return null; + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index df751fc9fa48..180c77250fd1 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -79,22 +79,23 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { } @Override - public void registerOrganizer() { - if (mAnimationController != null) { - throw new IllegalStateException("Must unregister the organizer before re-register."); + public void unregisterOrganizer() { + stopOverrideSplitAnimation(); + mAnimationController = null; + super.unregisterOrganizer(); + } + + void startOverrideSplitAnimation() { + if (mAnimationController == null) { + mAnimationController = new TaskFragmentAnimationController(this); } - super.registerOrganizer(); - mAnimationController = new TaskFragmentAnimationController(this); mAnimationController.registerRemoteAnimations(); } - @Override - public void unregisterOrganizer() { + void stopOverrideSplitAnimation() { if (mAnimationController != null) { mAnimationController.unregisterRemoteAnimations(); - mAnimationController = null; } - super.unregisterOrganizer(); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index b8e8b0114b47..8f368c2bee22 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -33,6 +33,7 @@ import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -63,6 +64,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); + // We currently only support split activity embedding within the one root Task. + private final Rect mParentBounds = new Rect(); + public SplitController() { mPresenter = new SplitPresenter(new MainThreadExecutor(), this); ActivityThread activityThread = ActivityThread.currentActivityThread(); @@ -79,6 +83,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) { mSplitRules.clear(); mSplitRules.addAll(rules); + updateAnimationOverride(); } @NonNull @@ -158,6 +163,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) { + onParentBoundsMayChange(parentConfig.windowConfiguration.getBounds()); TaskFragmentContainer container = getContainer(fragmentToken); if (container != null) { mPresenter.updateContainer(container); @@ -165,6 +171,51 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + private void onParentBoundsMayChange(Activity activity) { + if (activity.isFinishing()) { + return; + } + + onParentBoundsMayChange(mPresenter.getParentContainerBounds(activity)); + } + + private void onParentBoundsMayChange(Rect parentBounds) { + if (!parentBounds.isEmpty() && !mParentBounds.equals(parentBounds)) { + mParentBounds.set(parentBounds); + updateAnimationOverride(); + } + } + + /** + * Updates if we should override transition animation. We only want to override if the Task + * bounds is large enough for at least one split rule. + */ + private void updateAnimationOverride() { + if (mParentBounds.isEmpty()) { + // We don't know about the parent bounds yet. + return; + } + + // Check if the parent container bounds can support any split rule. + boolean supportSplit = false; + for (EmbeddingRule rule : mSplitRules) { + if (!(rule instanceof SplitRule)) { + continue; + } + if (mPresenter.shouldShowSideBySide(mParentBounds, (SplitRule) rule)) { + supportSplit = true; + break; + } + } + + // We only want to override if it supports split. + if (supportSplit) { + mPresenter.startOverrideSplitAnimation(); + } else { + mPresenter.stopOverrideSplitAnimation(); + } + } + void onActivityCreated(@NonNull Activity launchedActivity) { handleActivityCreated(launchedActivity); updateCallbackIfNecessary(); @@ -180,6 +231,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer currentContainer = getContainerWithActivity( launchedActivity.getActivityToken()); + if (currentContainer == null) { + // Initial check before any TaskFragment is created. + onParentBoundsMayChange(launchedActivity); + } + // Check if the activity is configured to always be expanded. if (shouldExpand(launchedActivity, null, splitRules)) { if (shouldContainerBeExpanded(currentContainer)) { @@ -257,6 +313,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // onTaskFragmentParentInfoChanged return; } + // The bounds of the container may have been changed. + onParentBoundsMayChange(activity); // Check if activity requires a placeholder launchPlaceholderIfNecessary(activity); @@ -346,7 +404,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen TaskFragmentContainer getTopActiveContainer() { for (int i = mContainers.size() - 1; i >= 0; i--) { TaskFragmentContainer container = mContainers.get(i); - if (!container.isFinished()) { + if (!container.isFinished() && container.getTopNonFinishingActivity() != null) { return container; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java index 3c7d2de6165f..a801dc8193fd 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java @@ -37,32 +37,42 @@ class TaskFragmentAnimationController { private final TaskFragmentOrganizer mOrganizer; private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner(); + private final RemoteAnimationDefinition mDefinition; + private boolean mIsRegister; TaskFragmentAnimationController(TaskFragmentOrganizer organizer) { mOrganizer = organizer; + mDefinition = new RemoteAnimationDefinition(); + final RemoteAnimationAdapter animationAdapter = + new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */); + mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter); + mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter); + mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter); + mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter); + mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter); + mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter); + mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter); } void registerRemoteAnimations() { if (DEBUG) { Log.v(TAG, "registerRemoteAnimations"); } - final RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); - final RemoteAnimationAdapter animationAdapter = - new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */); - definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter); - definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter); - definition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter); - definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter); - definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter); - definition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter); - definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter); - mOrganizer.registerRemoteAnimations(definition); + if (mIsRegister) { + return; + } + mOrganizer.registerRemoteAnimations(mDefinition); + mIsRegister = true; } void unregisterRemoteAnimations() { if (DEBUG) { Log.v(TAG, "unregisterRemoteAnimations"); } + if (!mIsRegister) { + return; + } mOrganizer.unregisterRemoteAnimations(); + mIsRegister = false; } } diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml new file mode 100644 index 000000000000..d8f356164358 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml @@ -0,0 +1,25 @@ +<?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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/tv_pip_menu_focus_border" + android:pathData="M7,10l5,5 5,-5H7z"/> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml new file mode 100644 index 000000000000..3e0011c65942 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml @@ -0,0 +1,25 @@ +<?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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/tv_pip_menu_focus_border" + android:pathData="M14,7l-5,5 5,5V7z"/> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml new file mode 100644 index 000000000000..f6b3c72e3cb5 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml @@ -0,0 +1,25 @@ +<?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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/tv_pip_menu_focus_border" + android:pathData="M10,17l5,-5 -5,-5v10z"/> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml new file mode 100644 index 000000000000..1a3446249573 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml @@ -0,0 +1,25 @@ +<?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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/tv_pip_menu_focus_border" + android:pathData="M7,14l5,-5 5,5H7z"/> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml new file mode 100644 index 000000000000..37f4c87006ba --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml @@ -0,0 +1,27 @@ +<?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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:pathData="M11,5.83L11,10h2L13,5.83l1.83,1.83 1.41,-1.42L12,2 7.76,6.24l1.41,1.42zM17.76,7.76l-1.42,1.41L18.17,11L14,11v2h4.17l-1.83,1.83 1.42,1.41L22,12zM13,18.17L13,14h-2v4.17l-1.83,-1.83 -1.41,1.42L12,22l4.24,-4.24 -1.41,-1.42zM10,13v-2L5.83,11l1.83,-1.83 -1.42,-1.41L2,12l4.24,4.24 1.42,-1.41L5.83,13z" + android:fillColor="#FFFFFF" /> + +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/background_panel.xml b/libs/WindowManager/Shell/res/layout/background_panel.xml new file mode 100644 index 000000000000..c3569d80fa1e --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/background_panel.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ 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 + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/background_panel_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal | center_vertical" + android:background="@android:color/transparent"> +</LinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index 5b90c99c22e0..b56b114e9a4c 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml @@ -15,57 +15,101 @@ limitations under the License. --> <!-- Layout for TvPipMenuView --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tv_pip_menu" android:layout_width="match_parent" android:layout_height="match_parent"> - <FrameLayout - android:id="@+id/tv_pip_menu_frame" + <HorizontalScrollView android:layout_width="match_parent" android:layout_height="match_parent" - android:alpha="0" > + android:layout_centerHorizontal="true" + android:gravity="center" + android:scrollbars="none" + android:layout_centerInParent="true" + android:layout_margin="@dimen/pip_menu_outer_space"> - <HorizontalScrollView - android:layout_width="match_parent" + <LinearLayout + android:id="@+id/tv_pip_menu_action_buttons" + android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_centerHorizontal="true" + android:paddingStart="@dimen/pip_menu_button_wrapper_margin" + android:paddingEnd="@dimen/pip_menu_button_wrapper_margin" android:gravity="center" - android:scrollbars="none" - android:requiresFadingEdge="vertical" - android:fadingEdgeLength="30dp"> + android:orientation="horizontal" + android:alpha="0"> - <LinearLayout - android:id="@+id/tv_pip_menu_action_buttons" + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_fullscreen_button" android:layout_width="wrap_content" - android:layout_height="match_parent" - android:paddingStart="@dimen/pip_menu_button_wrapper_margin" - android:paddingEnd="@dimen/pip_menu_button_wrapper_margin" - android:gravity="center" - android:orientation="horizontal"> + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_fullscreen_white" + android:text="@string/pip_fullscreen" /> - <com.android.wm.shell.pip.tv.TvPipMenuActionButton - android:id="@+id/tv_pip_menu_fullscreen_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_fullscreen_white" - android:text="@string/pip_fullscreen" /> + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_move_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_move_white" + android:text="@String/pip_move" /> - <com.android.wm.shell.pip.tv.TvPipMenuActionButton - android:id="@+id/tv_pip_menu_close_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_close_white" - android:text="@string/pip_close" /> + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_close_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_close_white" + android:text="@string/pip_close" /> - <!-- More TvPipMenuActionButtons may be added here at runtime. --> + <!-- More TvPipMenuActionButtons may be added here at runtime. --> - </LinearLayout> - </HorizontalScrollView> + </LinearLayout> + </HorizontalScrollView> - <View - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/tv_pip_menu_border"/> - </FrameLayout> -</FrameLayout> + <View + android:id="@+id/tv_pip_menu_frame" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:alpha="0" + android:layout_margin="@dimen/pip_menu_outer_space_frame" + android:background="@drawable/tv_pip_menu_border"/> + + <ImageView + android:id="@+id/tv_pip_menu_arrow_up" + android:layout_width="@dimen/pip_menu_arrow_size" + android:layout_height="@dimen/pip_menu_arrow_size" + android:layout_centerHorizontal="true" + android:layout_alignParentTop="true" + android:alpha="0" + android:elevation="@dimen/pip_menu_arrow_elevation" + android:src="@drawable/pip_ic_move_up" /> + + <ImageView + android:id="@+id/tv_pip_menu_arrow_right" + android:layout_width="@dimen/pip_menu_arrow_size" + android:layout_height="@dimen/pip_menu_arrow_size" + android:layout_centerVertical="true" + android:layout_alignParentRight="true" + android:alpha="0" + android:elevation="@dimen/pip_menu_arrow_elevation" + android:src="@drawable/pip_ic_move_right" /> + + <ImageView + android:id="@+id/tv_pip_menu_arrow_down" + android:layout_width="@dimen/pip_menu_arrow_size" + android:layout_height="@dimen/pip_menu_arrow_size" + android:layout_centerHorizontal="true" + android:layout_alignParentBottom="true" + android:alpha="0" + android:elevation="@dimen/pip_menu_arrow_elevation" + android:src="@drawable/pip_ic_move_down" /> + + <ImageView + android:id="@+id/tv_pip_menu_arrow_left" + android:layout_width="@dimen/pip_menu_arrow_size" + android:layout_height="@dimen/pip_menu_arrow_size" + android:layout_centerVertical="true" + android:layout_alignParentLeft="true" + android:alpha="0" + android:elevation="@dimen/pip_menu_arrow_elevation" + android:src="@drawable/pip_ic_move_left" /> +</RelativeLayout> diff --git a/libs/WindowManager/Shell/res/values-af/strings_tv.xml b/libs/WindowManager/Shell/res/values-af/strings_tv.xml index 6ce588034f9e..3edb8e967768 100644 --- a/libs/WindowManager/Shell/res/values-af/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-af/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Titellose program)"</string> <string name="pip_close" msgid="9135220303720555525">"Maak PIP toe"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Volskerm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml index fcb87c5682e3..b1c6542ce616 100644 --- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string> <string name="pip_close" msgid="9135220303720555525">"PIPን ዝጋ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml index 4eef29e2ed12..dfc505365ca4 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ليس هناك عنوان للبرنامج)"</string> <string name="pip_close" msgid="9135220303720555525">"إغلاق PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ملء الشاشة"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings_tv.xml b/libs/WindowManager/Shell/res/values-as/strings_tv.xml index 170b2dbd458c..352bde5a1931 100644 --- a/libs/WindowManager/Shell/res/values-as/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-as/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিৰোনামবিহীন কাৰ্যক্ৰম)"</string> <string name="pip_close" msgid="9135220303720555525">"পিপ বন্ধ কৰক"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"সম্পূৰ্ণ স্ক্ৰীন"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings_tv.xml b/libs/WindowManager/Shell/res/values-az/strings_tv.xml index c9f1acbef31b..9b46d5fc222c 100644 --- a/libs/WindowManager/Shell/res/values-az/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-az/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıqsız proqram)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP bağlayın"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml index 6fbc91bbec60..790a6d471e55 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ceo ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml index d33bf99e2ebd..c4df7fc74121 100644 --- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string> <string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml index f4fad601179f..cbb00ae19024 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без заглавие)"</string> <string name="pip_close" msgid="9135220303720555525">"Затваряне на PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цял екран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml index 0eb83a0276e6..f24c92a797e5 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিরোনামহীন প্রোগ্রাম)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP বন্ধ করুন"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"পূর্ণ স্ক্রিন"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml index 9a655bb41066..80bac2a07da3 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> <string name="pip_close" msgid="9135220303720555525">"Zatvori sliku u slici"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml index b80fc41402dd..66cd93a2c78e 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sense títol)"</string> <string name="pip_close" msgid="9135220303720555525">"Tanca PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml index 56abcbe473fb..500050b09cab 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Bez názvu)"</string> <string name="pip_close" msgid="9135220303720555525">"Ukončit obraz v obraze (PIP)"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings_tv.xml b/libs/WindowManager/Shell/res/values-da/strings_tv.xml index fdb6b783399e..896895b90f47 100644 --- a/libs/WindowManager/Shell/res/values-da/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-da/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uden titel)"</string> <string name="pip_close" msgid="9135220303720555525">"Luk integreret billede"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Fuld skærm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml index 02cce9d73647..7809efe85afa 100644 --- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Kein Sendungsname gefunden)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings_tv.xml b/libs/WindowManager/Shell/res/values-el/strings_tv.xml index 880ea37e6bf7..088bcc39b859 100644 --- a/libs/WindowManager/Shell/res/values-el/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-el/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Δεν υπάρχει τίτλος προγράμματος)"</string> <string name="pip_close" msgid="9135220303720555525">"Κλείσιμο PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Πλήρης οθόνη"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml index e3f08c8cc76f..7900fdc3a0b1 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml index e3f08c8cc76f..7900fdc3a0b1 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml index e3f08c8cc76f..7900fdc3a0b1 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml index e3f08c8cc76f..7900fdc3a0b1 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml index 3f9ef0ea2816..3b12d90f33a2 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml @@ -21,4 +21,5 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml index 5d5954a19761..3be850a90c30 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sin título de programa)"</string> <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml index d31b9b45cae3..7eba361df2c3 100644 --- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sin título)"</string> <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings_tv.xml b/libs/WindowManager/Shell/res/values-et/strings_tv.xml index bc7a6adafc03..ca6e6695b52b 100644 --- a/libs/WindowManager/Shell/res/values-et/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-et/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programmi pealkiri puudub)"</string> <string name="pip_close" msgid="9135220303720555525">"Sule PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Täisekraan"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml index cf5f98883082..3f47e9591f66 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa izengabea)"</string> <string name="pip_close" msgid="9135220303720555525">"Itxi PIPa"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantaila osoa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml index 5b815b4c7b86..cc5cf64d01dd 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string> <string name="pip_close" msgid="9135220303720555525">"بستن PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml index 77ad6eef91e7..b77988659cc3 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nimetön)"</string> <string name="pip_close" msgid="9135220303720555525">"Sulje PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Koko näyttö"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml index 0ec7f40f0e9f..1798c7db482c 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Aucun programme de titre)"</string> <string name="pip_close" msgid="9135220303720555525">"Fermer mode IDI"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml index 27fd155535b7..b039934ada8c 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programme sans titre)"</string> <string name="pip_close" msgid="9135220303720555525">"Fermer mode PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml index df96f6cb794d..0d91eba31492 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sen título)"</string> <string name="pip_close" msgid="9135220303720555525">"Pechar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml index 3608f1d530c0..a748df3e2f43 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(કોઈ ટાઇટલ પ્રોગ્રામ નથી)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP બંધ કરો"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"પૂર્ણ સ્ક્રીન"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml index 720bb6ca5e24..040072bf5087 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP बंद करें"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्क्रीन"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml index 21f8cb63f470..20081e4b49dc 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli zaslon"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml index 0010086bb0b5..c78146d9a773 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Cím nélküli program)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP bezárása"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Teljes képernyő"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml index cb18762be48b..55d5bd76ecd3 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Առանց վերնագրի ծրագիր)"</string> <string name="pip_close" msgid="9135220303720555525">"Փակել PIP-ն"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Լիէկրան"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings_tv.xml b/libs/WindowManager/Shell/res/values-in/strings_tv.xml index 8f3a28764b00..640185263f8e 100644 --- a/libs/WindowManager/Shell/res/values-in/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-in/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tanpa judul)"</string> <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Layar penuh"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings_tv.xml b/libs/WindowManager/Shell/res/values-is/strings_tv.xml index 1f148d948a0e..fa36829f5634 100644 --- a/libs/WindowManager/Shell/res/values-is/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-is/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Efni án titils)"</string> <string name="pip_close" msgid="9135220303720555525">"Loka mynd í mynd"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Allur skjárinn"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings_tv.xml b/libs/WindowManager/Shell/res/values-it/strings_tv.xml index 127454cf28bf..f6e91be5b4ae 100644 --- a/libs/WindowManager/Shell/res/values-it/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-it/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma senza titolo)"</string> <string name="pip_close" msgid="9135220303720555525">"Chiudi PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Schermo intero"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml index ef98a9c41cf2..356e8d536e4c 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string> <string name="pip_close" msgid="9135220303720555525">"סגירת PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml index b7ab28c44fd2..07684d3acbe6 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無題の番組)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP を閉じる"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全画面表示"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml index 1bf4b8ebdcda..043e5eb8fda9 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(პროგრამის სათაურის გარეშე)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP-ის დახურვა"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"სრულ ეკრანზე"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml index 8f1e725e79e2..79437975ca7e 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Атаусыз бағдарлама)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP жабу"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Толық экран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings_tv.xml b/libs/WindowManager/Shell/res/values-km/strings_tv.xml index b55997056e66..2e5625407ae9 100644 --- a/libs/WindowManager/Shell/res/values-km/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-km/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(កម្មវិធីគ្មានចំណងជើង)"</string> <string name="pip_close" msgid="9135220303720555525">"បិទ PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ពេញអេក្រង់"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml index 9d3942fa4dd3..6c8880d46cef 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP ಮುಚ್ಚಿ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ಪೂರ್ಣ ಪರದೆ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml index 46d6ad4e0b0f..35b1b19f629c 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(제목 없는 프로그램)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP 닫기"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"전체화면"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml index d5d1d7ef914e..72d70f056ce9 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Аталышы жок программа)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP\'ти жабуу"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Толук экран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml index f6362c120b9f..3604726b7b69 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ໂປຣແກຣມບໍ່ມີຊື່)"</string> <string name="pip_close" msgid="9135220303720555525">"ປິດ PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ເຕັມໜ້າຈໍ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml index e4695a05f038..fa5a4c4427bf 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa be pavadinimo)"</string> <string name="pip_close" msgid="9135220303720555525">"Uždaryti PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Visas ekranas"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml index f2b037fbeeee..cafd43ac9ef4 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma bez nosaukuma)"</string> <string name="pip_close" msgid="9135220303720555525">"Aizvērt PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pilnekrāna režīms"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml index 25dc764f4d5e..b927b568301a 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без наслов)"</string> <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цел екран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml index c74e0bbfaa5b..aef059fcec4f 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്ണ്ണ സ്ക്രീന്"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml index 55519d462b69..7dfec68fc958 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Гарчиггүй хөтөлбөр)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP-г хаах"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Бүтэн дэлгэц"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml index ad2cfc6035c2..447cb7d26544 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षक नसलेला कार्यक्रम)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP बंद करा"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रीन"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml index b2d7214381ef..3a5584def102 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tiada tajuk)"</string> <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Skrin penuh"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings_tv.xml b/libs/WindowManager/Shell/res/values-my/strings_tv.xml index c18d53932163..84ec0e5caf11 100644 --- a/libs/WindowManager/Shell/res/values-my/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-my/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ခေါင်းစဉ်မဲ့ အစီအစဉ်)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP ကိုပိတ်ပါ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"မျက်နှာပြင် အပြည့်"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml index 8a7f315606ad..78ec6db0bd98 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uten tittel)"</string> <string name="pip_close" msgid="9135220303720555525">"Lukk PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Fullskjerm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml index 87fa3279f05e..4458a14f0a83 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षकविहीन कार्यक्रम)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP लाई बन्द गर्नुहोस्"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रिन"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml index df3809e5d6c6..d21515dc31f6 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Naamloos programma)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP sluiten"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Volledig scherm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings_tv.xml b/libs/WindowManager/Shell/res/values-or/strings_tv.xml index 295a5c4ee1ce..a6793502d0df 100644 --- a/libs/WindowManager/Shell/res/values-or/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-or/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(କୌଣସି ଟାଇଟଲ୍ ପ୍ରୋଗ୍ରାମ୍ ନାହିଁ)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP ବନ୍ଦ କରନ୍ତୁ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml index e32895a9a239..a0ff4f370f9f 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ਸਿਰਲੇਖ-ਰਹਿਤ ਪ੍ਰੋਗਰਾਮ)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP ਬੰਦ ਕਰੋ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml index 286fd7b2ff0f..6320893389a6 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez tytułu)"</string> <string name="pip_close" msgid="9135220303720555525">"Zamknij PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pełny ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml index 57edcdf74cf4..fef9d470d87f 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string> <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml index 9372e0f637cb..461571f57ea7 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sem título do programa)"</string> <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ecrã inteiro"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml index 57edcdf74cf4..fef9d470d87f 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string> <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml index 9438e4955b68..80bf151939ce 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program fără titlu)"</string> <string name="pip_close" msgid="9135220303720555525">"Închideți PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ecran complet"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml index 24785aa7e184..de5348aaff1e 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Без названия)"</string> <string name="pip_close" msgid="9135220303720555525">"\"Кадр в кадре\" – выйти"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Во весь экран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings_tv.xml b/libs/WindowManager/Shell/res/values-si/strings_tv.xml index 62ee6d4f44d2..047004048665 100644 --- a/libs/WindowManager/Shell/res/values-si/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-si/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(මාතෘකාවක් නැති වැඩසටහන)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP වසන්න"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"සම්පූර්ණ තිරය"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml index a7a515cdc61c..41a432c5c649 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez názvu)"</string> <string name="pip_close" msgid="9135220303720555525">"Zavrieť režim PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml index fe5c9ae5d2a8..de5605f91b64 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program brez naslova)"</string> <string name="pip_close" msgid="9135220303720555525">"Zapri način PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celozaslonsko"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml index 1d5583b2c826..08a640962401 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program pa titull)"</string> <string name="pip_close" msgid="9135220303720555525">"Mbyll PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ekrani i plotë"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml index 62ad1e8f6e69..f932928ad158 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програм без наслова)"</string> <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цео екран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml index 74fb590c3e4d..1428fdbacf4e 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Namnlöst program)"</string> <string name="pip_close" msgid="9135220303720555525">"Stäng PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Helskärm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml index cf0d8a9b3910..615209f9e9bd 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programu isiyo na jina)"</string> <string name="pip_close" msgid="9135220303720555525">"Funga PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Skrini nzima"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml index 8bca46314e30..71c242c94d1a 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(தலைப்பு இல்லை)"</string> <string name="pip_close" msgid="9135220303720555525">"PIPஐ மூடு"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"முழுத்திரை"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml index 47489efbc4c2..f2dfb39cb99d 100644 --- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(శీర్షిక లేని ప్రోగ్రామ్)"</string> <string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"పూర్తి స్క్రీన్"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings_tv.xml b/libs/WindowManager/Shell/res/values-th/strings_tv.xml index d3797e7c3cde..e810c885a898 100644 --- a/libs/WindowManager/Shell/res/values-th/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-th/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ไม่มีชื่อรายการ)"</string> <string name="pip_close" msgid="9135220303720555525">"ปิด PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"เต็มหน้าจอ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml index b01c1115cd34..11d2953467b2 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Walang pamagat na programa)"</string> <string name="pip_close" msgid="9135220303720555525">"Isara ang PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml index c92c4d02f465..6ed6e9fa1656 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıksız program)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP\'yi kapat"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml index e41ebc4fb245..558ec51752b9 100644 --- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml +++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml @@ -21,7 +21,14 @@ <dimen name="pip_menu_icon_size">20dp</dimen> <dimen name="pip_menu_button_margin">4dp</dimen> <dimen name="pip_menu_button_wrapper_margin">26dp</dimen> - <dimen name="pip_menu_border_width">2dp</dimen> - <dimen name="pip_menu_border_radius">0dp</dimen> + <dimen name="pip_menu_border_width">4dp</dimen> + <dimen name="pip_menu_border_radius">4dp</dimen> + <dimen name="pip_menu_outer_space">24dp</dimen> + + <!-- outer space minus border width --> + <dimen name="pip_menu_outer_space_frame">20dp</dimen> + + <dimen name="pip_menu_arrow_size">24dp</dimen> + <dimen name="pip_menu_arrow_elevation">5dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml index 74d4723d7850..482f59a4c117 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без назви)"</string> <string name="pip_close" msgid="9135220303720555525">"Закрити PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"На весь екран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml index 317953309947..c1954c750941 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP بند کریں"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml index ae5a647301c8..514055261e3b 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nomsiz)"</string> <string name="pip_close" msgid="9135220303720555525">"Kadr ichida kadr – chiqish"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Butun ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml index 082d12596076..e54d866f10e9 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Không có chương trình tiêu đề)"</string> <string name="pip_close" msgid="9135220303720555525">"Đóng PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Toàn màn hình"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml index cb3fcf7c4c16..9ce1e6c41e75 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(节目没有标题)"</string> <string name="pip_close" msgid="9135220303720555525">"关闭画中画"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全屏"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml index 956243ed6e6d..984677290a85 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(沒有標題的節目)"</string> <string name="pip_close" msgid="9135220303720555525">"關閉 PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml index 08b2f4bbca89..7314d486e8ff 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無標題的節目)"</string> <string name="pip_close" msgid="9135220303720555525">"關閉子母畫面"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml index 89c7f498652d..63d9dd57fe54 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Alukho uhlelo lwesihloko)"</string> <string name="pip_close" msgid="9135220303720555525">"Vala i-PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Iskrini esigcwele"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml index 17387fa1636f..08d3cef10428 100644 --- a/libs/WindowManager/Shell/res/values/colors_tv.xml +++ b/libs/WindowManager/Shell/res/values/colors_tv.xml @@ -19,6 +19,6 @@ <color name="tv_pip_menu_icon_unfocused">#E8EAED</color> <color name="tv_pip_menu_icon_disabled">#80868B</color> <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color> - <color name="tv_pip_menu_icon_bg_unfocused">#777777</color> - <color name="tv_pip_menu_focus_border">#CCE8EAED</color> + <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color> + <color name="tv_pip_menu_focus_border">#E8EAED</color> </resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 0cdaa206c156..1b8032b7077b 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -43,6 +43,9 @@ <!-- PiP minimum size, which is a % based off the shorter side of display width and height --> <fraction name="config_pipShortestEdgePercent">40%</fraction> + <!-- Show PiP enter split icon, which allows apps to directly enter splitscreen from PiP. --> + <bool name="config_pipEnableEnterSplitButton">false</bool> + <!-- Animation duration when using long press on recents to dock --> <integer name="long_press_dock_anim_duration">250</integer> diff --git a/libs/WindowManager/Shell/res/values/strings_tv.xml b/libs/WindowManager/Shell/res/values/strings_tv.xml index 2dfdcabaa931..730d80894c41 100644 --- a/libs/WindowManager/Shell/res/values/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values/strings_tv.xml @@ -30,5 +30,8 @@ <!-- Button to move picture-in-picture (PIP) screen to the fullscreen in PIP menu [CHAR LIMIT=30] --> <string name="pip_fullscreen">Full screen</string> + + <!-- Button to move picture-in-picture (PIP) via DPAD in the PIP menu [CHAR LIMIT=30] --> + <string name="pip_move">Move PIP</string> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 22bec3d63bcf..57cb7a5a57d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -17,6 +17,8 @@ package com.android.wm.shell.bubbles; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; @@ -42,6 +44,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.Notification; +import android.app.NotificationChannel; import android.app.PendingIntent; import android.content.Context; import android.content.pm.ActivityInfo; @@ -1092,6 +1095,24 @@ public class BubbleController { } } + @VisibleForTesting + public void onNotificationChannelModified(String pkg, UserHandle user, + NotificationChannel channel, int modificationType) { + // Only query overflow bubbles here because active bubbles will have an active notification + // and channel changes we care about would result in a ranking update. + List<Bubble> overflowBubbles = new ArrayList<>(mBubbleData.getOverflowBubbles()); + for (int i = 0; i < overflowBubbles.size(); i++) { + Bubble b = overflowBubbles.get(i); + if (Objects.equals(b.getShortcutId(), channel.getConversationId()) + && b.getPackageName().equals(pkg) + && b.getUser().getIdentifier() == user.getIdentifier()) { + if (!channel.canBubble() || channel.isDeleted()) { + mBubbleData.dismissBubbleWithKey(b.getKey(), DISMISS_NO_LONGER_BUBBLE); + } + } + } + } + /** * Retrieves any bubbles that are part of the notification group represented by the provided * group key. @@ -1348,7 +1369,7 @@ public class BubbleController { mStackView.updateContentDescription(); - mStackView.updateBubblesClickableStates(); + mStackView.updateBubblesAcessibillityStates(); } @VisibleForTesting @@ -1670,6 +1691,19 @@ public class BubbleController { } @Override + public void onNotificationChannelModified(String pkg, + UserHandle user, NotificationChannel channel, int modificationType) { + // Bubbles only cares about updates or deletions. + if (modificationType == NOTIFICATION_CHANNEL_OR_GROUP_UPDATED + || modificationType == NOTIFICATION_CHANNEL_OR_GROUP_DELETED) { + mMainExecutor.execute(() -> { + BubbleController.this.onNotificationChannelModified(pkg, user, channel, + modificationType); + }); + } + } + + @Override public void onStatusBarVisibilityChanged(boolean visible) { mMainExecutor.execute(() -> { BubbleController.this.onStatusBarVisibilityChanged(visible); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index d5d8b71056d6..a477bd7f8295 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1486,19 +1486,63 @@ public class BubbleStackView extends FrameLayout } /** - * Update bubbles' icon views clickable states. + * Update bubbles' icon views accessibility states. */ - public void updateBubblesClickableStates() { + public void updateBubblesAcessibillityStates() { for (int i = 0; i < mBubbleData.getBubbles().size(); i++) { - final Bubble bubble = mBubbleData.getBubbles().get(i); - if (bubble.getIconView() != null) { - if (mIsExpanded) { - // when stack is expanded all bubbles are clickable - bubble.getIconView().setClickable(true); - } else { - // when stack is collapsed, only the top bubble needs to be clickable, - // so that a11y ignores all the inaccessible bubbles in the stack - bubble.getIconView().setClickable(i == 0); + Bubble prevBubble = i > 0 ? mBubbleData.getBubbles().get(i - 1) : null; + Bubble bubble = mBubbleData.getBubbles().get(i); + + View bubbleIconView = bubble.getIconView(); + if (bubbleIconView == null) { + continue; + } + + if (mIsExpanded) { + // when stack is expanded + // all bubbles are important for accessibility + bubbleIconView + .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + + View prevBubbleIconView = prevBubble != null ? prevBubble.getIconView() : null; + + if (prevBubbleIconView != null) { + bubbleIconView.setAccessibilityDelegate(new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View v, + AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(v, info); + info.setTraversalAfter(prevBubbleIconView); + } + }); + } + } else { + // when stack is collapsed, only the top bubble is important for accessibility, + bubbleIconView.setImportantForAccessibility( + i == 0 ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : + View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + } + + if (mIsExpanded) { + // make the overflow bubble last in the accessibility traversal order + + View bubbleOverflowIconView = + mBubbleOverflow != null ? mBubbleOverflow.getIconView() : null; + if (bubbleOverflowIconView != null && !mBubbleData.getBubbles().isEmpty()) { + Bubble lastBubble = + mBubbleData.getBubbles().get(mBubbleData.getBubbles().size() - 1); + View lastBubbleIconView = lastBubble.getIconView(); + if (lastBubbleIconView != null) { + bubbleOverflowIconView.setAccessibilityDelegate( + new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View v, + AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(v, info); + info.setTraversalAfter(lastBubbleIconView); + } + }); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index c82249b8a369..af403d23d69c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -21,9 +21,12 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.app.NotificationChannel; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Bundle; +import android.os.UserHandle; +import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; import android.util.Pair; @@ -191,10 +194,26 @@ public interface Bubbles { * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should * bubble up */ - void onRankingUpdated(RankingMap rankingMap, + void onRankingUpdated( + RankingMap rankingMap, HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey); /** + * Called when a notification channel is modified, in response to + * {@link NotificationListenerService#onNotificationChannelModified}. + * + * @param pkg the package the notification channel belongs to. + * @param user the user the notification channel belongs to. + * @param channel the channel being modified. + * @param modificationType the type of modification that occurred to the channel. + */ + void onNotificationChannelModified( + String pkg, + UserHandle user, + NotificationChannel channel, + int modificationType); + + /** * Called when the status bar has become visible or invisible (either permanently or * temporarily). */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt index 9e012598554b..aac1d0626d30 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt @@ -17,13 +17,10 @@ package com.android.wm.shell.common.magnetictarget import android.annotation.SuppressLint import android.content.Context -import android.database.ContentObserver import android.graphics.PointF -import android.os.Handler -import android.os.UserHandle +import android.os.VibrationAttributes import android.os.VibrationEffect import android.os.Vibrator -import android.provider.Settings import android.view.MotionEvent import android.view.VelocityTracker import android.view.View @@ -147,6 +144,8 @@ abstract class MagnetizedObject<T : Any>( private val velocityTracker: VelocityTracker = VelocityTracker.obtain() private val vibrator: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + private val vibrationAttributes: VibrationAttributes = VibrationAttributes.createForUsage( + VibrationAttributes.USAGE_TOUCH) private var touchDown = PointF() private var touchSlop = 0 @@ -268,10 +267,6 @@ abstract class MagnetizedObject<T : Any>( */ var flungIntoTargetSpringConfig = springConfig - init { - initHapticSettingObserver(context) - } - /** * Adds the provided MagneticTarget to this object. The object will now be attracted to the * target if it strays within its magnetic field or is flung towards it. @@ -468,8 +463,8 @@ abstract class MagnetizedObject<T : Any>( /** Plays the given vibration effect if haptics are enabled. */ @SuppressLint("MissingPermission") private fun vibrateIfEnabled(effectId: Int) { - if (hapticsEnabled && systemHapticsEnabled) { - vibrator.vibrate(VibrationEffect.createPredefined(effectId)) + if (hapticsEnabled) { + vibrator.vibrate(VibrationEffect.createPredefined(effectId), vibrationAttributes) } } @@ -622,44 +617,6 @@ abstract class MagnetizedObject<T : Any>( } companion object { - - /** - * Whether the HAPTIC_FEEDBACK_ENABLED setting is true. - * - * We put it in the companion object because we need to register a settings observer and - * [MagnetizedObject] doesn't have an obvious lifecycle so we don't have a good time to - * remove that observer. Since this settings is shared among all instances we just let all - * instances read from this value. - */ - private var systemHapticsEnabled = false - private var hapticSettingObserverInitialized = false - - private fun initHapticSettingObserver(context: Context) { - if (hapticSettingObserverInitialized) { - return - } - - val hapticSettingObserver = - object : ContentObserver(Handler.getMain()) { - override fun onChange(selfChange: Boolean) { - systemHapticsEnabled = - Settings.System.getIntForUser( - context.contentResolver, - Settings.System.HAPTIC_FEEDBACK_ENABLED, - 0, - UserHandle.USER_CURRENT) != 0 - } - } - - context.contentResolver.registerContentObserver( - Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED), - true /* notifyForDescendants */, hapticSettingObserver) - - // Trigger the observer once to initialize systemHapticsEnabled. - hapticSettingObserver.onChange(false /* selfChange */) - hapticSettingObserverInitialized = true - } - /** * Magnetizes the given view. Magnetized views are attracted to one or more magnetic * targets. Magnetic targets attract objects that are dragged near them, and hold them there diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index 711a0ac76702..f91d7e2d28e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -30,7 +30,6 @@ import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipSnapAlgorithm; @@ -39,6 +38,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipUiEventLogger; +import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; import com.android.wm.shell.pip.tv.TvPipController; import com.android.wm.shell.pip.tv.TvPipMenuController; import com.android.wm.shell.pip.tv.TvPipNotificationController; @@ -61,7 +61,7 @@ public abstract class TvPipModule { static Optional<Pip> providePip( Context context, PipBoundsState pipBoundsState, - PipBoundsAlgorithm pipBoundsAlgorithm, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipTaskOrganizer pipTaskOrganizer, TvPipMenuController tvPipMenuController, PipMediaController pipMediaController, @@ -74,7 +74,7 @@ public abstract class TvPipModule { TvPipController.create( context, pipBoundsState, - pipBoundsAlgorithm, + tvPipBoundsAlgorithm, pipTaskOrganizer, pipTransitionController, tvPipMenuController, @@ -93,9 +93,9 @@ public abstract class TvPipModule { @WMSingleton @Provides - static PipBoundsAlgorithm providePipBoundsAlgorithm(Context context, + static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context, PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) { - return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm); + return new TvPipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm); } @WMSingleton @@ -109,10 +109,11 @@ public abstract class TvPipModule { @Provides static PipTransitionController provideTvPipTransition( Transitions transitions, ShellTaskOrganizer shellTaskOrganizer, - PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm, + PipAnimationController pipAnimationController, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipBoundsState pipBoundsState, TvPipMenuController pipMenuController) { return new TvPipTransition(pipBoundsState, pipMenuController, - pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer); + tvPipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer); } @WMSingleton @@ -156,7 +157,7 @@ public abstract class TvPipModule { SyncTransactionQueue syncTransactionQueue, PipBoundsState pipBoundsState, PipTransitionState pipTransitionState, - PipBoundsAlgorithm pipBoundsAlgorithm, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipAnimationController pipAnimationController, PipTransitionController pipTransitionController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, @@ -166,7 +167,7 @@ public abstract class TvPipModule { PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, - syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm, + syncTransactionQueue, pipTransitionState, pipBoundsState, tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper, pipTransitionController, splitScreenOptional, newSplitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index cc37caec5225..7879e7a5bb00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -246,10 +246,11 @@ public class WMShellModule { PipBoundsState pipBoundsState, PipMediaController pipMediaController, SystemWindows systemWindows, Optional<SplitScreenController> splitScreenOptional, + PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, splitScreenOptional, mainExecutor, mainHandler); + systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index 8e6c05d83906..eda09e3ce0b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -62,6 +62,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; +import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -132,6 +133,8 @@ public class DragAndDropPolicy { final Rect fullscreenHitRegion = new Rect(displayRegion); final boolean inLandscape = mSession.displayLayout.isLandscape(); final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible(); + final float dividerWidth = mContext.getResources().getDimensionPixelSize( + R.dimen.split_divider_bar_width); // We allow splitting if we are already in split-screen or the running task is a standard // task in fullscreen mode. final boolean allowSplit = inSplitScreen @@ -153,8 +156,11 @@ public class DragAndDropPolicy { // If we have existing split regions use those bounds, otherwise split it 50/50 if (inSplitScreen) { + // Add the divider bounds to each side since that counts for the hit region. leftHitRegion.set(topOrLeftBounds); + leftHitRegion.right += dividerWidth / 2; rightHitRegion.set(bottomOrRightBounds); + rightHitRegion.left -= dividerWidth / 2; } else { displayRegion.splitVertically(leftHitRegion, rightHitRegion); } @@ -170,8 +176,11 @@ public class DragAndDropPolicy { // If we have existing split regions use those bounds, otherwise split it 50/50 if (inSplitScreen) { + // Add the divider bounds to each side since that counts for the hit region. topHitRegion.set(topOrLeftBounds); + topHitRegion.bottom += dividerWidth / 2; bottomHitRegion.set(bottomOrRightBounds); + bottomHitRegion.top -= dividerWidth / 2; } else { displayRegion.splitHorizontally(topHitRegion, bottomHitRegion); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java new file mode 100644 index 000000000000..c20b7d9b2747 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java @@ -0,0 +1,246 @@ +/* + * 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.wm.shell.onehanded; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; +import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; + +import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Binder; +import android.util.Slog; +import android.view.ContextThemeWrapper; +import android.view.IWindow; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.SurfaceSession; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; +import com.android.wm.shell.common.DisplayLayout; + +import java.io.PrintWriter; + +/** + * Holds view hierarchy of a root surface and helps inflate a themeable view for background. + */ +public final class BackgroundWindowManager extends WindowlessWindowManager { + private static final String TAG = BackgroundWindowManager.class.getSimpleName(); + private static final int THEME_COLOR_OFFSET = 10; + + private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory + mTransactionFactory; + + private Context mContext; + private Rect mDisplayBounds; + private SurfaceControlViewHost mViewHost; + private SurfaceControl mLeash; + private View mBackgroundView; + private @OneHandedState.State int mCurrentState; + + public BackgroundWindowManager(Context context) { + super(context.getResources().getConfiguration(), null /* rootSurface */, + null /* hostInputToken */); + mContext = context; + mTransactionFactory = SurfaceControl.Transaction::new; + } + + @Override + public SurfaceControl getSurfaceControl(IWindow window) { + return super.getSurfaceControl(window); + } + + @Override + public void setConfiguration(Configuration configuration) { + super.setConfiguration(configuration); + mContext = mContext.createConfigurationContext(configuration); + } + + /** + * onConfigurationChanged events for updating background theme color. + */ + public void onConfigurationChanged() { + if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { + updateThemeOnly(); + } + } + + /** + * One-handed mode state changed callback + * @param newState of One-handed mode representing by {@link OneHandedState} + */ + public void onStateChanged(int newState) { + mCurrentState = newState; + } + + @Override + protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) + .setColorLayer() + .setBufferSize(mDisplayBounds.width(), mDisplayBounds.height()) + .setFormat(PixelFormat.RGB_888) + .setOpaque(true) + .setName(TAG) + .setCallsite("BackgroundWindowManager#attachToParentSurface"); + mLeash = builder.build(); + b.setParent(mLeash); + } + + /** Inflates background view on to the root surface. */ + boolean initView() { + if (mBackgroundView != null || mViewHost != null) { + return false; + } + + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + mBackgroundView = (View) LayoutInflater.from(mContext) + .inflate(R.layout.background_panel, null /* root */); + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + mDisplayBounds.width(), mDisplayBounds.height(), 0 /* TYPE NONE */, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH + | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); + lp.token = new Binder(); + lp.setTitle("background-panel"); + lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + mBackgroundView.setBackgroundColor(getThemeColorForBackground()); + mViewHost.setView(mBackgroundView, lp); + return true; + } + + /** + * Called when onDisplayAdded() or onDisplayRemoved() callback. + * @param displayLayout The latest {@link DisplayLayout} for display bounds. + */ + public void onDisplayChanged(DisplayLayout displayLayout) { + // One-handed mode is only available on portrait. + if (displayLayout.height() > displayLayout.width()) { + mDisplayBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height()); + } else { + mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width()); + } + } + + private void updateThemeOnly() { + if (mBackgroundView == null || mViewHost == null || mLeash == null) { + Slog.w(TAG, "Background view or SurfaceControl does not exist when trying to " + + "update theme only!"); + return; + } + + WindowManager.LayoutParams lp = (WindowManager.LayoutParams) + mBackgroundView.getLayoutParams(); + mBackgroundView.setBackgroundColor(getThemeColorForBackground()); + mViewHost.setView(mBackgroundView, lp); + } + + /** + * Shows the background layer when One-handed mode triggered. + */ + public void showBackgroundLayer() { + if (!initView()) { + updateThemeOnly(); + return; + } + if (mLeash == null) { + Slog.w(TAG, "SurfaceControl mLeash is null, can't show One-handed mode " + + "background panel!"); + return; + } + + mTransactionFactory.getTransaction() + .setAlpha(mLeash, 1.0f) + .setLayer(mLeash, -1 /* at bottom-most layer */) + .show(mLeash) + .apply(); + } + + /** + * Remove the leash of background layer after stop One-handed mode. + */ + public void removeBackgroundLayer() { + if (mBackgroundView != null) { + mBackgroundView = null; + } + + if (mViewHost != null) { + mViewHost.release(); + mViewHost = null; + } + + if (mLeash != null) { + mTransactionFactory.getTransaction().remove(mLeash).apply(); + mLeash = null; + } + } + + /** + * Gets {@link SurfaceControl} of the background layer. + * @return {@code null} if not exist. + */ + @Nullable + SurfaceControl getSurfaceControl() { + return mLeash; + } + + private int getThemeColor() { + final Context themedContext = new ContextThemeWrapper(mContext, + com.android.internal.R.style.Theme_DeviceDefault_DayNight); + return themedContext.getColor(R.color.one_handed_tutorial_background_color); + } + + int getThemeColorForBackground() { + final int origThemeColor = getThemeColor(); + return android.graphics.Color.argb(Color.alpha(origThemeColor), + Color.red(origThemeColor) - THEME_COLOR_OFFSET, + Color.green(origThemeColor) - THEME_COLOR_OFFSET, + Color.blue(origThemeColor) - THEME_COLOR_OFFSET); + } + + private float adjustColor(int origColor) { + return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f; + } + + void dump(@NonNull PrintWriter pw) { + final String innerPrefix = " "; + pw.println(TAG); + pw.print(innerPrefix + "mDisplayBounds="); + pw.println(mDisplayBounds); + pw.print(innerPrefix + "mViewHost="); + pw.println(mViewHost); + pw.print(innerPrefix + "mLeash="); + pw.println(mLeash); + pw.print(innerPrefix + "mBackgroundView="); + pw.println(mBackgroundView); + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java deleted file mode 100644 index 9e1c61aac868..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2020 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.wm.shell.onehanded; - -import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.view.ContextThemeWrapper; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.view.animation.LinearInterpolator; -import android.window.DisplayAreaAppearedInfo; -import android.window.DisplayAreaInfo; -import android.window.DisplayAreaOrganizer; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayLayout; - -import java.io.PrintWriter; -import java.util.List; -import java.util.concurrent.Executor; - -/** - * Manages OneHanded color background layer areas. - * To avoid when turning the Dark theme on, users can not clearly identify - * the screen has entered one handed mode. - */ -public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer - implements OneHandedAnimationCallback, OneHandedState.OnStateChangedListener { - private static final String TAG = "OneHandedBackgroundPanelOrganizer"; - private static final int THEME_COLOR_OFFSET = 10; - private static final int ALPHA_ANIMATION_DURATION = 200; - - private final Context mContext; - private final SurfaceSession mSurfaceSession = new SurfaceSession(); - private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory - mTransactionFactory; - - private @OneHandedState.State int mCurrentState; - private ValueAnimator mAlphaAnimator; - - private float mTranslationFraction; - private float[] mThemeColor; - - /** - * The background to distinguish the boundary of translated windows and empty region when - * one handed mode triggered. - */ - private Rect mBkgBounds; - private Rect mStableInsets; - - @Nullable - @VisibleForTesting - SurfaceControl mBackgroundSurface; - @Nullable - private SurfaceControl mParentLeash; - - public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout, - OneHandedSettingsUtil settingsUtil, Executor executor) { - super(executor); - mContext = context; - mTranslationFraction = settingsUtil.getTranslationFraction(context); - mTransactionFactory = SurfaceControl.Transaction::new; - updateThemeColors(); - } - - @Override - public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo, - @NonNull SurfaceControl leash) { - mParentLeash = leash; - } - - @Override - public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) { - final List<DisplayAreaAppearedInfo> displayAreaInfos; - displayAreaInfos = super.registerOrganizer(displayAreaFeature); - for (int i = 0; i < displayAreaInfos.size(); i++) { - final DisplayAreaAppearedInfo info = displayAreaInfos.get(i); - onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash()); - } - return displayAreaInfos; - } - - @Override - public void unregisterOrganizer() { - super.unregisterOrganizer(); - removeBackgroundPanelLayer(); - mParentLeash = null; - } - - @Override - public void onAnimationUpdate(SurfaceControl.Transaction tx, float xPos, float yPos) { - final int yTopPos = (mStableInsets.top - mBkgBounds.height()) + Math.round(yPos); - tx.setPosition(mBackgroundSurface, 0, yTopPos); - } - - @Nullable - @VisibleForTesting - boolean isRegistered() { - return mParentLeash != null; - } - - void createBackgroundSurface() { - mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession) - .setBufferSize(mBkgBounds.width(), mBkgBounds.height()) - .setColorLayer() - .setFormat(PixelFormat.RGB_888) - .setOpaque(true) - .setName("one-handed-background-panel") - .setCallsite("OneHandedBackgroundPanelOrganizer") - .build(); - - // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash. - mAlphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f); - mAlphaAnimator.setInterpolator(new LinearInterpolator()); - mAlphaAnimator.setDuration(ALPHA_ANIMATION_DURATION); - mAlphaAnimator.addUpdateListener( - animator -> detachBackgroundFromParent(animator)); - } - - void detachBackgroundFromParent(ValueAnimator animator) { - if (mBackgroundSurface == null || mParentLeash == null) { - return; - } - // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash. - final float currentValue = (float) animator.getAnimatedValue(); - final SurfaceControl.Transaction tx = mTransactionFactory.getTransaction(); - if (currentValue == 0.0f) { - tx.reparent(mBackgroundSurface, null).apply(); - } else { - tx.setAlpha(mBackgroundSurface, (float) animator.getAnimatedValue()).apply(); - } - } - - /** - * Called when onDisplayAdded() or onDisplayRemoved() callback. - * - * @param displayLayout The latest {@link DisplayLayout} representing current displayId - */ - public void onDisplayChanged(DisplayLayout displayLayout) { - mStableInsets = displayLayout.stableInsets(); - // Ensure the mBkgBounds is portrait, due to OHM only support on portrait - if (displayLayout.height() > displayLayout.width()) { - mBkgBounds = new Rect(0, 0, displayLayout.width(), - Math.round(displayLayout.height() * mTranslationFraction) + mStableInsets.top); - } else { - mBkgBounds = new Rect(0, 0, displayLayout.height(), - Math.round(displayLayout.width() * mTranslationFraction) + mStableInsets.top); - } - } - - @VisibleForTesting - void onStart() { - if (mBackgroundSurface == null) { - createBackgroundSurface(); - } - showBackgroundPanelLayer(); - } - - /** - * Called when transition finished. - */ - public void onStopFinished() { - if (mAlphaAnimator == null) { - return; - } - mAlphaAnimator.start(); - } - - @VisibleForTesting - void showBackgroundPanelLayer() { - if (mParentLeash == null) { - return; - } - - if (mBackgroundSurface == null) { - createBackgroundSurface(); - } - - // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash. - if (mAlphaAnimator.isRunning()) { - mAlphaAnimator.end(); - } - - mTransactionFactory.getTransaction() - .reparent(mBackgroundSurface, mParentLeash) - .setAlpha(mBackgroundSurface, 1.0f) - .setLayer(mBackgroundSurface, -1 /* at bottom-most layer */) - .setColor(mBackgroundSurface, mThemeColor) - .show(mBackgroundSurface) - .apply(); - } - - @VisibleForTesting - void removeBackgroundPanelLayer() { - if (mBackgroundSurface == null) { - return; - } - - mTransactionFactory.getTransaction() - .remove(mBackgroundSurface) - .apply(); - mBackgroundSurface = null; - } - - /** - * onConfigurationChanged events for updating tutorial text. - */ - public void onConfigurationChanged() { - updateThemeColors(); - - if (mCurrentState != STATE_ACTIVE) { - return; - } - showBackgroundPanelLayer(); - } - - private void updateThemeColors() { - final Context themedContext = new ContextThemeWrapper(mContext, - com.android.internal.R.style.Theme_DeviceDefault_DayNight); - final int themeColor = themedContext.getColor( - R.color.one_handed_tutorial_background_color); - mThemeColor = new float[]{ - adjustColor(Color.red(themeColor)), - adjustColor(Color.green(themeColor)), - adjustColor(Color.blue(themeColor))}; - } - - private float adjustColor(int origColor) { - return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f; - } - - @Override - public void onStateChanged(int newState) { - mCurrentState = newState; - } - - void dump(@NonNull PrintWriter pw) { - final String innerPrefix = " "; - pw.println(TAG); - pw.print(innerPrefix + "mBackgroundSurface="); - pw.println(mBackgroundSurface); - pw.print(innerPrefix + "mBkgBounds="); - pw.println(mBkgBounds); - pw.print(innerPrefix + "mThemeColor="); - pw.println(mThemeColor); - pw.print(innerPrefix + "mTranslationFraction="); - pw.println(mTranslationFraction); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 96f82fa5ce36..48acfc1c76e7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -99,7 +99,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, private OneHandedEventCallback mEventCallback; private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer; - private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; private OneHandedUiEventLogger mOneHandedUiEventLogger; private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener = @@ -163,7 +162,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, public void onStopFinished(Rect bounds) { mState.setState(STATE_NONE); notifyShortcutStateChanged(STATE_NONE); - mBackgroundPanelOrganizer.onStopFinished(); } }; @@ -201,32 +199,28 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context); OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor); OneHandedState oneHandedState = new OneHandedState(); + BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context); OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context, - settingsUtil, windowManager); + settingsUtil, windowManager, backgroundWindowManager); OneHandedAnimationController animationController = new OneHandedAnimationController(context); OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler, mainExecutor); - OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer = - new OneHandedBackgroundPanelOrganizer(context, displayLayout, settingsUtil, - mainExecutor); OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer( context, displayLayout, settingsUtil, animationController, tutorialHandler, - oneHandedBackgroundPanelOrganizer, jankMonitor, mainExecutor); + jankMonitor, mainExecutor); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); IOverlayManager overlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); - return new OneHandedController(context, displayController, - oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler, - settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, jankMonitor, - oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor, - mainHandler); + return new OneHandedController(context, displayController, organizer, touchHandler, + tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, + jankMonitor, oneHandedUiEventsLogger, overlayManager, taskStackListener, + mainExecutor, mainHandler); } @VisibleForTesting OneHandedController(Context context, DisplayController displayController, - OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer, OneHandedDisplayAreaOrganizer displayAreaOrganizer, OneHandedTouchHandler touchHandler, OneHandedTutorialHandler tutorialHandler, @@ -243,7 +237,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mContext = context; mOneHandedSettingsUtil = settingsUtil; mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil; - mBackgroundPanelOrganizer = backgroundPanelOrganizer; mDisplayAreaOrganizer = displayAreaOrganizer; mDisplayController = displayController; mTouchHandler = touchHandler; @@ -286,7 +279,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); - mState.addSListeners(mBackgroundPanelOrganizer); mState.addSListeners(mTutorialHandler); } @@ -368,7 +360,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction); mOneHandedAccessibilityUtil.announcementForScreenReader( mOneHandedAccessibilityUtil.getOneHandedStartDescription()); - mBackgroundPanelOrganizer.onStart(); mDisplayAreaOrganizer.scheduleOffset(0, yOffSet); mTimeoutHandler.resetTimer(); mOneHandedUiEventLogger.writeEvent( @@ -461,7 +452,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, } mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout); mTutorialHandler.onDisplayChanged(newDisplayLayout); - mBackgroundPanelOrganizer.onDisplayChanged(newDisplayLayout); } private ContentObserver getObserver(Runnable onChangeRunnable) { @@ -585,7 +575,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, if (!mIsOneHandedEnabled) { mDisplayAreaOrganizer.unregisterOrganizer(); - mBackgroundPanelOrganizer.unregisterOrganizer(); // Do NOT register + unRegister DA in the same call return; } @@ -594,11 +583,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mDisplayAreaOrganizer.registerOrganizer( OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED); } - - if (!mBackgroundPanelOrganizer.isRegistered()) { - mBackgroundPanelOrganizer.registerOrganizer( - OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL); - } } @VisibleForTesting @@ -613,13 +597,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, } private void onConfigChanged(Configuration newConfig) { - if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) { + if (mTutorialHandler == null) { return; } if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { return; } - mBackgroundPanelOrganizer.onConfigurationChanged(); mTutorialHandler.onConfigurationChanged(); } @@ -650,10 +633,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, pw.print(innerPrefix + "mIsSwipeToNotificationEnabled="); pw.println(mIsSwipeToNotificationEnabled); - if (mBackgroundPanelOrganizer != null) { - mBackgroundPanelOrganizer.dump(pw); - } - if (mDisplayAreaOrganizer != null) { mDisplayAreaOrganizer.dump(pw); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index 87eb40cbde62..f61d1b95bd85 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -80,7 +80,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mSurfaceControlTransactionFactory; private OneHandedTutorialHandler mTutorialHandler; private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>(); - private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; @VisibleForTesting OneHandedAnimationCallback mOneHandedAnimationCallback = @@ -135,7 +134,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { OneHandedSettingsUtil oneHandedSettingsUtil, OneHandedAnimationController animationController, OneHandedTutorialHandler tutorialHandler, - OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer, InteractionJankMonitor jankMonitor, ShellExecutor mainExecutor) { super(mainExecutor); @@ -150,7 +148,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION, animationDurationConfig); mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; - mBackgroundPanelOrganizer = oneHandedBackgroundGradientOrganizer; mTutorialHandler = tutorialHandler; } @@ -258,7 +255,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { animator.setTransitionDirection(direction) .addOneHandedAnimationCallback(mOneHandedAnimationCallback) .addOneHandedAnimationCallback(mTutorialHandler) - .addOneHandedAnimationCallback(mBackgroundPanelOrganizer) .setDuration(durationMs) .start(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java index 88f33755fa2d..04e8cf9d2c44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java @@ -32,7 +32,6 @@ import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.os.SystemProperties; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; @@ -65,6 +64,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, private final float mTutorialHeightRatio; private final WindowManager mWindowManager; + private final BackgroundWindowManager mBackgroundWindowManager; private @OneHandedState.State int mCurrentState; private int mTutorialAreaHeight; @@ -79,9 +79,10 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, private int mAlphaAnimationDurationMs; public OneHandedTutorialHandler(Context context, OneHandedSettingsUtil settingsUtil, - WindowManager windowManager) { + WindowManager windowManager, BackgroundWindowManager backgroundWindowManager) { mContext = context; mWindowManager = windowManager; + mBackgroundWindowManager = backgroundWindowManager; mTutorialHeightRatio = settingsUtil.getTranslationFraction(context); mAlphaAnimationDurationMs = settingsUtil.getTransitionDuration(context); } @@ -110,8 +111,19 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, } @Override + public void onStartFinished(Rect bounds) { + fillBackgroundColor(); + } + + @Override + public void onStopFinished(Rect bounds) { + removeBackgroundSurface(); + } + + @Override public void onStateChanged(int newState) { mCurrentState = newState; + mBackgroundWindowManager.onStateChanged(newState); switch (newState) { case STATE_ENTERING: createViewAndAttachToWindow(mContext); @@ -126,7 +138,6 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, case STATE_NONE: checkTransitionEnd(); removeTutorialFromWindowManager(); - break; default: break; } @@ -146,6 +157,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, } mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio); mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION; + mBackgroundWindowManager.onDisplayChanged(displayLayout); } @VisibleForTesting @@ -169,6 +181,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, private void attachTargetToWindow() { try { mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams()); + mBackgroundWindowManager.showBackgroundLayer(); } catch (IllegalStateException e) { // This shouldn't happen, but if the target is already added, just update its // layout params. @@ -186,6 +199,11 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, mTargetViewContainer = null; } + @VisibleForTesting + void removeBackgroundSurface() { + mBackgroundWindowManager.removeBackgroundLayer(); + } + /** * Returns layout params for the dismiss target, using the latest display metrics. */ @@ -213,9 +231,12 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, * onConfigurationChanged events for updating tutorial text. */ public void onConfigurationChanged() { + mBackgroundWindowManager.onConfigurationChanged(); + removeTutorialFromWindowManager(); if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { createViewAndAttachToWindow(mContext); + fillBackgroundColor(); updateThemeColor(); checkTransitionEnd(); } @@ -247,6 +268,14 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, tutorialDesc.setTextColor(themedTextColorSecondary); } + private void fillBackgroundColor() { + if (mTargetViewContainer == null || mBackgroundWindowManager == null) { + return; + } + mTargetViewContainer.setBackgroundColor( + mBackgroundWindowManager.getThemeColorForBackground()); + } + private void setupAlphaTransition(boolean isEntering) { final float start = isEntering ? 0.0f : 1.0f; final float end = isEntering ? 1.0f : 0.0f; @@ -282,5 +311,9 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, pw.println(mAlphaTransitionStart); pw.print(innerPrefix + "mAlphaAnimationDurationMs="); pw.println(mAlphaAnimationDurationMs); + + if (mBackgroundWindowManager != null) { + mBackgroundWindowManager.dump(pw); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index a4b866aa3f5e..1a3c51ea4f92 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -44,7 +44,7 @@ public class PipBoundsAlgorithm { private static final String TAG = PipBoundsAlgorithm.class.getSimpleName(); private static final float INVALID_SNAP_FRACTION = -1f; - private final @NonNull PipBoundsState mPipBoundsState; + protected final @NonNull PipBoundsState mPipBoundsState; private final PipSnapAlgorithm mSnapAlgorithm; private float mDefaultSizePercent; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 4c09a4e9938f..17005ea7d500 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -397,8 +397,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - mPipUiEventLoggerLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); final WindowContainerTransaction wct = new WindowContainerTransaction(); if (ENABLE_SHELL_TRANSITIONS) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 1716380c1f7c..3e5d5f645725 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -155,7 +155,8 @@ public class PipTransition extends PipTransitionController { switch (type) { case TRANSIT_EXIT_PIP: - startExitAnimation(info, startTransaction, finishCallback, exitPipChange); + startExitAnimation(info, startTransaction, finishTransaction, finishCallback, + exitPipChange); break; case TRANSIT_EXIT_PIP_TO_SPLIT: startExitToSplitAnimation(info, startTransaction, finishTransaction, @@ -283,6 +284,7 @@ public class PipTransition extends PipTransitionController { private void startExitAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TransitionInfo.Change pipChange) { TransitionInfo.Change displayRotationChange = null; @@ -298,8 +300,8 @@ public class PipTransition extends PipTransitionController { if (displayRotationChange != null) { // Exiting PIP to fullscreen with orientation change. - startExpandAndRotationAnimation(info, startTransaction, finishCallback, - displayRotationChange, pipChange); + startExpandAndRotationAnimation(info, startTransaction, finishTransaction, + finishCallback, displayRotationChange, pipChange); return; } @@ -322,20 +324,18 @@ public class PipTransition extends PipTransitionController { private void startExpandAndRotationAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TransitionInfo.Change displayRotationChange, @NonNull TransitionInfo.Change pipChange) { final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(), displayRotationChange.getEndRotation()); - final int displayW = displayRotationChange.getEndAbsBounds().width(); - final int displayH = displayRotationChange.getEndAbsBounds().height(); // Counter-rotate all "going-away" things since they are still in the old orientation. final CounterRotatorHelper rotator = new CounterRotatorHelper(); - rotator.handleClosingChanges(info, startTransaction, rotateDelta, displayW, displayH); + rotator.handleClosingChanges(info, startTransaction, displayRotationChange); mFinishCallback = (wct, wctCB) -> { - rotator.cleanUp(); mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); finishCallback.onTransitionFinished(wct, wctCB); }; @@ -366,6 +366,7 @@ public class PipTransition extends PipTransitionController { endBounds, startBounds, new Rect(), degree, x, y, true /* isExpanding */, pipRotateDelta == ROTATION_270 /* clockwise */); startTransaction.apply(); + rotator.cleanUp(finishTransaction); // Expand and rotate the pip window to fullscreen. final PipAnimationController.PipTransitionAnimator animator = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java index a0a76d801cf4..9c23a32a7d2b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java @@ -107,7 +107,10 @@ public class PipUiEventLogger { PICTURE_IN_PICTURE_STASH_LEFT(710), @UiEvent(doc = "User stashed picture-in-picture to the right side") - PICTURE_IN_PICTURE_STASH_RIGHT(711); + PICTURE_IN_PICTURE_STASH_RIGHT(711), + + @UiEvent(doc = "User taps on the settings button in PiP menu") + PICTURE_IN_PICTURE_SHOW_SETTINGS(933); private final int mId; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 101a55d8d367..6ec8f5b924f8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -44,6 +44,7 @@ import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMediaController.ActionListener; import com.android.wm.shell.pip.PipMenuController; +import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.splitscreen.SplitScreenController; import java.io.PrintWriter; @@ -118,6 +119,7 @@ public class PhonePipMenuController implements PipMenuController { private final ArrayList<Listener> mListeners = new ArrayList<>(); private final SystemWindows mSystemWindows; private final Optional<SplitScreenController> mSplitScreenController; + private final PipUiEventLogger mPipUiEventLogger; private ParceledListSlice<RemoteAction> mAppActions; private ParceledListSlice<RemoteAction> mMediaActions; private SyncRtSurfaceTransactionApplier mApplier; @@ -150,6 +152,7 @@ public class PhonePipMenuController implements PipMenuController { public PhonePipMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController mediaController, SystemWindows systemWindows, Optional<SplitScreenController> splitScreenOptional, + PipUiEventLogger pipUiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; mPipBoundsState = pipBoundsState; @@ -158,6 +161,7 @@ public class PhonePipMenuController implements PipMenuController { mMainExecutor = mainExecutor; mMainHandler = mainHandler; mSplitScreenController = splitScreenOptional; + mPipUiEventLogger = pipUiEventLogger; } public boolean isMenuVisible() { @@ -187,7 +191,7 @@ public class PhonePipMenuController implements PipMenuController { detachPipMenuView(); } mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler, - mSplitScreenController); + mSplitScreenController, mPipUiEventLogger); mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index da4bbe81a3e6..225305bd5178 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -63,6 +63,7 @@ import android.widget.LinearLayout; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -104,8 +105,6 @@ public class PipMenuView extends FrameLayout { private static final float MENU_BACKGROUND_ALPHA = 0.3f; private static final float DISABLED_ACTION_ALPHA = 0.54f; - private static final boolean ENABLE_ENTER_SPLIT = true; - private int mMenuState; private boolean mAllowMenuTimeout = true; private boolean mAllowTouches = true; @@ -121,8 +120,9 @@ public class PipMenuView extends FrameLayout { private int mBetweenActionPaddingLand; private AnimatorSet mMenuContainerAnimator; - private PhonePipMenuController mController; - private Optional<SplitScreenController> mSplitScreenControllerOptional; + private final PhonePipMenuController mController; + private final Optional<SplitScreenController> mSplitScreenControllerOptional; + private final PipUiEventLogger mPipUiEventLogger; private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @@ -152,13 +152,15 @@ public class PipMenuView extends FrameLayout { public PipMenuView(Context context, PhonePipMenuController controller, ShellExecutor mainExecutor, Handler mainHandler, - Optional<SplitScreenController> splitScreenController) { + Optional<SplitScreenController> splitScreenController, + PipUiEventLogger pipUiEventLogger) { super(context, null, 0); mContext = context; mController = controller; mMainExecutor = mainExecutor; mMainHandler = mainHandler; mSplitScreenControllerOptional = splitScreenController; + mPipUiEventLogger = pipUiEventLogger; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); inflate(context, R.layout.pip_menu, this); @@ -277,6 +279,8 @@ public class PipMenuView extends FrameLayout { boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) { mAllowMenuTimeout = allowMenuTimeout; mDidLastShowMenuResize = resizeMenuOnShow; + final boolean enableEnterSplit = + mContext.getResources().getBoolean(R.bool.config_pipEnableEnterSplitButton); if (mMenuState != menuState) { // Disallow touches if the menu needs to resize while showing, and we are transitioning // to/from a full menu state. @@ -297,7 +301,7 @@ public class PipMenuView extends FrameLayout { mDismissButton.getAlpha(), 1f); ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, mEnterSplitButton.getAlpha(), - ENABLE_ENTER_SPLIT && mFocusedTaskAllowSplitScreen ? 1f : 0f); + enableEnterSplit && mFocusedTaskAllowSplitScreen ? 1f : 0f); if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, enterSplitAnim); @@ -539,6 +543,8 @@ public class PipMenuView extends FrameLayout { // handles the message hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* resize */, ANIM_TYPE_HIDE); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); } private void dismissPip() { @@ -547,6 +553,7 @@ public class PipMenuView extends FrameLayout { // any other dismissal that will update the touch state and fade out the PIP task // and the menu view at the same time. mController.onPipDismiss(); + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE); } } @@ -566,6 +573,7 @@ public class PipMenuView extends FrameLayout { Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivityAsUser(settingsIntent, UserHandle.of(topPipActivityInfo.second)); + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_SETTINGS); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 3ace5f405d36..350f2856e2bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -149,7 +149,6 @@ public class PipTouchHandler { @Override public void onPipDismiss() { - mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE); mTouchState.removeDoubleTapTimeoutCallback(); mMotionHelper.dismissPip(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java new file mode 100644 index 000000000000..33f3bfb7b266 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -0,0 +1,69 @@ +/* + * 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.wm.shell.pip.tv; + +import android.content.Context; +import android.graphics.Rect; +import android.util.Log; +import android.view.Gravity; + +import androidx.annotation.NonNull; + +import com.android.wm.shell.pip.PipBoundsAlgorithm; +import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.pip.PipSnapAlgorithm; + +/** + * Contains pip bounds calculations that are specific to TV. + */ +public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { + + private static final String TAG = TvPipBoundsAlgorithm.class.getSimpleName(); + private static final boolean DEBUG = false; + + public TvPipBoundsAlgorithm(Context context, + @NonNull PipBoundsState pipBoundsState, + @NonNull PipSnapAlgorithm pipSnapAlgorithm) { + super(context, pipBoundsState, pipSnapAlgorithm); + } + + /** + * The normal bounds at a different position on the screen. + */ + public Rect getTvNormalBounds(int gravity) { + Rect normalBounds = getNormalBounds(); + Rect insetBounds = new Rect(); + getInsetBounds(insetBounds); + + if (mPipBoundsState.isImeShowing()) { + if (DEBUG) Log.d(TAG, "IME showing, height: " + mPipBoundsState.getImeHeight()); + insetBounds.bottom -= mPipBoundsState.getImeHeight(); + } + + Rect result = new Rect(); + Gravity.apply(gravity, normalBounds.width(), normalBounds.height(), insetBounds, result); + + if (DEBUG) { + Log.d(TAG, "normalBounds: " + normalBounds.toShortString()); + Log.d(TAG, "insetBounds: " + insetBounds.toShortString()); + Log.d(TAG, "gravity: " + Gravity.toString(gravity)); + Log.d(TAG, "resultBounds: " + result.toShortString()); + } + + return result; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index b165706bc038..de53939c01db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -18,6 +18,10 @@ package com.android.wm.shell.pip.tv; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.KeyEvent.KEYCODE_DPAD_DOWN; +import static android.view.KeyEvent.KEYCODE_DPAD_LEFT; +import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; +import static android.view.KeyEvent.KEYCODE_DPAD_UP; import android.annotation.IntDef; import android.app.ActivityManager; @@ -33,6 +37,7 @@ import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; import android.view.DisplayInfo; +import android.view.Gravity; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; @@ -42,7 +47,6 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipTaskOrganizer; @@ -85,10 +89,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal */ private static final int STATE_PIP_MENU = 2; + private static final int DEFAULT_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT; + private final Context mContext; private final PipBoundsState mPipBoundsState; - private final PipBoundsAlgorithm mPipBoundsAlgorithm; + private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; private final TvPipNotificationController mPipNotificationController; @@ -97,6 +103,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final TvPipImpl mImpl = new TvPipImpl(); private @State int mState = STATE_NO_PIP; + private @Gravity.GravityFlags int mGravity = DEFAULT_GRAVITY; private int mPinnedTaskId = NONEXISTENT_TASK_ID; private int mResizeAnimationDuration; @@ -104,7 +111,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public static Pip create( Context context, PipBoundsState pipBoundsState, - PipBoundsAlgorithm pipBoundsAlgorithm, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, TvPipMenuController tvPipMenuController, @@ -116,7 +123,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal return new TvPipController( context, pipBoundsState, - pipBoundsAlgorithm, + tvPipBoundsAlgorithm, pipTaskOrganizer, pipTransitionController, tvPipMenuController, @@ -130,7 +137,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private TvPipController( Context context, PipBoundsState pipBoundsState, - PipBoundsAlgorithm pipBoundsAlgorithm, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, TvPipMenuController tvPipMenuController, @@ -145,7 +152,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mPipBoundsState = pipBoundsState; mPipBoundsState.setDisplayId(context.getDisplayId()); mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay())); - mPipBoundsAlgorithm = pipBoundsAlgorithm; + mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm; mPipMediaController = pipMediaController; @@ -192,24 +199,19 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void showPictureInPictureMenu() { if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), state=" + stateToName(mState)); - if (mState != STATE_PIP) { + if (mState == STATE_NO_PIP) { if (DEBUG) Log.d(TAG, " > cannot open Menu from the current state."); return; } setState(STATE_PIP_MENU); - resizePinnedStack(STATE_PIP_MENU); + movePinnedStack(); } - /** - * Moves Pip window to its "normal" position. - */ @Override - public void movePipToNormalPosition() { - if (DEBUG) Log.d(TAG, "movePipToNormalPosition(), state=" + stateToName(mState)); - + public void closeMenu() { + if (DEBUG) Log.d(TAG, "closeMenu(), state before=" + stateToName(mState)); setState(STATE_PIP); - resizePinnedStack(STATE_PIP); } /** @@ -223,50 +225,78 @@ public class TvPipController implements PipTransitionController.PipTransitionCal onPipDisappeared(); } - /** - * Closes Pip window. - */ @Override - public void closePip() { - if (DEBUG) Log.d(TAG, "closePip(), state=" + stateToName(mState)); + public void movePip(int keycode) { + if (updatePosition(keycode)) { + if (DEBUG) Log.d(TAG, "New gravity: " + Gravity.toString(mGravity)); + mTvPipMenuController.updateMenu(mGravity); + movePinnedStack(); + } else { + if (DEBUG) Log.d(TAG, "Position hasn't changed"); + } + } - removeTask(mPinnedTaskId); - onPipDisappeared(); + @Override + public int getPipGravity() { + return mGravity; } /** - * Resizes the Pip task/window to the appropriate size for the given state. - * This is a legacy API. Now we expect that the state argument passed to it should always match - * the current state of the Controller. If it does not match an {@link IllegalArgumentException} - * will be thrown. However, if the passed state does match - we'll determine the right bounds - * to the state and will move Pip task/window there. - * - * @param state the to determine the Pip bounds. IMPORTANT: should always match the current - * state of the Controller. + * @return true if position changed */ - private void resizePinnedStack(@State int state) { - if (state != mState) { - throw new IllegalArgumentException("The passed state should match the current state!"); - } - if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + stateToName(mState)); + private boolean updatePosition(int keycode) { + if (DEBUG) Log.d(TAG, "updatePosition, keycode: " + keycode); - final Rect newBounds; - switch (mState) { - case STATE_PIP_MENU: - case STATE_PIP: - // Let PipBoundsAlgorithm figure out what the correct bounds are at the moment. - // Internally, it will get the "default" bounds from PipBoundsState and adjust them - // as needed to account for things like IME state (will query PipBoundsState for - // this information as well, so it's important to keep PipBoundsState up to date). - newBounds = mPipBoundsAlgorithm.getNormalBounds(); + int updatedGravity; + switch (keycode) { + case KEYCODE_DPAD_UP: + updatedGravity = (mGravity & (~Gravity.BOTTOM)) | Gravity.TOP; + break; + case KEYCODE_DPAD_DOWN: + updatedGravity = (mGravity & (~Gravity.TOP)) | Gravity.BOTTOM; + break; + case KEYCODE_DPAD_LEFT: + updatedGravity = (mGravity & (~Gravity.RIGHT)) | Gravity.LEFT; + break; + case KEYCODE_DPAD_RIGHT: + updatedGravity = (mGravity & (~Gravity.LEFT)) | Gravity.RIGHT; break; - - case STATE_NO_PIP: default: - return; + updatedGravity = mGravity; } - mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null); + if (updatedGravity != mGravity) { + mGravity = updatedGravity; + return true; + } + return false; + } + + /** + * Animate to the updated position of the PiP based on the state and position of the PiP. + */ + private void movePinnedStack() { + if (mState == STATE_NO_PIP) { + return; + } + + Rect bounds = mTvPipBoundsAlgorithm.getTvNormalBounds(mGravity); + if (DEBUG) Log.d(TAG, "movePinnedStack() - new pip bounds: " + bounds.toShortString()); + mPipTaskOrganizer.scheduleAnimateResizePip(bounds, + mResizeAnimationDuration, rect -> { + if (DEBUG) Log.d(TAG, "movePinnedStack() animation done"); + }); + } + + /** + * Closes Pip window. + */ + @Override + public void closePip() { + if (DEBUG) Log.d(TAG, "closePip(), state=" + stateToName(mState)); + + removeTask(mPinnedTaskId); + onPipDisappeared(); } private void registerSessionListenerForCurrentUser() { @@ -298,6 +328,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mPipNotificationController.dismiss(); mTvPipMenuController.hideMenu(); + mGravity = DEFAULT_GRAVITY; setState(STATE_NO_PIP); mPinnedTaskId = NONEXISTENT_TASK_ID; } @@ -384,10 +415,9 @@ public class TvPipController implements PipTransitionController.PipTransitionCal return; } mPipBoundsState.setImeVisibility(imeVisible, imeHeight); - // "Normal" Pip bounds may have changed, so if we are in the "normal" state, - // let's update the bounds. - if (mState == STATE_PIP) { - resizePinnedStack(STATE_PIP); + + if (mState != STATE_NO_PIP) { + movePinnedStack(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java new file mode 100644 index 000000000000..927c1ec2a888 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java @@ -0,0 +1,47 @@ +/* + * 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.wm.shell.pip.tv; + +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; + +/** + * All interpolators needed for TV specific Pip animations + */ +public class TvPipInterpolators { + + /** + * A standard ease-in-out curve reserved for moments of interaction (button and card states). + */ + public static final Interpolator STANDARD = new PathInterpolator(0.2f, 0.1f, 0f, 1f); + + /** + * A sharp ease-out-expo curve created for snappy but fluid browsing between cards and clusters. + */ + public static final Interpolator BROWSE = new PathInterpolator(0.18f, 1f, 0.22f, 1f); + + /** + * A smooth ease-out-expo curve created for incoming elements (forward, back, overlay). + */ + public static final Interpolator ENTER = new PathInterpolator(0.12f, 1f, 0.4f, 1f); + + /** + * A smooth ease-in-out-expo curve created for outgoing elements (forward, back, overlay). + */ + public static final Interpolator EXIT = new PathInterpolator(0.4f, 1f, 0.12f, 1f); + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 77bfa07a425f..72ead0023366 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -24,13 +24,18 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ParceledListSlice; +import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Handler; +import android.os.RemoteException; import android.util.Log; import android.view.SurfaceControl; +import android.view.SyncRtSurfaceTransactionApplier; import androidx.annotation.Nullable; +import com.android.wm.shell.R; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; @@ -53,11 +58,33 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis private Delegate mDelegate; private SurfaceControl mLeash; - private TvPipMenuView mMenuView; + private TvPipMenuView mPipMenuView; + + // User can actively move the PiP via the DPAD. + private boolean mInMoveMode; private final List<RemoteAction> mMediaActions = new ArrayList<>(); private final List<RemoteAction> mAppActions = new ArrayList<>(); + private SyncRtSurfaceTransactionApplier mApplier; + RectF mTmpSourceRectF = new RectF(); + RectF mTmpDestinationRectF = new RectF(); + Matrix mMoveTransform = new Matrix(); + + private final float[] mTmpValues = new float[9]; + private final Runnable mUpdateEmbeddedMatrix = () -> { + if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { + return; + } + mMoveTransform.getValues(mTmpValues); + try { + mPipMenuView.getViewRootImpl().getAccessibilityEmbeddedConnection() + .setScreenMatrix(mTmpValues); + } catch (RemoteException e) { + if (DEBUG) e.printStackTrace(); + } + }; + public TvPipMenuController(Context context, PipBoundsState pipBoundsState, SystemWindows systemWindows, PipMediaController pipMediaController, Handler mainHandler) { @@ -107,13 +134,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis private void attachPipMenuView() { if (DEBUG) Log.d(TAG, "attachPipMenuView()"); - if (mMenuView != null) { + if (mPipMenuView != null) { detachPipMenuView(); } - mMenuView = new TvPipMenuView(mContext); - mMenuView.setListener(this); - mSystemWindows.addView(mMenuView, + mPipMenuView = new TvPipMenuView(mContext); + mPipMenuView.setListener(this); + mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); } @@ -122,56 +149,83 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis public void showMenu() { if (DEBUG) Log.d(TAG, "showMenu()"); - if (mMenuView != null) { - Rect pipBounds = mPipBoundsState.getBounds(); - mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams( - MENU_WINDOW_TITLE, pipBounds.width(), pipBounds.height())); + if (mPipMenuView != null) { + Rect menuBounds = getMenuBounds(mPipBoundsState.getBounds()); + mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams( + MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height())); maybeUpdateMenuViewActions(); - SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mMenuView); + SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mPipMenuView); if (menuSurfaceControl != null) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, 1); - t.setPosition(menuSurfaceControl, pipBounds.left, pipBounds.top); + t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1); + t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top); t.apply(); } - mMenuView.show(); + mPipMenuView.show(mInMoveMode, mDelegate.getPipGravity()); } } - void hideMenu() { - hideMenu(true); + void updateMenu(int gravity) { + mPipMenuView.showMovementHints(gravity); } - void hideMenu(boolean movePipWindow) { - if (DEBUG) Log.d(TAG, "hideMenu(), movePipWindow=" + movePipWindow); + private Rect getMenuBounds(Rect pipBounds) { + int extraSpaceInPx = mContext.getResources() + .getDimensionPixelSize(R.dimen.pip_menu_outer_space); + Rect menuBounds = new Rect(pipBounds); + menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx); + return menuBounds; + } + void hideMenu() { if (!isMenuVisible()) { + if (DEBUG) Log.d(TAG, "hideMenu() - Menu isn't visible, so don't hide"); return; + } else { + if (DEBUG) Log.d(TAG, "hideMenu()"); } - mMenuView.hide(); - if (movePipWindow) { - mDelegate.movePipToNormalPosition(); + mPipMenuView.hide(mInMoveMode); + if (!mInMoveMode) { + mDelegate.closeMenu(); } } @Override - public void detach() { - hideMenu(); - detachPipMenuView(); - mLeash = null; + public void onEnterMoveMode() { + if (DEBUG) Log.d(TAG, "onEnterMoveMode - " + mInMoveMode); + mInMoveMode = true; + mPipMenuView.showMenuButtons(false); + mPipMenuView.showMovementHints(mDelegate.getPipGravity()); } - private void detachPipMenuView() { - if (DEBUG) Log.d(TAG, "detachPipMenuView()"); + @Override + public boolean onExitMoveMode() { + if (DEBUG) Log.d(TAG, "onExitMoveMode - " + mInMoveMode); + if (mInMoveMode) { + mInMoveMode = false; + mPipMenuView.showMenuButtons(true); + mPipMenuView.hideMovementHints(); + return true; + } + return false; + } - if (mMenuView == null) { - return; + @Override + public boolean onPipMovement(int keycode) { + if (DEBUG) Log.d(TAG, "onPipMovement - " + mInMoveMode); + if (mInMoveMode) { + mDelegate.movePip(keycode); } + return mInMoveMode; + } - mSystemWindows.removeView(mMenuView); - mMenuView = null; + @Override + public void detach() { + hideMenu(); + detachPipMenuView(); + mLeash = null; } @Override @@ -209,24 +263,146 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } private void maybeUpdateMenuViewActions() { - if (mMenuView == null) { + if (mPipMenuView == null) { return; } if (!mAppActions.isEmpty()) { - mMenuView.setAdditionalActions(mAppActions, mMainHandler); + mPipMenuView.setAdditionalActions(mAppActions, mMainHandler); } else { - mMenuView.setAdditionalActions(mMediaActions, mMainHandler); + mPipMenuView.setAdditionalActions(mMediaActions, mMainHandler); } } @Override public boolean isMenuVisible() { - return mMenuView != null && mMenuView.isVisible(); + boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible(); + if (DEBUG) Log.d(TAG, "isMenuVisible: " + isVisible); + return isVisible; + } + + /** + * Does an immediate window crop of the PiP menu. + */ + @Override + public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash, + @android.annotation.Nullable SurfaceControl.Transaction t, + Rect destinationBounds) { + if (DEBUG) Log.d(TAG, "resizePipMenu: " + destinationBounds.toShortString()); + if (destinationBounds.isEmpty()) { + return; + } + + if (!maybeCreateSyncApplier()) { + return; + } + + SurfaceControl surfaceControl = getSurfaceControl(); + SyncRtSurfaceTransactionApplier.SurfaceParams + params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl) + .withWindowCrop(getMenuBounds(destinationBounds)) + .build(); + if (pipLeash != null && t != null) { + SyncRtSurfaceTransactionApplier.SurfaceParams + pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash) + .withMergeTransaction(t) + .build(); + mApplier.scheduleApply(params, pipParams); + } else { + mApplier.scheduleApply(params); + } + } + + private SurfaceControl getSurfaceControl() { + return mSystemWindows.getViewSurface(mPipMenuView); + } + + @Override + public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction, + Rect pipDestBounds) { + if (DEBUG) Log.d(TAG, "movePipMenu: " + pipDestBounds.toShortString()); + + if (pipDestBounds.isEmpty()) { + if (transaction == null && DEBUG) Log.d(TAG, "no transaction given"); + return; + } + if (!maybeCreateSyncApplier()) { + return; + } + + Rect menuDestBounds = getMenuBounds(pipDestBounds); + Rect mTmpSourceBounds = new Rect(); + // If there is no pip leash supplied, that means the PiP leash is already finalized + // resizing and the PiP menu is also resized. We then want to do a scale from the current + // new menu bounds. + if (pipLeash != null && transaction != null) { + if (DEBUG) Log.d(TAG, "mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()"); + mPipMenuView.getBoundsOnScreen(mTmpSourceBounds); + } else { + if (DEBUG) Log.d(TAG, "mTmpSourceBounds based on menu width and height"); + mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height()); + } + + mTmpSourceRectF.set(mTmpSourceBounds); + mTmpDestinationRectF.set(menuDestBounds); + mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); + + SurfaceControl surfaceControl = getSurfaceControl(); + SyncRtSurfaceTransactionApplier.SurfaceParams params = + new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceControl).withMatrix(mMoveTransform).build(); + + if (pipLeash != null && transaction != null) { + SyncRtSurfaceTransactionApplier.SurfaceParams + pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash) + .withMergeTransaction(transaction) + .build(); + mApplier.scheduleApply(params, pipParams); + } else { + mApplier.scheduleApply(params); + } + + if (mPipMenuView.getViewRootImpl() != null) { + mPipMenuView.getHandler().removeCallbacks(mUpdateEmbeddedMatrix); + mPipMenuView.getHandler().post(mUpdateEmbeddedMatrix); + } + } + + private boolean maybeCreateSyncApplier() { + if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { + Log.v(TAG, "Not going to move PiP, either menu or its parent is not created."); + return false; + } + + if (mApplier == null) { + mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView); + } + return true; + } + + private void detachPipMenuView() { + if (mPipMenuView == null) { + return; + } + + mApplier = null; + mSystemWindows.removeView(mPipMenuView); + mPipMenuView = null; + } + + @Override + public void updateMenuBounds(Rect destinationBounds) { + Rect menuBounds = getMenuBounds(destinationBounds); + if (DEBUG) Log.d(TAG, "updateMenuBounds: " + menuBounds.toShortString()); + mSystemWindows.updateViewLayout(mPipMenuView, + getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(), + menuBounds.height())); } @Override public void onBackPress() { - hideMenu(); + if (!onExitMoveMode()) { + hideMenu(); + } } @Override @@ -240,8 +416,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } interface Delegate { - void movePipToNormalPosition(); void movePipToFullscreen(); + + void movePip(int keycode); + + int getPipGravity(); + + void closeMenu(); + void closePip(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 4327f1590f53..0141b6a1859e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -16,17 +16,21 @@ package com.android.wm.shell.pip.tv; -import static android.animation.AnimatorInflater.loadAnimator; import static android.view.KeyEvent.ACTION_UP; import static android.view.KeyEvent.KEYCODE_BACK; +import static android.view.KeyEvent.KEYCODE_DPAD_CENTER; +import static android.view.KeyEvent.KEYCODE_DPAD_DOWN; +import static android.view.KeyEvent.KEYCODE_DPAD_LEFT; +import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; +import static android.view.KeyEvent.KEYCODE_DPAD_UP; -import android.animation.Animator; import android.app.PendingIntent; import android.app.RemoteAction; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; +import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.SurfaceControl; @@ -34,6 +38,7 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.LinearLayout; import androidx.annotation.NonNull; @@ -45,16 +50,14 @@ import java.util.ArrayList; import java.util.List; /** - * A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu - * actions: Fullscreen and Close, but could also display "additional" actions, that may be set via - * a {@link #setAdditionalActions(List, Handler)} call. + * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu + * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set + * via a {@link #setAdditionalActions(List, Handler)} call. */ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { private static final String TAG = "TvPipMenuView"; private static final boolean DEBUG = TvPipController.DEBUG; - private final Animator mFadeInAnimation; - private final Animator mFadeOutAnimation; @Nullable private Listener mListener; @@ -62,6 +65,11 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { private final View mMenuFrameView; private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>(); + private final ImageView mArrowUp; + private final ImageView mArrowRight; + private final ImageView mArrowDown; + private final ImageView mArrowLeft; + public TvPipMenuView(@NonNull Context context) { this(context, null); } @@ -85,35 +93,68 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { .setOnClickListener(this); mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button) .setOnClickListener(this); + mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button) + .setOnClickListener(this); mMenuFrameView = findViewById(R.id.tv_pip_menu_frame); - mFadeInAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_in_animation); - mFadeInAnimation.setTarget(mMenuFrameView); - mFadeOutAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_out_animation); - mFadeOutAnimation.setTarget(mMenuFrameView); + mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up); + mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right); + mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down); + mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left); } void setListener(@Nullable Listener listener) { mListener = listener; } - void show() { - if (DEBUG) Log.d(TAG, "show()"); - - mFadeInAnimation.start(); + void show(boolean inMoveMode, int gravity) { + if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode); grantWindowFocus(true); + + if (inMoveMode) { + showMovementHints(gravity); + } else { + animateAlphaTo(1, mActionButtonsContainer); + } + animateAlphaTo(1, mMenuFrameView); } - void hide() { + void hide(boolean isInMoveMode) { if (DEBUG) Log.d(TAG, "hide()"); + animateAlphaTo(0, mActionButtonsContainer); + animateAlphaTo(0, mMenuFrameView); + hideMovementHints(); - mFadeOutAnimation.start(); - grantWindowFocus(false); + if (!isInMoveMode) { + grantWindowFocus(false); + } + } + + private void animateAlphaTo(float alpha, View view) { + view.animate() + .alpha(alpha) + .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER) + .setDuration(500) + .withStartAction(() -> { + if (alpha != 0) { + view.setVisibility(VISIBLE); + } + }) + .withEndAction(() -> { + if (alpha == 0) { + view.setVisibility(GONE); + } + }); } boolean isVisible() { - return mMenuFrameView != null && mMenuFrameView.getAlpha() != 0.0f; + return mMenuFrameView.getAlpha() != 0f + || mActionButtonsContainer.getAlpha() != 0f + || mArrowUp.getAlpha() != 0f + || mArrowRight.getAlpha() != 0f + || mArrowDown.getAlpha() != 0f + || mArrowLeft.getAlpha() != 0f; } private void grantWindowFocus(boolean grantFocus) { @@ -188,6 +229,8 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { final int id = v.getId(); if (id == R.id.tv_pip_menu_fullscreen_button) { mListener.onFullscreenButtonClick(); + } else if (id == R.id.tv_pip_menu_move_button) { + mListener.onEnterMoveMode(); } else if (id == R.id.tv_pip_menu_close_button) { mListener.onCloseButtonClick(); } else { @@ -207,17 +250,79 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK - && mListener != null) { - mListener.onBackPress(); - return true; + if (DEBUG) { + Log.d(TAG, "dispatchKeyEvent, action: " + event.getAction() + + ", keycode: " + event.getKeyCode()); + } + if (mListener != null && event.getAction() == ACTION_UP) { + switch (event.getKeyCode()) { + case KEYCODE_BACK: + mListener.onBackPress(); + return true; + case KEYCODE_DPAD_UP: + case KEYCODE_DPAD_DOWN: + case KEYCODE_DPAD_LEFT: + case KEYCODE_DPAD_RIGHT: + return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent( + event); + case KEYCODE_DPAD_CENTER: + return mListener.onExitMoveMode() || super.dispatchKeyEvent(event); + default: + break; + } } return super.dispatchKeyEvent(event); } + /** + * Shows user hints for moving the PiP, e.g. arrows. + */ + public void showMovementHints(int gravity) { + if (DEBUG) Log.d(TAG, "showMovementHints(), position: " + Gravity.toString(gravity)); + + animateAlphaTo((gravity & Gravity.BOTTOM) == Gravity.BOTTOM ? 1f : 0f, mArrowUp); + animateAlphaTo((gravity & Gravity.TOP) == Gravity.TOP ? 1f : 0f, mArrowDown); + animateAlphaTo((gravity & Gravity.RIGHT) == Gravity.RIGHT ? 1f : 0f, mArrowLeft); + animateAlphaTo((gravity & Gravity.LEFT) == Gravity.LEFT ? 1f : 0f, mArrowRight); + } + + /** + * Hides user hints for moving the PiP, e.g. arrows. + */ + public void hideMovementHints() { + if (DEBUG) Log.d(TAG, "hideMovementHints()"); + animateAlphaTo(0, mArrowUp); + animateAlphaTo(0, mArrowRight); + animateAlphaTo(0, mArrowDown); + animateAlphaTo(0, mArrowLeft); + } + + /** + * Show or hide the pip user actions. + */ + public void showMenuButtons(boolean show) { + if (DEBUG) Log.d(TAG, "showMenuButtons: " + show); + animateAlphaTo(show ? 1 : 0, mActionButtonsContainer); + } + interface Listener { + void onBackPress(); + + void onEnterMoveMode(); + + /** + * @return whether move mode was exited + */ + boolean onExitMoveMode(); + + /** + * @return whether pip movement was handled. + */ + boolean onPipMovement(int keycode); + void onCloseButtonClick(); + void onFullscreenButtonClick(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index 551476dc9d54..5062cc436461 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -29,7 +29,6 @@ import androidx.annotation.Nullable; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipTransitionController; @@ -42,11 +41,11 @@ import com.android.wm.shell.transition.Transitions; public class TvPipTransition extends PipTransitionController { public TvPipTransition(PipBoundsState pipBoundsState, PipMenuController pipMenuController, - PipBoundsAlgorithm pipBoundsAlgorithm, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipAnimationController pipAnimationController, Transitions transitions, @NonNull ShellTaskOrganizer shellTaskOrganizer) { - super(pipBoundsState, pipMenuController, pipBoundsAlgorithm, pipAnimationController, + super(pipBoundsState, pipMenuController, tvPipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 7decb54d16bb..338c944f7eec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -292,7 +292,6 @@ public class RecentTasksController implements TaskStackListenerCallback, * Invalidates this instance, preventing future calls from updating the controller. */ void invalidate() { - Slog.d("b/206648922", "invalidating controller: " + mController); mController = null; } @@ -313,13 +312,16 @@ public class RecentTasksController implements TaskStackListenerCallback, @Override public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) throws RemoteException { + if (mController == null) { + // The controller is already invalidated -- just return an empty task list for now + return new GroupedRecentTaskInfo[0]; + } + final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null}; executeRemoteCallWithTaskPermission(mController, "getRecentTasks", (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) .toArray(new GroupedRecentTaskInfo[0]), true /* blocking */); - Slog.d("b/206648922", "getRecentTasks(" + maxNum + "): " + out[0] - + " mController=" + mController); return out[0]; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 0aa8d7e2f1a3..8664d9be3340 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -44,6 +44,7 @@ import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import android.window.WindowContainerTransactionCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.TransactionPool; @@ -66,28 +67,26 @@ class SplitScreenTransitions { DismissTransition mPendingDismiss = null; IBinder mPendingEnter = null; + IBinder mPendingRecent = null; private IBinder mAnimatingTransition = null; private OneShotRemoteHandler mRemoteHandler = null; - private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> { - if (wct != null || wctCB != null) { - throw new UnsupportedOperationException("finish transactions not supported yet."); - } - onFinish(); - }; + private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; /** Keeps track of currently running animations */ private final ArrayList<Animator> mAnimations = new ArrayList<>(); + private final StageCoordinator mStageCoordinator; private Transitions.TransitionFinishCallback mFinishCallback = null; private SurfaceControl.Transaction mFinishTransaction; SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, - @NonNull Runnable onFinishCallback) { + @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) { mTransactionPool = pool; mTransitions = transitions; mOnFinish = onFinishCallback; + mStageCoordinator = stageCoordinator; } void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -166,7 +165,7 @@ class SplitScreenTransitions { } } t.apply(); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); } /** Starts a transition to enter split with a remote transition animator. */ @@ -203,7 +202,26 @@ class SplitScreenTransitions { return transition; } - void onFinish() { + IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct, + Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) { + if (transition == null) { + transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler); + } + mPendingRecent = transition; + + if (remoteTransition != null) { + // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) + mRemoteHandler = new OneShotRemoteHandler( + mTransitions.getMainExecutor(), remoteTransition); + mRemoteHandler.setTransition(transition); + } + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced Enter recent panel"); + return transition; + } + + void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; mOnFinish.run(); if (mFinishTransaction != null) { @@ -211,14 +229,23 @@ class SplitScreenTransitions { mTransactionPool.release(mFinishTransaction); mFinishTransaction = null; } - mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); - mFinishCallback = null; + if (mFinishCallback != null) { + mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */); + mFinishCallback = null; + } if (mAnimatingTransition == mPendingEnter) { mPendingEnter = null; } if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { mPendingDismiss = null; } + if (mAnimatingTransition == mPendingRecent) { + // If the wct is not null while finishing recent transition, it indicates it's not + // returning to home and hence needing the wct to reorder tasks. + final boolean toHome = wct == null; + mStageCoordinator.finishRecentAnimation(toHome); + mPendingRecent = null; + } mAnimatingTransition = null; } @@ -240,7 +267,7 @@ class SplitScreenTransitions { mTransactionPool.release(transaction); mTransitions.getMainExecutor().execute(() -> { mAnimations.remove(va); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); }); }; va.addListener(new Animator.AnimatorListener() { @@ -288,7 +315,7 @@ class SplitScreenTransitions { mTransactionPool.release(transaction); mTransitions.getMainExecutor().execute(() -> { mAnimations.remove(va); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); }); }; va.addListener(new AnimatorListenerAdapter() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 83830ec56c32..c8120509f0db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -20,6 +20,7 @@ import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -236,7 +237,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, deviceStateManager.registerCallback(taskOrganizer.getExecutor(), new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged)); mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete); + mOnTransitionAnimationComplete, this); mDisplayController.addDisplayWindowListener(this); mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); @@ -266,7 +267,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTDAOrganizer.registerListener(displayId, this); mSplitLayout = splitLayout; mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete); + mOnTransitionAnimationComplete, this); mMainUnfoldController = unfoldControllerProvider.get().orElse(null); mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; @@ -1271,13 +1272,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int activityType = triggerTask.getActivityType(); if (activityType == ACTIVITY_TYPE_ASSISTANT) { // We don't want assistant panel to dismiss split screen, so do nothing. + } else if (activityType == ACTIVITY_TYPE_HOME + || activityType == ACTIVITY_TYPE_RECENTS) { + // Enter overview panel, so start recent transition. + mSplitTransitions.startRecentTransition(transition, out, this, + request.getRemoteTransition()); } else { - // Going home or occluded by the other fullscreen task, so dismiss both. + // Occluded by the other fullscreen task, so dismiss both. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); - final int exitReason = activityType == ACTIVITY_TYPE_HOME - ? EXIT_REASON_RETURN_HOME : EXIT_REASON_UNKNOWN; mSplitTransitions.startDismissTransition(transition, out, this, - STAGE_TYPE_UNDEFINED, exitReason); + STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); } } } else { @@ -1307,13 +1311,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (transition != mSplitTransitions.mPendingEnter && ( - mSplitTransitions.mPendingDismiss == null + if (transition != mSplitTransitions.mPendingEnter + && transition != mSplitTransitions.mPendingRecent + && (mSplitTransitions.mPendingDismiss == null || mSplitTransitions.mPendingDismiss.mTransition != transition)) { // Not entering or exiting, so just do some house-keeping and validation. // If we're not in split-mode, just abort so something else can handle it. - if (!isSplitScreenVisible()) return false; + if (!mMainStage.isActive()) return false; for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); @@ -1356,6 +1361,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean shouldAnimate = true; if (mSplitTransitions.mPendingEnter == transition) { shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); + } else if (mSplitTransitions.mPendingRecent == transition) { + shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); } else if (mSplitTransitions.mPendingDismiss != null && mSplitTransitions.mPendingDismiss.mTransition == transition) { shouldAnimate = startPendingDismissAnimation( @@ -1500,6 +1507,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + private boolean startPendingRecentAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { + setDividerVisibility(false, t); + return true; + } + + void finishRecentAnimation(boolean toHome) { + if (toHome) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + mSplitTransitions.startDismissTransition(null /* transition */, wct, this, + STAGE_TYPE_UNDEFINED, EXIT_REASON_RETURN_HOME); + } else { + setDividerVisibility(true, null /* t */); + } + } + private void addDividerBarToTransition(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 73f65b08a7eb..f8902c6d2cc6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -269,6 +269,8 @@ public class StartingSurfaceDrawer { // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM // flag because we do know that the next window will take input // focus, so we want to get the IME window up on top of us right away. + // Touches will only pass through to the host activity window and will be blocked from + // passing to any other windows. windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -276,9 +278,6 @@ public class StartingSurfaceDrawer { params.token = appToken; params.packageName = activityInfo.packageName; params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - // Setting as trusted overlay to let touches pass through. This is safe because this - // window is controlled by the system. - params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; if (!context.getResources().getCompatibilityInfo().supportsScreen()) { params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java index 08c99b2c4a83..19133e29de4b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java @@ -18,7 +18,9 @@ package com.android.wm.shell.transition; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import android.graphics.Rect; import android.util.ArrayMap; +import android.util.RotationUtils; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerToken; @@ -35,13 +37,21 @@ import java.util.List; */ public class CounterRotatorHelper { private final ArrayMap<WindowContainerToken, CounterRotator> mRotatorMap = new ArrayMap<>(); - private SurfaceControl mRootLeash; + private final Rect mLastDisplayBounds = new Rect(); + private int mLastRotationDelta; /** Puts the surface controls of closing changes to counter-rotated surfaces. */ public void handleClosingChanges(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - int rotateDelta, int displayW, int displayH) { - mRootLeash = info.getRootLeash(); + @NonNull TransitionInfo.Change displayRotationChange) { + final int rotationDelta = RotationUtils.deltaRotation( + displayRotationChange.getStartRotation(), displayRotationChange.getEndRotation()); + final Rect displayBounds = displayRotationChange.getEndAbsBounds(); + final int displayW = displayBounds.width(); + final int displayH = displayBounds.height(); + mLastRotationDelta = rotationDelta; + mLastDisplayBounds.set(displayBounds); + final List<TransitionInfo.Change> changes = info.getChanges(); final int numChanges = changes.size(); for (int i = numChanges - 1; i >= 0; --i) { @@ -55,7 +65,7 @@ public class CounterRotatorHelper { CounterRotator crot = mRotatorMap.get(parent); if (crot == null) { crot = new CounterRotator(); - crot.setup(startTransaction, info.getChange(parent).getLeash(), rotateDelta, + crot.setup(startTransaction, info.getChange(parent).getLeash(), rotationDelta, displayW, displayH); final SurfaceControl rotatorSc = crot.getSurface(); if (rotatorSc != null) { @@ -71,11 +81,29 @@ public class CounterRotatorHelper { } } - /** Restores to the original state, i.e. reparent back to transition root. */ - public void cleanUp() { + /** + * Returns the rotated end bounds if the change is put in previous rotation. Otherwise the + * original end bounds are returned. + */ + @NonNull + public Rect getEndBoundsInStartRotation(@NonNull TransitionInfo.Change change) { + if (mLastRotationDelta == 0) return change.getEndAbsBounds(); + final Rect rotatedBounds = new Rect(change.getEndAbsBounds()); + RotationUtils.rotateBounds(rotatedBounds, mLastDisplayBounds, mLastRotationDelta); + return rotatedBounds; + } + + /** + * Removes the counter rotation surface in the finish transaction. No need to reparent the + * children as the finish transaction should have already taken care of that. + * + * This can only be called after startTransaction for {@link #handleClosingChanges} is applied. + */ + public void cleanUp(@NonNull SurfaceControl.Transaction finishTransaction) { for (int i = mRotatorMap.size() - 1; i >= 0; --i) { - mRotatorMap.valueAt(i).cleanUp(mRootLeash); + mRotatorMap.valueAt(i).cleanUp(finishTransaction); } mRotatorMap.clear(); + mLastRotationDelta = 0; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 5833ca80d384..13e81bdb3c0b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -124,6 +124,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { /** Keeps track of the currently-running animations associated with each transition. */ private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); + private final CounterRotatorHelper mRotator = new CounterRotatorHelper(); private final Rect mInsets = new Rect(0, 0, 0, 0); private float mTransitionAnimationScaleSetting = 1.0f; @@ -277,12 +278,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final ArrayList<Animator> animations = new ArrayList<>(); mAnimations.put(transition, animations); - final CounterRotatorHelper rotator = new CounterRotatorHelper(); - final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; - rotator.cleanUp(); if (mRotationAnimation != null) { mRotationAnimation.kill(); mRotationAnimation = null; @@ -292,17 +290,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); }; - boolean requireBackgroundForTransition = false; - + @ColorInt int backgroundColorForTransition = 0; final int wallpaperTransit = getWallpaperTransitType(info); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); final boolean isTask = change.getTaskInfo() != null; if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { - int rotateDelta = change.getEndRotation() - change.getStartRotation(); - int displayW = change.getEndAbsBounds().width(); - int displayH = change.getEndAbsBounds().height(); if (info.getType() == TRANSIT_CHANGE) { boolean isSeamless = isRotationSeamless(info, mDisplayController); final int anim = getRotationAnimation(info); @@ -316,8 +310,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } else { // Opening/closing an app into a new orientation. - rotator.handleClosingChanges(info, startTransaction, rotateDelta, - displayW, displayH); + mRotator.handleClosingChanges(info, startTransaction, change); } } @@ -353,8 +346,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { Animation a = loadAnimation(info, change, wallpaperTransit); if (a != null) { - if (changeRequiresBackground(info, change)) { - requireBackgroundForTransition = true; + if (isTask) { + final @TransitionType int type = info.getType(); + final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN + || type == TRANSIT_CLOSE + || type == TRANSIT_TO_FRONT + || type == TRANSIT_TO_BACK; + if (isOpenOrCloseTransition) { + // Use the overview background as the background for the animation + final Context uiContext = ActivityThread.currentActivityThread() + .getSystemUiContext(); + backgroundColorForTransition = + uiContext.getColor(R.color.overview_background); + } } float cornerRadius = 0; @@ -366,9 +370,21 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { ScreenDecorationsUtils.getWindowCornerRadius(displayContext); } + if (a.getShowBackground()) { + // use the window's background color if provided as the background color for the + // animation - the top most window with a valid background color and + // showBackground set takes precedence. + if (change.getBackgroundColor() != 0) { + backgroundColorForTransition = change.getBackgroundColor(); + } + } + + final Rect clipRect = Transitions.isClosingType(change.getMode()) + ? mRotator.getEndBoundsInStartRotation(change) + : change.getEndAbsBounds(); startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */, - cornerRadius, change.getEndAbsBounds()); + cornerRadius, clipRect); if (info.getAnimationOptions() != null) { attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(), @@ -377,35 +393,26 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - if (requireBackgroundForTransition) { - addBackgroundToTransition(info.getRootLeash(), startTransaction, finishTransaction); + if (backgroundColorForTransition != 0) { + addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition, + startTransaction, finishTransaction); } startTransaction.apply(); + mRotator.cleanUp(finishTransaction); TransitionMetrics.getInstance().reportAnimationStart(transition); // run finish now in-case there are no animations onAnimFinish.run(); return true; } - private boolean changeRequiresBackground(TransitionInfo info, - TransitionInfo.Change change) { - final boolean isTask = change.getTaskInfo() != null; - final @TransitionType int type = info.getType(); - final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN || type == TRANSIT_CLOSE - || type == TRANSIT_TO_FRONT || type == TRANSIT_TO_BACK; - return isTask && isOpenOrCloseTransition; - } - private void addBackgroundToTransition( @NonNull SurfaceControl rootLeash, + @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction ) { - final Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext(); - final @ColorInt int overviewBackgroundColor = - uiContext.getColor(R.color.overview_background); - final Color bgColor = Color.valueOf(overviewBackgroundColor); + final Color bgColor = Color.valueOf(color); final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() }; final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder() @@ -449,6 +456,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); final int overrideType = options != null ? options.getType() : ANIM_NONE; final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true; + final Rect endBounds = Transitions.isClosingType(changeMode) + ? mRotator.getEndBoundsInStartRotation(change) + : change.getEndAbsBounds(); if (info.isKeyguardGoingAway()) { a = mTransitionAnimation.loadKeyguardExitAnimation(flags, @@ -466,8 +476,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a = new AlphaAnimation(1.f, 1.f); a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION); } else if (type == TRANSIT_RELAUNCH) { - a = mTransitionAnimation.createRelaunchAnimation( - change.getEndAbsBounds(), mInsets, change.getEndAbsBounds()); + a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds); } else if (overrideType == ANIM_CUSTOM && (canCustomContainer || options.getOverrideTaskTransition())) { a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter @@ -476,16 +485,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(); } else if (overrideType == ANIM_CLIP_REVEAL) { a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter, - change.getEndAbsBounds(), change.getEndAbsBounds(), - options.getTransitionBounds()); + endBounds, endBounds, options.getTransitionBounds()); } else if (overrideType == ANIM_SCALE_UP) { a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter, - change.getEndAbsBounds(), options.getTransitionBounds()); + endBounds, options.getTransitionBounds()); } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) { final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP; a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp, - change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(), + endBounds, type, wallpaperTransit, options.getThumbnail(), options.getTransitionBounds()); } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) { // This received a transferred starting window, so don't animate @@ -558,8 +566,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (a != null) { if (!a.isInitialized()) { - Rect end = change.getEndAbsBounds(); - a.initialize(end.width(), end.height(), end.width(), end.height()); + final int width = endBounds.width(); + final int height = endBounds.height(); + a.initialize(width, height, width, height); } a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java index b9b671635010..7f8eaf1a9b55 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java @@ -18,14 +18,11 @@ package com.android.wm.shell.util; import android.view.SurfaceControl; -import java.util.ArrayList; - /** * Utility class that takes care of counter-rotating surfaces during a transition animation. */ public class CounterRotator { - SurfaceControl mSurface = null; - ArrayList<SurfaceControl> mRotateChildren = null; + private SurfaceControl mSurface = null; /** Gets the surface with the counter-rotation. */ public SurfaceControl getSurface() { @@ -41,7 +38,6 @@ public class CounterRotator { public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta, float displayW, float displayH) { if (rotateDelta == 0) return; - mRotateChildren = new ArrayList<>(); // We want to counter-rotate, so subtract from 4 rotateDelta = 4 - (rotateDelta + 4) % 4; mSurface = new SurfaceControl.Builder() @@ -64,24 +60,19 @@ public class CounterRotator { } /** - * Add a surface that needs to be counter-rotate. + * Adds a surface that needs to be counter-rotate. */ public void addChild(SurfaceControl.Transaction t, SurfaceControl child) { if (mSurface == null) return; t.reparent(child, mSurface); - mRotateChildren.add(child); } /** - * Clean-up. This undoes any reparenting and effectively stops the counter-rotation. + * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the + * counter rotation surface. */ - public void cleanUp(SurfaceControl rootLeash) { + public void cleanUp(SurfaceControl.Transaction finishTransaction) { if (mSurface == null) return; - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = mRotateChildren.size() - 1; i >= 0; --i) { - t.reparent(mRotateChildren.get(i), rootLeash); - } - t.remove(mSurface); - t.apply(); + finishTransaction.remove(mSurface); } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index 8e6fa5f1314c..7e232ea32181 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -66,7 +66,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( stringExtras: Map<String, String> ) { super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras) - wmHelper.waitFor("hasPipWindow") { it.wmState.hasPipWindow() } + wmHelper.waitPipShown() } private fun focusOnObject(selector: BySelector): Boolean { @@ -88,7 +88,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( clickObject(ENTER_PIP_BUTTON_ID) // Wait on WMHelper or simply wait for 3 seconds - wmHelper?.waitFor("hasPipWindow") { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000) + wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000) // when entering pip, the dismiss button is visible at the start. to ensure the pip // animation is complete, wait until the pip dismiss button is no longer visible. // b/176822698: dismiss-only state will be removed in the future @@ -148,7 +148,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( } // Wait for animation to complete. - wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } + wmHelper.waitPipGone() wmHelper.waitForHomeActivityVisible() } @@ -165,7 +165,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( ?: error("PIP window expand button not found") val expandButtonBounds = expandPipObject.visibleBounds uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY()) - wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } + wmHelper.waitPipGone() wmHelper.waitForAppTransitionIdle() } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index 2c02d2c183c6..fb1004bda0cb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -27,7 +27,6 @@ import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.exitSplitScreen -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview import com.android.server.wm.flicker.helpers.setRotation @@ -41,7 +40,6 @@ import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible import com.android.wm.shell.flicker.helpers.SimpleAppHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -112,11 +110,7 @@ class LegacySplitScreenToLauncher( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index 7d7add48c898..264d482426cb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -29,7 +29,6 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.resizeSplitScreen import com.android.server.wm.flicker.helpers.setRotation @@ -45,7 +44,6 @@ import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.helpers.SimpleAppHelper import com.android.wm.shell.flicker.testapp.Components -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -137,11 +135,7 @@ class ResizeLegacySplitScreen( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Test fun topAppLayerIsAlwaysVisible() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt index 5678899dbbae..d703ea082c87 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales @@ -35,7 +34,6 @@ import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -79,11 +77,7 @@ class RotateOneLaunchedAppAndEnterSplitScreen( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt index c2edf9dd5db2..6b1883914e59 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales @@ -35,7 +34,6 @@ import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -78,11 +76,7 @@ class RotateOneLaunchedAppInSplitScreenMode( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt index 777998c99e75..acd658b5ba56 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation @@ -37,7 +36,6 @@ import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -88,11 +86,7 @@ class RotateTwoLaunchedAppAndEnterSplitScreen( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt index 914b11d51529..b40be8b5f401 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation @@ -37,7 +36,6 @@ import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -93,11 +91,7 @@ class RotateTwoLaunchedAppInSplitScreenMode( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @FlakyTest @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index d3bb0082be07..db94de238a5d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -26,9 +27,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -93,11 +92,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() /** * Checks [pipApp] window remains visible throughout the animation @@ -187,13 +182,14 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { } /** - * Checks the focus doesn't change during the animation + * Checks that the focus changes between the [pipApp] window and the launcher when + * closing the pip window */ - @FlakyTest + @Postsubmit @Test - fun focusDoesNotChange() { + fun focusChanges() { testSpec.assertEventLog { - this.focusDoesNotChange() + this.focusChanges(pipApp.`package`, "NexusLauncherActivity") } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index fa9fbcd53ed1..dee13c182a95 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -27,7 +27,6 @@ import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.traces.common.FlickerComponentName @@ -36,7 +35,6 @@ import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Com import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -100,7 +98,7 @@ class EnterPipToOtherOrientationTest( // Enter PiP, and assert that the PiP is within bounds now that the device is back // in portrait broadcastActionTrigger.doAction(ACTION_ENTER_PIP) - wmHelper.waitFor { it.wmState.hasPipWindow() } + wmHelper.waitPipShown() wmHelper.waitForAppTransitionIdle() // during rotation the status bar becomes invisible and reappears at the end wmHelper.waitForNavBarStatusBarVisible() @@ -121,11 +119,7 @@ class EnterPipToOtherOrientationTest( */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() /** * Checks that all parts of the screen are covered at the start and end of the transition diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt index f8a3aff98276..990872f58dc1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -18,9 +18,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.helpers.FixedAppHelper -import org.junit.Assume.assumeFalse import org.junit.Test /** @@ -29,15 +27,6 @@ import org.junit.Test abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { protected val testApp = FixedAppHelper(instrumentation) - /** {@inheritDoc} */ - @Presubmit - @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } - /** * Checks that the pip app window remains inside the display bounds throughout the whole * animation diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt index 9a220070db01..173140d77cb6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.dsl.FlickerBuilder @@ -96,14 +96,13 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition } /** - * Checks that the focus changes between the [pipApp] window and the launcher when - * closing the pip window + * Checks that the focus doesn't change between windows during the transition */ - @FlakyTest(bugId = 151179149) + @Postsubmit @Test - open fun focusChanges() { + open fun focusDoesNotChange() { testSpec.assertEventLog { - this.focusChanges(pipApp.launcherName, "NexusLauncherActivity") + this.focusDoesNotChange() } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt index 2231d8864f14..2c08b7f5fac2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -24,8 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -82,11 +80,7 @@ class ExitPipViaExpandButtonClickTest( /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index fcac2c782d07..e340f4cd8595 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -24,9 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -101,11 +99,7 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 8e6603b3cc30..6524182e9082 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -24,9 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerRotatesScales -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -65,7 +63,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti val pipCenterY = pipRegion.centerY() val displayCenterX = device.displayWidth / 2 device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 10) - wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } + wmHelper.waitPipGone() wmHelper.waitForWindowSurfaceDisappeared(pipApp.component) wmHelper.waitForAppTransitionIdle() } @@ -81,11 +79,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index ef9ff4fc63c2..8d14f70357b1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -148,7 +149,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition /** * Checks that the focus doesn't change between windows during the transition */ - @FlakyTest + @Postsubmit @Test fun focusDoesNotChange() { testSpec.assertEventLog { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt index f9e180e8ec61..e4150241d42c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -24,8 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume.assumeFalse import com.android.server.wm.flicker.traces.region.RegionSubject import org.junit.FixMethodOrder import org.junit.Test @@ -85,11 +83,7 @@ class MovePipDownShelfHeightChangeTest( /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index b7bfa1b2df88..4a4c46c596a2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -25,8 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -85,11 +83,7 @@ class MovePipUpShelfHeightChangeTest( /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index 93a4e1be3bb7..bb66f7bbc01f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -125,13 +125,13 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { removeAllTasksButHome() if (!eachRun) { pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) - wmHelper.waitFor { it.wmState.hasPipWindow() } + wmHelper.waitPipShown() } } eachRun { if (eachRun) { pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) - wmHelper.waitFor { it.wmState.hasPipWindow() } + wmHelper.waitPipShown() } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java new file mode 100644 index 000000000000..f3f70673b332 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java @@ -0,0 +1,61 @@ +/** + * 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.wm.shell.onehanded; + +import static com.google.common.truth.Truth.assertThat; + +import android.testing.TestableLooper; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for {@link BackgroundWindowManager} */ +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidJUnit4.class) +public class BackgroundWindowManagerTest extends ShellTestCase { + private BackgroundWindowManager mBackgroundWindowManager; + @Mock + private DisplayLayout mMockDisplayLayout; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mBackgroundWindowManager = new BackgroundWindowManager(mContext); + mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout); + } + + @Test + @UiThreadTest + public void testInitRelease() { + mBackgroundWindowManager.initView(); + assertThat(mBackgroundWindowManager.getSurfaceControl()).isNotNull(); + + mBackgroundWindowManager.removeBackgroundLayer(); + assertThat(mBackgroundWindowManager.getSurfaceControl()).isNull(); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java deleted file mode 100644 index 7b9553c5ef9b..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2020 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.wm.shell.onehanded; - -import static android.view.Display.DEFAULT_DISPLAY; -import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; - -import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; -import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.Display; -import android.view.SurfaceControl; -import android.window.DisplayAreaInfo; -import android.window.IWindowContainerToken; -import android.window.WindowContainerToken; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.DisplayLayout; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase { - private DisplayAreaInfo mDisplayAreaInfo; - private Display mDisplay; - private DisplayLayout mDisplayLayout; - private OneHandedBackgroundPanelOrganizer mSpiedBackgroundPanelOrganizer; - private WindowContainerToken mToken; - private SurfaceControl mLeash; - - @Mock - IWindowContainerToken mMockRealToken; - @Mock - DisplayController mMockDisplayController; - @Mock - OneHandedSettingsUtil mMockSettingsUtil; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mToken = new WindowContainerToken(mMockRealToken); - mLeash = new SurfaceControl(); - mDisplay = mContext.getDisplay(); - mDisplayLayout = new DisplayLayout(mContext, mDisplay); - when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); - mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, - FEATURE_ONE_HANDED_BACKGROUND_PANEL); - - mSpiedBackgroundPanelOrganizer = spy( - new OneHandedBackgroundPanelOrganizer(mContext, mDisplayLayout, mMockSettingsUtil, - Runnable::run)); - mSpiedBackgroundPanelOrganizer.onDisplayChanged(mDisplayLayout); - } - - @Test - public void testOnDisplayAreaAppeared() { - mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); - - assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isTrue(); - verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer(); - } - - @Test - public void testShowBackgroundLayer() { - mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, null); - mSpiedBackgroundPanelOrganizer.onStart(); - - verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer(); - } - - @Test - public void testRemoveBackgroundLayer() { - mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); - - assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isNotNull(); - - reset(mSpiedBackgroundPanelOrganizer); - mSpiedBackgroundPanelOrganizer.removeBackgroundPanelLayer(); - - assertThat(mSpiedBackgroundPanelOrganizer.mBackgroundSurface).isNull(); - } - - @Test - public void testStateNone_onConfigurationChanged() { - mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_NONE); - mSpiedBackgroundPanelOrganizer.onConfigurationChanged(); - - verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer(); - } - - @Test - public void testStateActivate_onConfigurationChanged() { - mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_ACTIVE); - mSpiedBackgroundPanelOrganizer.onConfigurationChanged(); - - verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer(); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 636e875bed7e..2886b97a3020 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -73,8 +73,6 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock DisplayController mMockDisplayController; @Mock - OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; - @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock OneHandedEventCallback mMockEventCallback; @@ -115,7 +113,6 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(null); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true); - when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true); when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn( mDefaultEnabled); when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn( @@ -134,7 +131,6 @@ public class OneHandedControllerTest extends OneHandedTestCase { mSpiedOneHandedController = spy(new OneHandedController( mContext, mMockDisplayController, - mMockBackgroundOrganizer, mMockDisplayAreaOrganizer, mMockTouchHandler, mMockTutorialHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index df21163c68cd..9c7f7237871a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -95,8 +95,6 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { @Mock WindowContainerTransaction mMockWindowContainerTransaction; @Mock - OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; - @Mock ShellExecutor mMockShellMainExecutor; @Mock OneHandedSettingsUtil mMockSettingsUitl; @@ -143,7 +141,6 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockSettingsUitl, mMockAnimationController, mTutorialHandler, - mMockBackgroundOrganizer, mJankMonitor, mMockShellMainExecutor)); @@ -431,7 +428,6 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockSettingsUitl, mMockAnimationController, mTutorialHandler, - mMockBackgroundOrganizer, mJankMonitor, mMockShellMainExecutor)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java index 58399b6444fa..dba1b8b86261 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java @@ -67,8 +67,6 @@ public class OneHandedStateTest extends OneHandedTestCase { @Mock DisplayController mMockDisplayController; @Mock - OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; - @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock OneHandedTouchHandler mMockTouchHandler; @@ -105,7 +103,6 @@ public class OneHandedStateTest extends OneHandedTestCase { when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); - when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true); when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn( mDefaultEnabled); when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn( @@ -123,7 +120,6 @@ public class OneHandedStateTest extends OneHandedTestCase { mSpiedOneHandedController = spy(new OneHandedController( mContext, mMockDisplayController, - mMockBackgroundOrganizer, mMockDisplayAreaOrganizer, mMockTouchHandler, mMockTutorialHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java index b1434ca325b7..63d8bfd1e7ef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java @@ -56,6 +56,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { OneHandedSettingsUtil mMockSettingsUtil; @Mock WindowManager mMockWindowManager; + @Mock + BackgroundWindowManager mMockBackgroundWindowManager; @Before public void setUp() { @@ -63,10 +65,11 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { when(mMockSettingsUtil.getTutorialShownCounts(any(), anyInt())).thenReturn(0); mDisplay = mContext.getDisplay(); - mDisplayLayout = new DisplayLayout(mContext, mDisplay); + mDisplayLayout = new DisplayLayout(getTestContext().getApplicationContext(), mDisplay); mSpiedTransitionState = spy(new OneHandedState()); mSpiedTutorialHandler = spy( - new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager)); + new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager, + mMockBackgroundWindowManager)); mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index ea94cf0f7597..59c377a3e13d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -251,7 +251,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test - public void testDismissToHome() { + public void testEnterRecents() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() @@ -264,7 +264,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); - assertTrue(containsSplitExit(result)); + assertTrue(result.isEmpty()); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible()); @@ -284,7 +284,7 @@ public class SplitTransitionTests extends ShellTestCase { mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); - assertFalse(mStageCoordinator.isSplitScreenVisible()); + assertTrue(mStageCoordinator.isSplitScreenVisible()); } @Test diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp index 3eedda88fdce..d87a3ce72177 100644 --- a/libs/androidfw/Locale.cpp +++ b/libs/androidfw/Locale.cpp @@ -29,40 +29,33 @@ using ::android::StringPiece; namespace android { -void LocaleValue::set_language(const char* language_chars) { +template <size_t N, class Transformer> +static void safe_transform_copy(const char* source, char (&dest)[N], Transformer t) { size_t i = 0; - while ((*language_chars) != '\0') { - language[i++] = ::tolower(*language_chars); - language_chars++; + while (i < N && (*source) != '\0') { + dest[i++] = t(i, *source); + source++; + } + while (i < N) { + dest[i++] = '\0'; } } +void LocaleValue::set_language(const char* language_chars) { + safe_transform_copy(language_chars, language, [](size_t, char c) { return ::tolower(c); }); +} + void LocaleValue::set_region(const char* region_chars) { - size_t i = 0; - while ((*region_chars) != '\0') { - region[i++] = ::toupper(*region_chars); - region_chars++; - } + safe_transform_copy(region_chars, region, [](size_t, char c) { return ::toupper(c); }); } void LocaleValue::set_script(const char* script_chars) { - size_t i = 0; - while ((*script_chars) != '\0') { - if (i == 0) { - script[i++] = ::toupper(*script_chars); - } else { - script[i++] = ::tolower(*script_chars); - } - script_chars++; - } + safe_transform_copy(script_chars, script, + [](size_t i, char c) { return i ? ::tolower(c) : ::toupper(c); }); } void LocaleValue::set_variant(const char* variant_chars) { - size_t i = 0; - while ((*variant_chars) != '\0') { - variant[i++] = *variant_chars; - variant_chars++; - } + safe_transform_copy(variant_chars, variant, [](size_t, char c) { return c; }); } static inline bool is_alpha(const std::string& str) { @@ -234,6 +227,10 @@ ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter, return static_cast<ssize_t>(iter - start_iter); } +// Make sure the following memcpy's are properly sized. +static_assert(sizeof(ResTable_config::localeScript) == sizeof(LocaleValue::script)); +static_assert(sizeof(ResTable_config::localeVariant) == sizeof(LocaleValue::variant)); + void LocaleValue::InitFromResTable(const ResTable_config& config) { config.unpackLanguage(language); config.unpackRegion(region); diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index b8029087cb4f..e359145feef7 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -95,6 +95,16 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, endHyphen, advances); } +minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, + const Typeface* typeface, const uint16_t* buf, + size_t start, size_t count, size_t bufSize) { + minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); + const minikin::U16StringPiece textBuf(buf, bufSize); + const minikin::Range range(start, start + count); + + return minikin::getFontExtent(textBuf, range, bidiFlags, minikinPaint); +} + bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) { const Typeface* resolvedFace = Typeface::resolveDefault(typeface); return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs); diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index a15803ad2dca..009b84b140ea 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -56,6 +56,10 @@ public: size_t start, size_t count, size_t bufSize, float* advances); + static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, + const Typeface* typeface, const uint16_t* buf, + size_t start, size_t count, size_t bufSize); + static bool hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs); diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp index b7ddd211b0de..08fc80fbdafd 100644 --- a/libs/hwui/jni/NinePatch.cpp +++ b/libs/hwui/jni/NinePatch.cpp @@ -67,7 +67,7 @@ public: size_t chunkSize = obj != NULL ? env->GetArrayLength(obj) : 0; if (chunkSize < (int) (sizeof(Res_png_9patch))) { jniThrowRuntimeException(env, "Array too small for chunk."); - return NULL; + return 0; } int8_t* storage = new int8_t[chunkSize]; diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 22a1e1fd94b9..f76863255153 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -541,26 +541,6 @@ namespace PaintGlue { return result; } - // ------------------ @FastNative --------------------------- - - static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { - Paint* obj = reinterpret_cast<Paint*>(objHandle); - ScopedUtfChars localesChars(env, locales); - jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str()); - obj->setMinikinLocaleListId(minikinLocaleListId); - return minikinLocaleListId; - } - - static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - if (!settings) { - paint->setFontFeatureSettings(std::string()); - } else { - ScopedUtfChars settingsChars(env, settings); - paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size())); - } - } - static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) { const int kElegantTop = 2500; const int kElegantBottom = -1000; @@ -593,6 +573,67 @@ namespace PaintGlue { return spacing; } + static void doFontExtent(JNIEnv* env, jlong paintHandle, const jchar buf[], jint start, + jint count, jint bufSize, jboolean isRtl, jobject fmi) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + minikin::MinikinExtent extent = + MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize); + + SkFontMetrics metrics; + getMetricsInternal(paintHandle, &metrics); + + metrics.fAscent = extent.ascent; + metrics.fDescent = extent.descent; + + // If top/bottom is narrower than ascent/descent, adjust top/bottom to ascent/descent. + metrics.fTop = std::min(metrics.fAscent, metrics.fTop); + metrics.fBottom = std::max(metrics.fDescent, metrics.fBottom); + + GraphicsJNI::set_metrics_int(env, fmi, metrics); + } + + static void getFontMetricsIntForText___C(JNIEnv* env, jclass, jlong paintHandle, + jcharArray text, jint start, jint count, jint ctxStart, + jint ctxCount, jboolean isRtl, jobject fmi) { + ScopedCharArrayRO textArray(env, text); + + doFontExtent(env, paintHandle, textArray.get() + ctxStart, start - ctxStart, count, + ctxCount, isRtl, fmi); + } + + static void getFontMetricsIntForText___String(JNIEnv* env, jclass, jlong paintHandle, + jstring text, jint start, jint count, + jint ctxStart, jint ctxCount, jboolean isRtl, + jobject fmi) { + ScopedStringChars textChars(env, text); + + doFontExtent(env, paintHandle, textChars.get() + ctxStart, start - ctxStart, count, + ctxCount, isRtl, fmi); + } + + // ------------------ @FastNative --------------------------- + + static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + ScopedUtfChars localesChars(env, locales); + jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str()); + obj->setMinikinLocaleListId(minikinLocaleListId); + return minikinLocaleListId; + } + + static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, + jstring settings) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + if (!settings) { + paint->setFontFeatureSettings(std::string()); + } else { + ScopedUtfChars settingsChars(env, settings); + paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size())); + } + } + static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { SkFontMetrics metrics; SkScalar spacing = getMetricsInternal(paintHandle, &metrics); @@ -1015,6 +1056,11 @@ static const JNINativeMethod methods[] = { {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F}, {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I}, + {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___C}, + {"nGetFontMetricsIntForText", + "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___String}, // --------------- @FastNative ---------------------- @@ -1093,6 +1139,7 @@ static const JNINativeMethod methods[] = { {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, }; + int register_android_graphics_Paint(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods)); } diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index c4366f756a21..c505b53b2df1 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -64,7 +64,8 @@ static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) { /////////////////////////////////////////////////////////////////////////////////////////////// static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, - jint tileModeX, jint tileModeY, bool filter) { + jint tileModeX, jint tileModeY, bool filter, + bool isDirectSampled) { const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); sk_sp<SkImage> image; if (bitmapHandle) { @@ -79,8 +80,12 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j } SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest, SkMipmapMode::kNone); - sk_sp<SkShader> shader = image->makeShader( - (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); + sk_sp<SkShader> shader; + if (isDirectSampled) { + shader = image->makeRawShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); + } else { + shader = image->makeShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); + } ThrowIAE_IfNull(env, shader.get()); if (matrix) { @@ -393,7 +398,7 @@ static const JNINativeMethod gShaderMethods[] = { }; static const JNINativeMethod gBitmapShaderMethods[] = { - { "nativeCreate", "(JJIIZ)J", (void*)BitmapShader_constructor }, + {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor}, }; static const JNINativeMethod gLinearGradientMethods[] = { diff --git a/libs/hwui/jni/android_util_PathParser.cpp b/libs/hwui/jni/android_util_PathParser.cpp index 72995efb1c21..8cbb70ed2c86 100644 --- a/libs/hwui/jni/android_util_PathParser.cpp +++ b/libs/hwui/jni/android_util_PathParser.cpp @@ -61,7 +61,7 @@ static jlong createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr } else { delete pathData; doThrowIAE(env, result.failureMessage.c_str()); - return NULL; + return 0; } } diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp index bd9bd7121f8a..09539ecc34b0 100644 --- a/libs/hwui/jni/text/MeasuredText.cpp +++ b/libs/hwui/jni/text/MeasuredText.cpp @@ -65,11 +65,11 @@ static jlong nInitBuilder(CRITICAL_JNI_PARAMS) { // Regular JNI static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, - jlong paintPtr, jint start, jint end, jboolean isRtl) { + jlong paintPtr, jint lbStyle, jint start, jint end, jboolean isRtl) { Paint* paint = toPaint(paintPtr); const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface()); minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); - toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), isRtl); + toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), lbStyle, isRtl); } // Regular JNI @@ -144,7 +144,7 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { static const JNINativeMethod gMTBuilderMethods[] = { // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*)nInitBuilder}, - {"nAddStyleRun", "(JJIIZ)V", (void*)nAddStyleRun}, + {"nAddStyleRun", "(JJIIIZ)V", (void*)nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun}, {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText}, {"nFreeBuilder", "(J)V", (void*)nFreeBuilder}, diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 6755b7c081f4..d86237789b11 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -119,7 +119,7 @@ void RenderThread::extendedFrameCallback(const AChoreographerFrameCallbackData* AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData); int64_t vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId( cbData, preferredFrameTimelineIndex); - int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadline( + int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos( cbData, preferredFrameTimelineIndex); int64_t frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(cbData); // TODO(b/193273294): Remove when shared memory in use w/ expected present time always current. diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 55f932dffff1..6c0fd5f65359 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -55,6 +55,7 @@ cc_library_shared { "-Wall", "-Wextra", "-Werror", + "-Wthread-safety", ], } diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index f43586f8d9d0..1dc74e5f7740 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -18,11 +18,13 @@ //#define LOG_NDEBUG 0 #include "PointerController.h" -#include "PointerControllerContext.h" #include <SkBlendMode.h> #include <SkCanvas.h> #include <SkColor.h> +#include <android-base/thread_annotations.h> + +#include "PointerControllerContext.h" namespace android { @@ -36,8 +38,18 @@ const ui::Transform kIdentityTransform; void PointerController::DisplayInfoListener::onWindowInfosChanged( const std::vector<android::gui::WindowInfo>&, - const std::vector<android::gui::DisplayInfo>& displayInfo) { - mPointerController.onDisplayInfosChanged(displayInfo); + const std::vector<android::gui::DisplayInfo>& displayInfos) { + std::scoped_lock lock(mLock); + if (mPointerController == nullptr) return; + + // PointerController uses DisplayInfoListener's lock. + base::ScopedLockAssertion assumeLocked(mPointerController->getLock()); + mPointerController->onDisplayInfosChangedLocked(displayInfos); +} + +void PointerController::DisplayInfoListener::onPointerControllerDestroyed() { + std::scoped_lock lock(mLock); + mPointerController = nullptr; } // --- PointerController --- @@ -68,16 +80,36 @@ std::shared_ptr<PointerController> PointerController::create( PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, const sp<SpriteController>& spriteController) + : PointerController( + policy, looper, spriteController, + [](const sp<android::gui::WindowInfosListener>& listener) { + SurfaceComposerClient::getDefault()->addWindowInfosListener(listener); + }, + [](const sp<android::gui::WindowInfosListener>& listener) { + SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener); + }) {} + +PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, + const sp<Looper>& looper, + const sp<SpriteController>& spriteController, + WindowListenerConsumer registerListener, + WindowListenerConsumer unregisterListener) : mContext(policy, looper, spriteController, *this), mCursorController(mContext), - mDisplayInfoListener(new DisplayInfoListener(*this)) { - std::scoped_lock lock(mLock); + mDisplayInfoListener(new DisplayInfoListener(this)), + mUnregisterWindowInfosListener(std::move(unregisterListener)) { + std::scoped_lock lock(getLock()); mLocked.presentation = Presentation::SPOT; - SurfaceComposerClient::getDefault()->addWindowInfosListener(mDisplayInfoListener); + registerListener(mDisplayInfoListener); } PointerController::~PointerController() { - SurfaceComposerClient::getDefault()->removeWindowInfosListener(mDisplayInfoListener); + mDisplayInfoListener->onPointerControllerDestroyed(); + mUnregisterWindowInfosListener(mDisplayInfoListener); +} + +std::mutex& PointerController::getLock() const { + return mDisplayInfoListener->mLock; } bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX, @@ -89,7 +121,7 @@ void PointerController::move(float deltaX, float deltaY) { const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); const auto& transform = getTransformForDisplayLocked(displayId); transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); } @@ -108,7 +140,7 @@ void PointerController::setPosition(float x, float y) { const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); const auto& transform = getTransformForDisplayLocked(displayId); transformed = transform.transform(x, y); } @@ -119,7 +151,7 @@ void PointerController::getPosition(float* outX, float* outY) const { const int32_t displayId = mCursorController.getDisplayId(); mCursorController.getPosition(outX, outY); { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); const auto& transform = getTransformForDisplayLocked(displayId); const auto xy = transform.inverse().transform(*outX, *outY); *outX = xy.x; @@ -132,17 +164,17 @@ int32_t PointerController::getDisplayId() const { } void PointerController::fade(Transition transition) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); mCursorController.fade(transition); } void PointerController::unfade(Transition transition) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); mCursorController.unfade(transition); } void PointerController::setPresentation(Presentation presentation) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); if (mLocked.presentation == presentation) { return; @@ -162,7 +194,7 @@ void PointerController::setPresentation(Presentation presentation) { void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; const ui::Transform& transform = getTransformForDisplayLocked(displayId); @@ -185,11 +217,11 @@ void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t } void PointerController::clearSpots() { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); clearSpotsLocked(); } -void PointerController::clearSpotsLocked() REQUIRES(mLock) { +void PointerController::clearSpotsLocked() { for (auto& [displayID, spotController] : mLocked.spotControllers) { spotController.clearSpots(); } @@ -200,7 +232,7 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout } void PointerController::reloadPointerResources() { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); for (auto& [displayID, spotController] : mLocked.spotControllers) { spotController.reloadSpotResources(); @@ -216,7 +248,7 @@ void PointerController::reloadPointerResources() { } void PointerController::setDisplayViewport(const DisplayViewport& viewport) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); bool getAdditionalMouseResources = false; if (mLocked.presentation == PointerController::Presentation::POINTER) { @@ -226,12 +258,12 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { } void PointerController::updatePointerIcon(int32_t iconId) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); mCursorController.updatePointerIcon(iconId); } void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); mCursorController.setCustomPointerIcon(icon); } @@ -245,7 +277,7 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& displayIdSet.insert(viewport.displayId); } - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) { int32_t displayID = it->first; if (!displayIdSet.count(displayID)) { @@ -261,8 +293,8 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& } } -void PointerController::onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfo) { - std::scoped_lock lock(mLock); +void PointerController::onDisplayInfosChangedLocked( + const std::vector<gui::DisplayInfo>& displayInfo) { mLocked.mDisplayInfos = displayInfo; } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 796077f6c38c..2e6e851ee15a 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -72,13 +72,31 @@ public: void reloadPointerResources(); void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports); - void onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfos); + void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos) + REQUIRES(getLock()); + +protected: + using WindowListenerConsumer = + std::function<void(const sp<android::gui::WindowInfosListener>&)>; + + // Constructor used to test WindowInfosListener registration. + PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, + const sp<SpriteController>& spriteController, + WindowListenerConsumer registerListener, + WindowListenerConsumer unregisterListener); private: + PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, + const sp<SpriteController>& spriteController); + friend PointerControllerContext::LooperCallback; friend PointerControllerContext::MessageHandler; - mutable std::mutex mLock; + // PointerController's DisplayInfoListener can outlive the PointerController because when the + // listener is registered, a strong pointer to the listener (which can extend its lifecycle) + // is given away. To avoid the small overhead of using two separate locks in these two objects, + // we use the DisplayInfoListener's lock in PointerController. + std::mutex& getLock() const; PointerControllerContext mContext; @@ -89,24 +107,28 @@ private: std::vector<gui::DisplayInfo> mDisplayInfos; std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; - } mLocked GUARDED_BY(mLock); + } mLocked GUARDED_BY(getLock()); class DisplayInfoListener : public gui::WindowInfosListener { public: - explicit DisplayInfoListener(PointerController& pc) : mPointerController(pc){}; + explicit DisplayInfoListener(PointerController* pc) : mPointerController(pc){}; void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&, const std::vector<android::gui::DisplayInfo>&) override; + void onPointerControllerDestroyed(); + + // This lock is also used by PointerController. See PointerController::getLock(). + std::mutex mLock; private: - PointerController& mPointerController; + PointerController* mPointerController GUARDED_BY(mLock); }; + sp<DisplayInfoListener> mDisplayInfoListener; + const WindowListenerConsumer mUnregisterWindowInfosListener; - const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(mLock); + const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock()); - PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - const sp<SpriteController>& spriteController); - void clearSpotsLocked(); + void clearSpotsLocked() REQUIRES(getLock()); }; } // namespace android diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index b67088a389b6..dae1fccec804 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -255,4 +255,36 @@ TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) { ensureDisplayViewportIsSet(); } +class PointerControllerWindowInfoListenerTest : public Test {}; + +class TestPointerController : public PointerController { +public: + TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener, + const sp<Looper>& looper) + : PointerController( + new MockPointerControllerPolicyInterface(), looper, + new NiceMock<MockSpriteController>(looper), + [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { + // Register listener + registeredListener = listener; + }, + [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { + // Unregister listener + if (registeredListener == listener) registeredListener = nullptr; + }) {} +}; + +TEST_F(PointerControllerWindowInfoListenerTest, + doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) { + sp<android::gui::WindowInfosListener> registeredListener; + sp<android::gui::WindowInfosListener> localListenerCopy; + { + TestPointerController pointerController(registeredListener, new Looper(false)); + ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered"; + localListenerCopy = registeredListener; + } + EXPECT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered"; + localListenerCopy->onWindowInfosChanged({}, {}); +} + } // namespace android diff --git a/libs/usb/tests/accessorytest/f_accessory.h b/libs/usb/tests/accessorytest/f_accessory.h index 312f4ba6eed3..75e017c16674 100644 --- a/libs/usb/tests/accessorytest/f_accessory.h +++ b/libs/usb/tests/accessorytest/f_accessory.h @@ -1,148 +1,53 @@ -/* - * Gadget Function Driver for Android USB accessories - * - * Copyright (C) 2011 Google, Inc. - * Author: Mike Lockwood <lockwood@android.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef __LINUX_USB_F_ACCESSORY_H -#define __LINUX_USB_F_ACCESSORY_H - -/* Use Google Vendor ID when in accessory mode */ +/**************************************************************************** + **************************************************************************** + *** + *** This header was automatically generated from a Linux kernel header + *** of the same name, to make information necessary for userspace to + *** call into the kernel available to libc. It contains only constants, + *** structures, and macros generated from the original header, and thus, + *** contains no copyrightable information. + *** + *** To edit the content of this header, modify the corresponding + *** source file (e.g. under external/kernel-headers/original/) then + *** run bionic/libc/kernel/tools/update_all.py + *** + *** Any manual change here will be lost the next time this script will + *** be run. You've been warned! + *** + **************************************************************************** + ****************************************************************************/ +#ifndef _UAPI_LINUX_USB_F_ACCESSORY_H +#define _UAPI_LINUX_USB_F_ACCESSORY_H #define USB_ACCESSORY_VENDOR_ID 0x18D1 - - -/* Product ID to use when in accessory mode */ #define USB_ACCESSORY_PRODUCT_ID 0x2D00 - -/* Product ID to use when in accessory mode and adb is enabled */ +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ #define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01 - -/* Indexes for strings sent by the host via ACCESSORY_SEND_STRING */ -#define ACCESSORY_STRING_MANUFACTURER 0 -#define ACCESSORY_STRING_MODEL 1 -#define ACCESSORY_STRING_DESCRIPTION 2 -#define ACCESSORY_STRING_VERSION 3 -#define ACCESSORY_STRING_URI 4 -#define ACCESSORY_STRING_SERIAL 5 - -/* Control request for retrieving device's protocol version - * - * requestType: USB_DIR_IN | USB_TYPE_VENDOR - * request: ACCESSORY_GET_PROTOCOL - * value: 0 - * index: 0 - * data version number (16 bits little endian) - * 1 for original accessory support - * 2 adds audio and HID support - */ -#define ACCESSORY_GET_PROTOCOL 51 - -/* Control request for host to send a string to the device - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_SEND_STRING - * value: 0 - * index: string ID - * data zero terminated UTF8 string - * - * The device can later retrieve these strings via the - * ACCESSORY_GET_STRING_* ioctls - */ -#define ACCESSORY_SEND_STRING 52 - -/* Control request for starting device in accessory mode. - * The host sends this after setting all its strings to the device. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_START - * value: 0 - * index: 0 - * data none - */ -#define ACCESSORY_START 53 - -/* Control request for registering a HID device. - * Upon registering, a unique ID is sent by the accessory in the - * value parameter. This ID will be used for future commands for - * the device - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_REGISTER_HID_DEVICE - * value: Accessory assigned ID for the HID device - * index: total length of the HID report descriptor - * data none - */ -#define ACCESSORY_REGISTER_HID 54 - -/* Control request for unregistering a HID device. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_REGISTER_HID - * value: Accessory assigned ID for the HID device - * index: 0 - * data none - */ -#define ACCESSORY_UNREGISTER_HID 55 - -/* Control request for sending the HID report descriptor. - * If the HID descriptor is longer than the endpoint zero max packet size, - * the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC - * commands. The data for the descriptor must be sent sequentially - * if multiple packets are needed. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_SET_HID_REPORT_DESC - * value: Accessory assigned ID for the HID device - * index: offset of data in descriptor - * (needed when HID descriptor is too big for one packet) - * data the HID report descriptor - */ -#define ACCESSORY_SET_HID_REPORT_DESC 56 - -/* Control request for sending HID events. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_SEND_HID_EVENT - * value: Accessory assigned ID for the HID device - * index: 0 - * data the HID report for the event - */ -#define ACCESSORY_SEND_HID_EVENT 57 - -/* Control request for setting the audio mode. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_SET_AUDIO_MODE - * value: 0 - no audio - * 1 - device to host, 44100 16-bit stereo PCM - * index: 0 - * data the HID report for the event - */ -#define ACCESSORY_SET_AUDIO_MODE 58 - - - -/* ioctls for retrieving strings set by the host */ -#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256]) -#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256]) -#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256]) -#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256]) -#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256]) -#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256]) -/* returns 1 if there is a start request pending */ -#define ACCESSORY_IS_START_REQUESTED _IO('M', 7) -/* returns audio mode (set via the ACCESSORY_SET_AUDIO_MODE control request) */ -#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8) - -#endif /* __LINUX_USB_F_ACCESSORY_H */ +#define ACCESSORY_STRING_MANUFACTURER 0 +#define ACCESSORY_STRING_MODEL 1 +#define ACCESSORY_STRING_DESCRIPTION 2 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_STRING_VERSION 3 +#define ACCESSORY_STRING_URI 4 +#define ACCESSORY_STRING_SERIAL 5 +#define ACCESSORY_GET_PROTOCOL 51 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_SEND_STRING 52 +#define ACCESSORY_START 53 +#define ACCESSORY_REGISTER_HID 54 +#define ACCESSORY_UNREGISTER_HID 55 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_SET_HID_REPORT_DESC 56 +#define ACCESSORY_SEND_HID_EVENT 57 +#define ACCESSORY_SET_AUDIO_MODE 58 +#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256]) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256]) +#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256]) +#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256]) +#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256]) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256]) +#define ACCESSORY_IS_START_REQUESTED _IO('M', 7) +#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8) +#endif +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java index e4a0d0caceca..a15834407315 100644 --- a/location/java/android/location/Geocoder.java +++ b/location/java/android/location/Geocoder.java @@ -298,6 +298,7 @@ public final class Geocoder { * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box * @param upperRightLatitude the latitude of the upper right corner of the bounding box * @param upperRightLongitude the longitude of the upper right corner of the bounding box + * @param listener a listener for receiving results * * @throws IllegalArgumentException if locationName is null * @throws IllegalArgumentException if any latitude or longitude is invalid diff --git a/location/java/android/location/GnssAutomaticGainControl.aidl b/location/java/android/location/GnssAutomaticGainControl.aidl new file mode 100644 index 000000000000..8298cb711064 --- /dev/null +++ b/location/java/android/location/GnssAutomaticGainControl.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.location; + +parcelable GnssAutomaticGainControl; diff --git a/location/java/android/location/GnssAutomaticGainControl.java b/location/java/android/location/GnssAutomaticGainControl.java new file mode 100644 index 000000000000..e4f7304a8c9d --- /dev/null +++ b/location/java/android/location/GnssAutomaticGainControl.java @@ -0,0 +1,215 @@ +/* + * 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 android.location; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * A class that contains GNSS Automatic Gain Control (AGC) information. + * + * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC + * level may be used to indicate potential interference. Higher gain (and/or lower input power) + * shall be output as a positive number. Hence in cases of strong jamming, in the band of this + * signal, this value will go more negative. This value must be consistent given the same level + * of the incoming signal power. + * + * <p> Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW + * components) may also affect the typical output of this value on any given hardware design + * in an open sky test - the important aspect of this output is that changes in this value are + * indicative of changes on input signal power in the frequency band for this measurement. + */ +public final class GnssAutomaticGainControl implements Parcelable { + private final double mLevelDb; + private final int mConstellationType; + private final long mCarrierFrequencyHz; + + /** + * Creates a {@link GnssAutomaticGainControl} with a full list of parameters. + */ + private GnssAutomaticGainControl(double levelDb, int constellationType, + long carrierFrequencyHz) { + mLevelDb = levelDb; + mConstellationType = constellationType; + mCarrierFrequencyHz = carrierFrequencyHz; + } + + /** + * Gets the Automatic Gain Control level in dB. + */ + @FloatRange(from = -10000, to = 10000) + public double getLevelDb() { + return mLevelDb; + } + + /** + * Gets the constellation type. + * + * <p>The return value is one of those constants with {@code CONSTELLATION_} prefix in + * {@link GnssStatus}. + */ + @GnssStatus.ConstellationType + public int getConstellationType() { + return mConstellationType; + } + + /** + * Gets the carrier frequency of the tracked signal. + * + * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz, + * L5 = 1176.45 MHz, varying GLO channels, etc. + * + * @return the carrier frequency of the signal tracked in Hz. + */ + @IntRange(from = 0) + public long getCarrierFrequencyHz() { + return mCarrierFrequencyHz; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flag) { + parcel.writeDouble(mLevelDb); + parcel.writeInt(mConstellationType); + parcel.writeLong(mCarrierFrequencyHz); + } + + @NonNull + public static final Creator<GnssAutomaticGainControl> CREATOR = + new Creator<GnssAutomaticGainControl>() { + @Override + @NonNull + public GnssAutomaticGainControl createFromParcel(@NonNull Parcel parcel) { + return new GnssAutomaticGainControl(parcel.readDouble(), parcel.readInt(), + parcel.readLong()); + } + + @Override + public GnssAutomaticGainControl[] newArray(int i) { + return new GnssAutomaticGainControl[i]; + } + }; + + @NonNull + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("GnssAutomaticGainControl["); + s.append("Level=").append(mLevelDb).append(" dB"); + s.append(" Constellation=").append( + GnssStatus.constellationTypeToString(mConstellationType)); + s.append(" CarrierFrequency=").append(mCarrierFrequencyHz).append(" Hz"); + s.append(']'); + return s.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GnssAutomaticGainControl)) { + return false; + } + + GnssAutomaticGainControl other = (GnssAutomaticGainControl) obj; + if (Double.compare(mLevelDb, other.mLevelDb) + != 0) { + return false; + } + if (mConstellationType != other.mConstellationType) { + return false; + } + if (mCarrierFrequencyHz != other.mCarrierFrequencyHz) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return Objects.hash(mLevelDb, mConstellationType, mCarrierFrequencyHz); + } + + /** Builder for {@link GnssAutomaticGainControl} */ + public static final class Builder { + private double mLevelDb; + private int mConstellationType; + private long mCarrierFrequencyHz; + + /** + * Constructs a {@link GnssAutomaticGainControl.Builder} instance. + */ + public Builder() { + } + + /** + * Constructs a {@link GnssAutomaticGainControl.Builder} instance by copying a + * {@link GnssAutomaticGainControl}. + */ + public Builder(@NonNull GnssAutomaticGainControl agc) { + mLevelDb = agc.getLevelDb(); + mConstellationType = agc.getConstellationType(); + mCarrierFrequencyHz = agc.getCarrierFrequencyHz(); + } + + /** + * Sets the Automatic Gain Control level in dB. + */ + @NonNull + public Builder setLevelDb(@FloatRange(from = -10000, to = 10000) double levelDb) { + Preconditions.checkArgument(levelDb >= -10000 && levelDb <= 10000); + mLevelDb = levelDb; + return this; + } + + /** + * Sets the constellation type. + */ + @NonNull + public Builder setConstellationType(@GnssStatus.ConstellationType int constellationType) { + mConstellationType = constellationType; + return this; + } + + /** + * Sets the Carrier frequency in Hz. + */ + @NonNull public Builder setCarrierFrequencyHz(@IntRange(from = 0) long carrierFrequencyHz) { + Preconditions.checkArgumentNonnegative(carrierFrequencyHz); + mCarrierFrequencyHz = carrierFrequencyHz; + return this; + } + + /** Builds a {@link GnssAutomaticGainControl} instance as specified by this builder. */ + @NonNull + public GnssAutomaticGainControl build() { + return new GnssAutomaticGainControl(mLevelDb, mConstellationType, mCarrierFrequencyHz); + } + } +} diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java index 2c94820d50c1..ab3dafe9cec7 100644 --- a/location/java/android/location/GnssMeasurement.java +++ b/location/java/android/location/GnssMeasurement.java @@ -1381,7 +1381,10 @@ public final class GnssMeasurement implements Parcelable { /** * Returns {@code true} if {@link #getAutomaticGainControlLevelDb()} is available, * {@code false} otherwise. + * + * @deprecated Use {@link GnssMeasurementsEvent#getGnssAutomaticGainControls()} instead. */ + @Deprecated public boolean hasAutomaticGainControlLevelDb() { return isFlagSet(HAS_AUTOMATIC_GAIN_CONTROL); } @@ -1401,7 +1404,10 @@ public final class GnssMeasurement implements Parcelable { * indicative of changes on input signal power in the frequency band for this measurement. * * <p> The value is only available if {@link #hasAutomaticGainControlLevelDb()} is {@code true} + * + * @deprecated Use {@link GnssMeasurementsEvent#getGnssAutomaticGainControls()} instead. */ + @Deprecated public double getAutomaticGainControlLevelDb() { return mAutomaticGainControlLevelInDb; } @@ -1409,7 +1415,9 @@ public final class GnssMeasurement implements Parcelable { /** * Sets the Automatic Gain Control level in dB. * @hide + * @deprecated Use {@link GnssMeasurementsEvent.Builder#setGnssAutomaticGainControls()} instead. */ + @Deprecated @TestApi public void setAutomaticGainControlLevelInDb(double agcLevelDb) { setFlag(HAS_AUTOMATIC_GAIN_CONTROL); diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java index b744017027b7..0397740d104e 100644 --- a/location/java/android/location/GnssMeasurementsEvent.java +++ b/location/java/android/location/GnssMeasurementsEvent.java @@ -18,16 +18,19 @@ package android.location; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.TestApi; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.security.InvalidParameterException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; /** * A class implementing a container for data associated with a measurement event. @@ -35,7 +38,8 @@ import java.util.Collections; */ public final class GnssMeasurementsEvent implements Parcelable { private final GnssClock mClock; - private final Collection<GnssMeasurement> mReadOnlyMeasurements; + private final List<GnssMeasurement> mMeasurements; + private final List<GnssAutomaticGainControl> mGnssAgcs; /** * Used for receiving GNSS satellite measurements from the GNSS engine. @@ -116,20 +120,13 @@ public final class GnssMeasurementsEvent implements Parcelable { } /** - * @hide + * Create a {@link GnssMeasurementsEvent} instance with a full list of parameters. */ - @TestApi - public GnssMeasurementsEvent(GnssClock clock, GnssMeasurement[] measurements) { - if (clock == null) { - throw new InvalidParameterException("Parameter 'clock' must not be null."); - } - if (measurements == null || measurements.length == 0) { - mReadOnlyMeasurements = Collections.emptyList(); - } else { - Collection<GnssMeasurement> measurementCollection = Arrays.asList(measurements); - mReadOnlyMeasurements = Collections.unmodifiableCollection(measurementCollection); - } - + private GnssMeasurementsEvent(@NonNull GnssClock clock, + @NonNull List<GnssMeasurement> measurements, + @NonNull List<GnssAutomaticGainControl> agcs) { + mMeasurements = measurements; + mGnssAgcs = agcs; mClock = clock; } @@ -143,26 +140,31 @@ public final class GnssMeasurementsEvent implements Parcelable { } /** - * Gets a read-only collection of measurements associated with the current event. + * Gets the collection of measurements associated with the current event. */ @NonNull public Collection<GnssMeasurement> getMeasurements() { - return mReadOnlyMeasurements; + return mMeasurements; + } + + /** + * Gets the collection of {@link GnssAutomaticGainControl} associated with the + * current event. + */ + @NonNull + public Collection<GnssAutomaticGainControl> getGnssAutomaticGainControls() { + return mGnssAgcs; } public static final @android.annotation.NonNull Creator<GnssMeasurementsEvent> CREATOR = new Creator<GnssMeasurementsEvent>() { @Override public GnssMeasurementsEvent createFromParcel(Parcel in) { - ClassLoader classLoader = getClass().getClassLoader(); - - GnssClock clock = in.readParcelable(classLoader, android.location.GnssClock.class); - - int measurementsLength = in.readInt(); - GnssMeasurement[] measurementsArray = new GnssMeasurement[measurementsLength]; - in.readTypedArray(measurementsArray, GnssMeasurement.CREATOR); - - return new GnssMeasurementsEvent(clock, measurementsArray); + GnssClock clock = in.readParcelable(getClass().getClassLoader(), android.location.GnssClock.class); + List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR); + List<GnssAutomaticGainControl> agcs = in.createTypedArrayList( + GnssAutomaticGainControl.CREATOR); + return new GnssMeasurementsEvent(clock, measurements, agcs); } @Override @@ -179,28 +181,105 @@ public final class GnssMeasurementsEvent implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mClock, flags); - - int measurementsCount = mReadOnlyMeasurements.size(); - GnssMeasurement[] measurementsArray = - mReadOnlyMeasurements.toArray(new GnssMeasurement[measurementsCount]); - parcel.writeInt(measurementsArray.length); - parcel.writeTypedArray(measurementsArray, flags); + parcel.writeTypedList(mMeasurements); + parcel.writeTypedList(mGnssAgcs); } @Override public String toString() { - StringBuilder builder = new StringBuilder("[ GnssMeasurementsEvent:\n\n"); + StringBuilder builder = new StringBuilder("GnssMeasurementsEvent["); + builder.append(mClock); + builder.append(' ').append(mMeasurements.toString()); + builder.append(' ').append(mGnssAgcs.toString()); + builder.append("]"); + return builder.toString(); + } - builder.append(mClock.toString()); - builder.append("\n"); + /** Builder for {@link GnssMeasurementsEvent} */ + public static final class Builder { + private GnssClock mClock; + private List<GnssMeasurement> mMeasurements; + private List<GnssAutomaticGainControl> mGnssAgcs; - for (GnssMeasurement measurement : mReadOnlyMeasurements) { - builder.append(measurement.toString()); - builder.append("\n"); + /** + * Constructs a {@link GnssMeasurementsEvent.Builder} instance. + */ + public Builder() { + mClock = new GnssClock(); + mMeasurements = new ArrayList<>(); + mGnssAgcs = new ArrayList<>(); } - builder.append("]"); + /** + * Constructs a {@link GnssMeasurementsEvent.Builder} instance by copying a + * {@link GnssMeasurementsEvent}. + */ + public Builder(@NonNull GnssMeasurementsEvent event) { + mClock = event.getClock(); + mMeasurements = (List<GnssMeasurement>) event.getMeasurements(); + mGnssAgcs = (List<GnssAutomaticGainControl>) event.getGnssAutomaticGainControls(); + } - return builder.toString(); + /** + * Sets the {@link GnssClock}. + */ + @NonNull + public Builder setClock(@NonNull GnssClock clock) { + Preconditions.checkNotNull(clock); + mClock = clock; + return this; + } + + /** + * Sets the collection of {@link GnssMeasurement}. + * + * This API exists for JNI since it is easier for JNI to work with an array than a + * collection. + * @hide + */ + @NonNull + public Builder setMeasurements(@Nullable GnssMeasurement... measurements) { + mMeasurements = measurements == null ? Collections.emptyList() : Arrays.asList( + measurements); + return this; + } + + /** + * Sets the collection of {@link GnssMeasurement}. + */ + @NonNull + public Builder setMeasurements(@NonNull Collection<GnssMeasurement> measurements) { + mMeasurements = new ArrayList<>(measurements); + return this; + } + + /** + * Sets the collection of {@link GnssAutomaticGainControl}. + * + * This API exists for JNI since it is easier for JNI to work with an array than a + * collection. + * @hide + */ + @NonNull + public Builder setGnssAutomaticGainControls(@Nullable GnssAutomaticGainControl... agcs) { + mGnssAgcs = agcs == null ? Collections.emptyList() : Arrays.asList(agcs); + return this; + } + + /** + * Sets the collection of {@link GnssAutomaticGainControl}. + */ + @NonNull + public Builder setGnssAutomaticGainControls( + @NonNull Collection<GnssAutomaticGainControl> agcs) { + mGnssAgcs = new ArrayList<>(agcs); + return this; + } + + /** Builds a {@link GnssMeasurementsEvent} instance as specified by this builder. */ + @NonNull + public GnssMeasurementsEvent build() { + return new GnssMeasurementsEvent(mClock, mMeasurements, mGnssAgcs); + } } } diff --git a/media/OWNERS b/media/OWNERS index 0aff43e00671..5f501372666b 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -3,7 +3,6 @@ elaurent@google.com essick@google.com etalvala@google.com hdmoon@google.com -hkuang@google.com hunga@google.com insun@google.com jaewan@google.com diff --git a/media/aidl/android/media/audio/common/AudioContentType.aidl b/media/aidl/android/media/audio/common/AudioContentType.aidl index 50ac181adcb2..f42ae2fedc52 100644 --- a/media/aidl/android/media/audio/common/AudioContentType.aidl +++ b/media/aidl/android/media/audio/common/AudioContentType.aidl @@ -50,4 +50,8 @@ enum AudioContentType { * in a game. These sounds are mostly synthesized or short Foley sounds. */ SONIFICATION = 4, + /** + * Content type value to use when the content type is ultrasound. + */ + ULTRASOUND = 1997, } diff --git a/media/aidl/android/media/audio/common/AudioInputFlags.aidl b/media/aidl/android/media/audio/common/AudioInputFlags.aidl index e4b6ec242ef4..83a5d9dc98e4 100644 --- a/media/aidl/android/media/audio/common/AudioInputFlags.aidl +++ b/media/aidl/android/media/audio/common/AudioInputFlags.aidl @@ -59,4 +59,8 @@ enum AudioInputFlags { * Input contains an encoded audio stream. */ DIRECT = 7, + /** + * Input is for capturing "ultrasound" audio commands. + */ + ULTRASOUND = 8, } diff --git a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl index 050503647d33..a46229da76ce 100644 --- a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl +++ b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl @@ -97,4 +97,8 @@ enum AudioOutputFlags { * tracks. */ GAPLESS_OFFLOAD = 15, + /** + * Output is used for transmitting ultrasound audio. + */ + ULTRASOUND = 16, } diff --git a/media/aidl/android/media/audio/common/AudioSource.aidl b/media/aidl/android/media/audio/common/AudioSource.aidl index 527ee39b3267..77799946c04a 100644 --- a/media/aidl/android/media/audio/common/AudioSource.aidl +++ b/media/aidl/android/media/audio/common/AudioSource.aidl @@ -87,4 +87,8 @@ enum AudioSource { * hotword detection. Same tuning as VOICE_RECOGNITION. */ HOTWORD = 1999, + /** Microphone audio source for ultrasound sound if available, + * behaves like DEFAULT otherwise. + */ + ULTRASOUND = 2000, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl index 3798b8253766..f9ac61426fa3 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl @@ -40,4 +40,5 @@ enum AudioContentType { MUSIC = 2, MOVIE = 3, SONIFICATION = 4, + ULTRASOUND = 1997, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl index 8a5dae0cc612..37aa64ae4cee 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl @@ -43,4 +43,5 @@ enum AudioInputFlags { VOIP_TX = 5, HW_AV_SYNC = 6, DIRECT = 7, + ULTRASOUND = 8, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl index ed16d177e18c..e2f286e775d2 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl @@ -51,4 +51,5 @@ enum AudioOutputFlags { VOIP_RX = 13, INCALL_MUSIC = 14, GAPLESS_OFFLOAD = 15, + ULTRASOUND = 16, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl index d1dfe416d692..acf822e5e0a5 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl @@ -50,4 +50,5 @@ enum AudioSource { ECHO_REFERENCE = 1997, FM_TUNER = 1998, HOTWORD = 1999, + ULTRASOUND = 2000, } diff --git a/media/java/Android.bp b/media/java/Android.bp index eeaf6e9015ac..c7c1d54cd3c6 100644 --- a/media/java/Android.bp +++ b/media/java/Android.bp @@ -8,7 +8,7 @@ package { } filegroup { - name: "framework-media-sources", + name: "framework-media-non-updatable-sources", srcs: [ "**/*.java", "**/*.aidl", diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 85e49cc5430b..ded9597b68ef 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -101,6 +101,13 @@ public final class AudioAttributes implements Parcelable { * or short Foley sounds. */ public final static int CONTENT_TYPE_SONIFICATION = 4; + /** + * @hide + * Content type value to use when the content type is ultrasound. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) + public static final int CONTENT_TYPE_ULTRASOUND = 1997; /** * Invalid value, only ever used for an uninitialized usage value @@ -958,6 +965,26 @@ public final class AudioAttributes implements Parcelable { } /** + * @hide + * Sets the attribute describing the content type of the audio signal, such as speech, + * , music or ultrasound. + * @param contentType the content type values. + * @return the same Builder instance. + */ + @SystemApi + public @NonNull Builder setInternalContentType(@AttrInternalContentType int contentType) { + switch (contentType) { + case CONTENT_TYPE_ULTRASOUND: + mContentType = contentType; + break; + default: + setContentType(contentType); + break; + } + return this; + } + + /** * Sets the combination of flags. * * This is a bitwise OR with the existing flags. @@ -1234,7 +1261,8 @@ public final class AudioAttributes implements Parcelable { /** * @hide * Same as {@link #setCapturePreset(int)} but authorizes the use of HOTWORD, - * REMOTE_SUBMIX, RADIO_TUNER, VOICE_DOWNLINK, VOICE_UPLINK, VOICE_CALL and ECHO_REFERENCE. + * REMOTE_SUBMIX, RADIO_TUNER, VOICE_DOWNLINK, VOICE_UPLINK, VOICE_CALL, ECHO_REFERENCE + * and ULTRASOUND * @param preset * @return the same Builder instance. */ @@ -1246,7 +1274,8 @@ public final class AudioAttributes implements Parcelable { || (preset == MediaRecorder.AudioSource.VOICE_DOWNLINK) || (preset == MediaRecorder.AudioSource.VOICE_UPLINK) || (preset == MediaRecorder.AudioSource.VOICE_CALL) - || (preset == MediaRecorder.AudioSource.ECHO_REFERENCE)) { + || (preset == MediaRecorder.AudioSource.ECHO_REFERENCE) + || (preset == MediaRecorder.AudioSource.ULTRASOUND)) { mSource = preset; } else { setCapturePreset(preset); @@ -1589,6 +1618,7 @@ public final class AudioAttributes implements Parcelable { case CONTENT_TYPE_MUSIC: return new String("CONTENT_TYPE_MUSIC"); case CONTENT_TYPE_MOVIE: return new String("CONTENT_TYPE_MOVIE"); case CONTENT_TYPE_SONIFICATION: return new String("CONTENT_TYPE_SONIFICATION"); + case CONTENT_TYPE_ULTRASOUND: return new String("CONTENT_TYPE_ULTRASOUND"); default: return new String("unknown content type " + mContentType); } } @@ -1823,4 +1853,16 @@ public final class AudioAttributes implements Parcelable { }) @Retention(RetentionPolicy.SOURCE) public @interface AttributeContentType {} + + /** @hide */ + @IntDef({ + CONTENT_TYPE_UNKNOWN, + CONTENT_TYPE_SPEECH, + CONTENT_TYPE_MUSIC, + CONTENT_TYPE_MOVIE, + CONTENT_TYPE_SONIFICATION, + CONTENT_TYPE_ULTRASOUND + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AttrInternalContentType {} } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 21fc6eccc67b..68e5d94f7559 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -79,6 +79,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -2995,19 +2996,17 @@ public class AudioManager { void onModeChanged(@AudioMode int mode); } - private final Object mModeListenerLock = new Object(); /** - * List of listeners for audio mode and their associated Executor. - * List is lazy-initialized on first registration + * manages the OnModeChangedListener listeners and the ModeDispatcherStub */ - @GuardedBy("mModeListenerLock") - private @Nullable ArrayList<ListenerInfo<OnModeChangedListener>> mModeListeners; + private final CallbackUtil.LazyListenerManager<OnModeChangedListener> mModeChangedListenerMgr = + new CallbackUtil.LazyListenerManager(); - @GuardedBy("mModeListenerLock") - private ModeDispatcherStub mModeDispatcherStub; - private final class ModeDispatcherStub extends IAudioModeDispatcher.Stub { + final class ModeDispatcherStub extends IAudioModeDispatcher.Stub + implements CallbackUtil.DispatcherStub { + @Override public void register(boolean register) { try { if (register) { @@ -3021,10 +3020,8 @@ public class AudioManager { } @Override - @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchAudioModeChanged(int mode) { - CallbackUtil.callListeners(mModeListeners, mModeListenerLock, - (listener) -> listener.onModeChanged(mode)); + mModeChangedListenerMgr.callListeners((listener) -> listener.onModeChanged(mode)); } } @@ -3037,15 +3034,8 @@ public class AudioManager { public void addOnModeChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnModeChangedListener listener) { - synchronized (mModeListenerLock) { - final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res = - CallbackUtil.addListener("addOnModeChangedListener", - executor, listener, mModeListeners, mModeDispatcherStub, - () -> new ModeDispatcherStub(), - stub -> stub.register(true)); - mModeListeners = res.first; - mModeDispatcherStub = res.second; - } + mModeChangedListenerMgr.addListener(executor, listener, "addOnModeChangedListener", + () -> new ModeDispatcherStub()); } /** @@ -3054,14 +3044,7 @@ public class AudioManager { * @param listener */ public void removeOnModeChangedListener(@NonNull OnModeChangedListener listener) { - synchronized (mModeListenerLock) { - final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res = - CallbackUtil.removeListener("removeOnModeChangedListener", - listener, mModeListeners, mModeDispatcherStub, - stub -> stub.register(false)); - mModeListeners = res.first; - mModeDispatcherStub = res.second; - } + mModeChangedListenerMgr.removeListener(listener, "removeOnModeChangedListener"); } /** @@ -5666,6 +5649,43 @@ public class AudioManager { } /** + * Get the audio devices that would be used for the routing of the given audio attributes. + * These are the devices anticipated to play sound from an {@link AudioTrack} created with + * the specified {@link AudioAttributes}. + * The audio routing can change if audio devices are physically connected or disconnected or + * concurrently through {@link AudioRouting} or {@link MediaRouter}. + * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @return an empty list if there was an issue with the request, a list of + * {@link AudioDeviceInfo} otherwise (typically one device, except for duplicated paths). + */ + public @NonNull List<AudioDeviceInfo> getAudioDevicesForAttributes( + @NonNull AudioAttributes attributes) { + final List<AudioDeviceAttributes> devicesForAttributes; + try { + Objects.requireNonNull(attributes); + final IAudioService service = getService(); + devicesForAttributes = service.getDevicesForAttributesUnprotected(attributes); + } catch (Exception e) { + Log.i(TAG, "No audio devices available for specified attributes."); + return Collections.emptyList(); + } + + // Map from AudioDeviceAttributes to AudioDeviceInfo + AudioDeviceInfo[] outputDeviceInfos = getDevicesStatic(GET_DEVICES_OUTPUTS); + List<AudioDeviceInfo> deviceInfosForAttributes = new ArrayList<>(); + for (AudioDeviceAttributes deviceForAttributes : devicesForAttributes) { + for (AudioDeviceInfo deviceInfo : outputDeviceInfos) { + if (deviceForAttributes.getType() == deviceInfo.getType() + && TextUtils.equals(deviceForAttributes.getAddress(), + deviceInfo.getAddress())) { + deviceInfosForAttributes.add(deviceInfo); + } + } + } + return Collections.unmodifiableList(deviceInfosForAttributes); + } + + /** * @hide * Volume behavior for an audio device that has no particular volume behavior set. Invalid as * an argument to {@link #setDeviceVolumeBehavior(AudioDeviceAttributes, int)} and should not @@ -7718,6 +7738,12 @@ public class AudioManager { } /** + * manages the OnCommunicationDeviceChangedListener listeners and the + * CommunicationDeviceDispatcherStub + */ + private final CallbackUtil.LazyListenerManager<OnCommunicationDeviceChangedListener> + mCommDeviceChangedListenerMgr = new CallbackUtil.LazyListenerManager(); + /** * Adds a listener for being notified of changes to the communication audio device. * See {@link #setCommunicationDevice(AudioDeviceInfo)}. * @param executor @@ -7726,16 +7752,9 @@ public class AudioManager { public void addOnCommunicationDeviceChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnCommunicationDeviceChangedListener listener) { - synchronized (mCommDevListenerLock) { - final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>, - CommunicationDeviceDispatcherStub> res = - CallbackUtil.addListener("addOnCommunicationDeviceChangedListener", - executor, listener, mCommDevListeners, mCommDevDispatcherStub, - () -> new CommunicationDeviceDispatcherStub(), - stub -> stub.register(true)); - mCommDevListeners = res.first; - mCommDevDispatcherStub = res.second; - } + mCommDeviceChangedListenerMgr.addListener( + executor, listener, "addOnCommunicationDeviceChangedListener", + () -> new CommunicationDeviceDispatcherStub()); } /** @@ -7745,32 +7764,14 @@ public class AudioManager { */ public void removeOnCommunicationDeviceChangedListener( @NonNull OnCommunicationDeviceChangedListener listener) { - synchronized (mCommDevListenerLock) { - final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>, - CommunicationDeviceDispatcherStub> res = - CallbackUtil.removeListener("removeOnCommunicationDeviceChangedListener", - listener, mCommDevListeners, mCommDevDispatcherStub, - stub -> stub.register(false)); - mCommDevListeners = res.first; - mCommDevDispatcherStub = res.second; - } + mCommDeviceChangedListenerMgr.removeListener(listener, + "removeOnCommunicationDeviceChangedListener"); } - private final Object mCommDevListenerLock = new Object(); - /** - * List of listeners for preferred device for strategy and their associated Executor. - * List is lazy-initialized on first registration - */ - @GuardedBy("mCommDevListenerLock") - private @Nullable - ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>> mCommDevListeners; - - @GuardedBy("mCommDevListenerLock") - private CommunicationDeviceDispatcherStub mCommDevDispatcherStub; - private final class CommunicationDeviceDispatcherStub - extends ICommunicationDeviceDispatcher.Stub { + extends ICommunicationDeviceDispatcher.Stub implements CallbackUtil.DispatcherStub { + @Override public void register(boolean register) { try { if (register) { @@ -7784,10 +7785,9 @@ public class AudioManager { } @Override - @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchCommunicationDeviceChanged(int portId) { AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS); - CallbackUtil.callListeners(mCommDevListeners, mCommDevListenerLock, + mCommDeviceChangedListenerMgr.callListeners( (listener) -> listener.onCommunicationDeviceChanged(device)); } } diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index e76bb42560f6..52838898146d 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -1033,11 +1033,12 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, //-------------- // audio source - if ( (audioSource < MediaRecorder.AudioSource.DEFAULT) || - ((audioSource > MediaRecorder.getAudioSourceMax()) && - (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) && - (audioSource != MediaRecorder.AudioSource.ECHO_REFERENCE) && - (audioSource != MediaRecorder.AudioSource.HOTWORD)) ) { + if ((audioSource < MediaRecorder.AudioSource.DEFAULT) + || ((audioSource > MediaRecorder.getAudioSourceMax()) + && (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) + && (audioSource != MediaRecorder.AudioSource.ECHO_REFERENCE) + && (audioSource != MediaRecorder.AudioSource.HOTWORD) + && (audioSource != MediaRecorder.AudioSource.ULTRASOUND))) { throw new IllegalArgumentException("Invalid audio source " + audioSource); } mRecordSource = audioSource; diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index af5a3da5f3e2..50bf1e5e0a26 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -249,7 +249,8 @@ public class AudioSystem AUDIO_FORMAT_SBC, AUDIO_FORMAT_APTX, AUDIO_FORMAT_APTX_HD, - AUDIO_FORMAT_LDAC} + AUDIO_FORMAT_LDAC, + AUDIO_FORMAT_LC3} ) @Retention(RetentionPolicy.SOURCE) public @interface AudioFormatNativeEnumForBtCodec {} @@ -281,6 +282,7 @@ public class AudioSystem case AUDIO_FORMAT_APTX: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX; case AUDIO_FORMAT_APTX_HD: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD; case AUDIO_FORMAT_LDAC: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC; + case AUDIO_FORMAT_LC3: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3; default: Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat) + " for conversion to BT codec"); @@ -321,6 +323,8 @@ public class AudioSystem return AudioSystem.AUDIO_FORMAT_APTX_HD; case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: return AudioSystem.AUDIO_FORMAT_LDAC; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3: + return AudioSystem.AUDIO_FORMAT_LC3; default: Log.e(TAG, "Unknown BT codec 0x" + Integer.toHexString(btCodec) + " for conversion to audio format"); @@ -421,6 +425,8 @@ public class AudioSystem return "AUDIO_FORMAT_LHDC_LL"; case /* AUDIO_FORMAT_APTX_TWSP */ 0x2A000000: return "AUDIO_FORMAT_APTX_TWSP"; + case /* AUDIO_FORMAT_LC3 */ 0x2B000000: + return "AUDIO_FORMAT_LC3"; /* Aliases */ case /* AUDIO_FORMAT_PCM_16_BIT */ 0x1: diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BtProfileConnectionInfo.java index 19ea2de6a434..d1bb41e70b54 100644 --- a/media/java/android/media/BtProfileConnectionInfo.java +++ b/media/java/android/media/BtProfileConnectionInfo.java @@ -34,7 +34,7 @@ public final class BtProfileConnectionInfo implements Parcelable { /** @hide */ @IntDef({ BluetoothProfile.A2DP, - BluetoothProfile.A2DP_SINK, // Can only be set by BtHelper + BluetoothProfile.A2DP_SINK, BluetoothProfile.HEADSET, // Can only be set by BtHelper BluetoothProfile.HEARING_AID, BluetoothProfile.LE_AUDIO, @@ -105,6 +105,16 @@ public final class BtProfileConnectionInfo implements Parcelable { } /** + * Constructor for A2dp sink info + * The {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent. + * + * @param volume of device -1 to ignore value + */ + public static @NonNull BtProfileConnectionInfo a2dpSinkInfo(int volume) { + return new BtProfileConnectionInfo(BluetoothProfile.A2DP_SINK, true, volume, false); + } + + /** * Constructor for hearing aid info * * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} diff --git a/media/java/android/media/CallbackUtil.java b/media/java/android/media/CallbackUtil.java index ac39317ed1d5..2b5fd25c49a9 100644 --- a/media/java/android/media/CallbackUtil.java +++ b/media/java/android/media/CallbackUtil.java @@ -18,11 +18,14 @@ package android.media; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.media.permission.ClearCallingIdentityContext; import android.media.permission.SafeCloseable; import android.util.Log; import android.util.Pair; +import com.android.internal.annotations.GuardedBy; + import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; @@ -221,4 +224,87 @@ import java.util.concurrent.Executor; } } + + /** + * Interface to be implemented by stub implementation for the events received from a server + * to the class managing the listener API. + * For an example see {@link AudioManager#ModeDispatcherStub} which registers with AudioService. + */ + interface DispatcherStub { + /** + * Register/unregister the stub as a listener of the events to be forwarded to the listeners + * managed by LazyListenerManager. + * @param register true for registering, false to unregister + */ + void register(boolean register); + } + + /** + * Class to manage a list of listeners and their callback, and the associated stub which + * receives the events to be forwarded to the listeners. + * The list of listeners and the stub and its registration are lazily initialized and registered + * @param <T> the listener class + */ + static class LazyListenerManager<T> { + private final Object mListenerLock = new Object(); + + @GuardedBy("mListenerLock") + private @Nullable ArrayList<ListenerInfo<T>> mListeners; + + @GuardedBy("mListenerLock") + private @Nullable DispatcherStub mDispatcherStub; + + LazyListenerManager() { + // nothing to initialize as instances of dispatcher and list of listeners + // are lazily initialized + } + + /** + * Add a new listener / executor pair for the configured listener + * @param executor Executor for the callback + * @param listener the listener to register + * @param methodName the name of the method calling this utility method for easier to read + * exception messages + * @param newStub how to build a new instance of the stub receiving the events when the + * number of listeners goes from 0 to 1, not called until then. + */ + void addListener(@NonNull Executor executor, @NonNull T listener, String methodName, + @NonNull java.util.function.Supplier<DispatcherStub> newStub) { + synchronized (mListenerLock) { + final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res = + CallbackUtil.addListener(methodName, + executor, listener, mListeners, mDispatcherStub, + newStub, + stub -> stub.register(true)); + mListeners = res.first; + mDispatcherStub = res.second; + } + } + + /** + * Remove a previously registered listener + * @param listener the listener to unregister + * @param methodName the name of the method calling this utility method for easier to read + * exception messages + */ + void removeListener(@NonNull T listener, String methodName) { + synchronized (mListenerLock) { + final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res = + CallbackUtil.removeListener(methodName, + listener, mListeners, mDispatcherStub, + stub -> stub.register(false)); + mListeners = res.first; + mDispatcherStub = res.second; + } + } + + /** + * Call the registered listeners with the given callback method + * @param callback the listener method to invoke + */ + @SuppressLint("GuardedBy") // lock applied inside callListeners method + void callListeners(CallbackMethod<T> callback) { + CallbackUtil.callListeners(mListeners, mListenerLock, callback); + } + } } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 7f6fb90297b3..96199a988704 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -308,6 +308,8 @@ interface IAudioService { List<AudioDeviceAttributes> getDevicesForAttributes(in AudioAttributes attributes); + List<AudioDeviceAttributes> getDevicesForAttributesUnprotected(in AudioAttributes attributes); + int setAllowedCapturePolicy(in int capturePolicy); int getAllowedCapturePolicy(); diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index bd0f32e6ffee..09d7fbd1df36 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -18,10 +18,13 @@ package android.media; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.graphics.GraphicBuffer; import android.graphics.ImageFormat; import android.graphics.ImageFormat.Format; import android.graphics.Rect; +import android.hardware.DataSpace; +import android.hardware.DataSpace.NamedDataSpace; import android.hardware.HardwareBuffer; import android.hardware.HardwareBuffer.Usage; import android.hardware.camera2.MultiResolutionImageReader; @@ -136,8 +139,7 @@ public class ImageReader implements AutoCloseable { // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not // work, and is inscrutable anyway return new ImageReader(width, height, format, maxImages, - format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, - /*parent*/ null); + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, null); } /** @@ -268,44 +270,32 @@ public class ImageReader implements AutoCloseable { // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not // work, and is inscrutable anyway return new ImageReader(width, height, format, maxImages, - format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, - parent); + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, parent); } - - /** - * @hide - */ - protected ImageReader(int width, int height, int format, int maxImages, long usage, - MultiResolutionImageReader parent) { - mWidth = width; - mHeight = height; - mFormat = format; - mUsage = usage; - mMaxImages = maxImages; - mParent = parent; - + private void initializeImageReader(int width, int height, int imageFormat, int maxImages, + long usage, int hardwareBufferFormat, long dataSpace, boolean useLegacyImageFormat) { if (width < 1 || height < 1) { throw new IllegalArgumentException( "The image dimensions must be positive"); } - if (mMaxImages < 1) { + + if (maxImages < 1) { throw new IllegalArgumentException( "Maximum outstanding image count must be at least 1"); } - if (format == ImageFormat.NV21) { + if (imageFormat == ImageFormat.NV21) { throw new IllegalArgumentException( - "NV21 format is not supported"); + "NV21 format is not supported"); } - mNumPlanes = ImageUtils.getNumPlanesForFormat(mFormat); + nativeInit(new WeakReference<>(this), width, height, maxImages, usage, + hardwareBufferFormat, dataSpace); - nativeInit(new WeakReference<>(this), width, height, format, maxImages, usage); + mIsReaderValid = true; mSurface = nativeGetSurface(); - - mIsReaderValid = true; // Estimate the native buffer allocation size and register it so it gets accounted for // during GC. Note that this doesn't include the buffers required by the buffer queue // itself and the buffers requested by the producer. @@ -313,10 +303,46 @@ public class ImageReader implements AutoCloseable { // complex, and 1 buffer is enough for the VM to treat the ImageReader as being of some // size. mEstimatedNativeAllocBytes = ImageUtils.getEstimatedNativeAllocBytes( - width, height, format, /*buffer count*/ 1); + width, height, useLegacyImageFormat ? imageFormat : hardwareBufferFormat, + /*buffer count*/ 1); VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes); } + private ImageReader(int width, int height, int imageFormat, int maxImages, long usage, + MultiResolutionImageReader parent) { + mWidth = width; + mHeight = height; + mFormat = imageFormat; + mUsage = usage; + mMaxImages = maxImages; + mParent = parent; + // retrieve hal Format and hal dataspace from imageFormat + mHardwareBufferFormat = PublicFormatUtils.getHalFormat(mFormat); + mDataSpace = PublicFormatUtils.getHalDataspace(mFormat); + mUseLegacyImageFormat = true; + mNumPlanes = ImageUtils.getNumPlanesForFormat(mFormat); + + initializeImageReader(width, height, imageFormat, maxImages, usage, mHardwareBufferFormat, + mDataSpace, mUseLegacyImageFormat); + } + + private ImageReader(int width, int height, int maxImages, long usage, + MultiResolutionImageReader parent, int hardwareBufferFormat, long dataSpace) { + mWidth = width; + mHeight = height; + mFormat = ImageFormat.UNKNOWN; // set default image format value as UNKNOWN + mUsage = usage; + mMaxImages = maxImages; + mParent = parent; + mHardwareBufferFormat = hardwareBufferFormat; + mDataSpace = dataSpace; + mUseLegacyImageFormat = false; + mNumPlanes = ImageUtils.getNumPlanesForHardwareBufferFormat(mHardwareBufferFormat); + + initializeImageReader(width, height, mFormat, maxImages, usage, hardwareBufferFormat, + dataSpace, mUseLegacyImageFormat); + } + /** * The default width of {@link Image Images}, in pixels. * @@ -354,6 +380,10 @@ public class ImageReader implements AutoCloseable { * As of now, each format is only compatible to itself. * The actual format of the images can be found using {@link Image#getFormat}.</p> * + * <p>Use this function if the ImageReader instance is created by factory method + * {@code newInstance} function or by builder pattern {@code ImageReader.Builder} and using + * {@link Builder#setImageFormat}.</p> + * * @return the expected format of an Image * * @see ImageFormat @@ -363,6 +393,32 @@ public class ImageReader implements AutoCloseable { } /** + * The default {@link HardwareBuffer} format of {@link Image Images}. + * + * <p>Use this function if the ImageReader instance is created by builder pattern + * {@code ImageReader.Builder} and using {@link Builder#setDefaultHardwareBufferFormat} and + * {@link Builder#setDefaultDataSpace}.</p> + * + * @return the expected {@link HardwareBuffer} format of an Image. + */ + public @HardwareBuffer.Format int getHardwareBufferFormat() { + return mHardwareBufferFormat; + } + + /** + * The default dataspace of {@link Image Images}. + * + * <p>Use this function if the ImageReader instance is created by builder pattern + * {@code ImageReader.Builder} and {@link Builder#setDefaultDataSpace}.</p> + * + * @return the expected dataspace of an Image. + */ + @SuppressLint("MethodNameUnits") + public @NamedDataSpace long getDataSpace() { + return mDataSpace; + } + + /** * Maximum number of images that can be acquired from the ImageReader by any time (for example, * with {@link #acquireNextImage}). * @@ -384,6 +440,15 @@ public class ImageReader implements AutoCloseable { } /** + * The usage flag of images that can be produced by the ImageReader. + * + * @return The usage flag of the images for this ImageReader. + */ + public @Usage long getUsage() { + return mUsage; + } + + /** * <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this * {@code ImageReader}.</p> * @@ -469,7 +534,12 @@ public class ImageReader implements AutoCloseable { * @hide */ public Image acquireNextImageNoThrowISE() { - SurfaceImage si = new SurfaceImage(mFormat); + SurfaceImage si; + if (mUseLegacyImageFormat) { + si = new SurfaceImage(mFormat); + } else { + si = new SurfaceImage(mHardwareBufferFormat, mDataSpace); + } return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null; } @@ -492,7 +562,7 @@ public class ImageReader implements AutoCloseable { // A null image will eventually be returned if ImageReader is already closed. int status = ACQUIRE_NO_BUFS; if (mIsReaderValid) { - status = nativeImageSetup(si); + status = nativeImageSetup(si, mUseLegacyImageFormat); } switch (status) { @@ -545,7 +615,12 @@ public class ImageReader implements AutoCloseable { public Image acquireNextImage() { // Initialize with reader format, but can be overwritten by native if the image // format is different from the reader format. - SurfaceImage si = new SurfaceImage(mFormat); + SurfaceImage si; + if (mUseLegacyImageFormat) { + si = new SurfaceImage(mFormat); + } else { + si = new SurfaceImage(mHardwareBufferFormat, mDataSpace); + } int status = acquireNextSurfaceImage(si); switch (status) { @@ -838,13 +913,161 @@ public class ImageReader implements AutoCloseable { } } + /** + * Builder class for {@link ImageReader} objects. + */ + public static final class Builder { + private int mWidth; + private int mHeight; + private int mMaxImages = 1; + private int mImageFormat = ImageFormat.UNKNOWN; + private int mHardwareBufferFormat = HardwareBuffer.RGBA_8888; + private long mDataSpace = DataSpace.DATASPACE_UNKNOWN; + private long mUsage = HardwareBuffer.USAGE_CPU_READ_OFTEN; + private boolean mUseLegacyImageFormat = false; + + /** + * Constructs a new builder for {@link ImageReader}. + * + * @param width The default width in pixels that will be passed to the producer. + * May be overridden by the producer. + * @param height The default height in pixels that will be passed to the producer. + * May be overridden by the producer. + * @see Image + */ + public Builder(@IntRange(from = 1) int width, @IntRange(from = 1) int height) { + mWidth = width; + mHeight = height; + } + + /** + * Set the maximal number of images. + * + * @param maxImages The maximum number of images the user will want to + * access simultaneously. This should be as small as possible to + * limit memory use. Default value is 1. + * @return the Builder instance with customized usage value. + */ + public @NonNull Builder setMaxImages(int maxImages) { + mMaxImages = maxImages; + return this; + } + + /** + * Set the consumer usage flag. + * + * @param usage The intended usage of the images consumed by this ImageReader. + * See the usages on {@link HardwareBuffer} for a list of valid usage bits. + * Default value is {@link HardwareBuffer#USAGE_CPU_READ_OFTEN}. + * @return the Builder instance with customized usage value. + * + * @see HardwareBuffer + */ + public @NonNull Builder setUsage(long usage) { + mUsage = usage; + return this; + } + + /** + * Set the default image format passed by the producer. May be overridden by the producer. + * + * <p>{@link #setImageFormat} function replaces the combination of + * {@link #setDefaultHardwareBufferFormat} and {@link #setDefaultDataSpace} functions. + * Either this or these two functions must be called to initialize an {@code ImageReader} + * instance.</p> + * + * @param imageFormat The format of the image that this reader will produce. This + * must be one of the {@link android.graphics.ImageFormat} or + * {@link android.graphics.PixelFormat} constants. Note that not + * all formats are supported, like ImageFormat.NV21. The default value is + * {@link ImageFormat#UNKNOWN}. + * @return the builder instance with customized image format value. + * + * @see #setDefaultHardwareBufferFormat + * @see #setDefaultDataSpace + */ + public @NonNull Builder setImageFormat(@Format int imageFormat) { + mImageFormat = imageFormat; + mUseLegacyImageFormat = true; + mHardwareBufferFormat = HardwareBuffer.RGBA_8888; + mDataSpace = DataSpace.DATASPACE_UNKNOWN; + return this; + } + + /** + * Set the default hardwareBuffer format passed by the producer. + * May be overridden by the producer. + * + * <p>This function works together with {@link #setDefaultDataSpace} for an + * {@link ImageReader} instance. Setting at least one of these two replaces + * {@link #setImageFormat} function.</p> + * + * <p>The format of the Image can be overridden after {@link #setImageFormat} by calling + * this function and then {@link #setDefaultDataSpace} functions. + * <i>Warning:</i> Missing one of callings for initializing or overriding the format may + * involve undefined behaviors.</p> + * + * @param hardwareBufferFormat The HardwareBuffer format of the image that this reader + * will produce. The default value is + * {@link HardwareBuffer#RGBA_8888 HardwareBuffer.RGBA_8888}. + * @return the builder instance with customized hardwareBuffer value. + * + * @see #setDefaultDataSpace + * @see #setImageFormat + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder setDefaultHardwareBufferFormat( + @HardwareBuffer.Format int hardwareBufferFormat) { + mHardwareBufferFormat = hardwareBufferFormat; + mUseLegacyImageFormat = false; + mImageFormat = ImageFormat.UNKNOWN; + return this; + } + + /** + * Set the default dataspace passed by the producer. + * May be overridden by the producer. + * + * <p>This function works together with {@link #setDefaultHardwareBufferFormat} for an + * {@link ImageReader} instance. Setting at least one of these two replaces + * {@link #setImageFormat} function.</p> + * + * @param dataSpace The dataspace of the image that this reader will produce. + * The default value is {@link DataSpace#DATASPACE_UNKNOWN}. + * @return the builder instance with customized dataspace value. + * + * @see #setDefaultHardwareBufferFormat + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder setDefaultDataSpace(@NamedDataSpace long dataSpace) { + mDataSpace = dataSpace; + mUseLegacyImageFormat = false; + mImageFormat = ImageFormat.UNKNOWN; + return this; + } + + /** + * Builds a new ImageReader object. + * + * @return The new ImageReader object. + */ + public @NonNull ImageReader build() { + if (mUseLegacyImageFormat) { + return new ImageReader(mWidth, mHeight, mImageFormat, mMaxImages, mUsage, null); + } else { + return new ImageReader(mWidth, mHeight, mMaxImages, mUsage, null, + mHardwareBufferFormat, mDataSpace); + } + } + } + private final int mWidth; private final int mHeight; private final int mFormat; private final long mUsage; private final int mMaxImages; private final int mNumPlanes; - private final Surface mSurface; + private Surface mSurface; private int mEstimatedNativeAllocBytes; private final Object mListenerLock = new Object(); @@ -861,6 +1084,12 @@ public class ImageReader implements AutoCloseable { // MultiResolutionImageReader. private final MultiResolutionImageReader mParent; + private final int mHardwareBufferFormat; + + private final long mDataSpace; + + private final boolean mUseLegacyImageFormat; + /** * This field is used by native code, do not access or modify. */ @@ -895,6 +1124,14 @@ public class ImageReader implements AutoCloseable { private class SurfaceImage extends android.media.Image { public SurfaceImage(int format) { mFormat = format; + mHardwareBufferFormat = ImageReader.this.mHardwareBufferFormat; + mDataSpace = ImageReader.this.mDataSpace; + } + + SurfaceImage(int hardwareBufferFormat, long dataSpace) { + mHardwareBufferFormat = hardwareBufferFormat; + mDataSpace = dataSpace; + mFormat = PublicFormatUtils.getPublicFormat(mHardwareBufferFormat, mDataSpace); } @Override @@ -909,10 +1146,15 @@ public class ImageReader implements AutoCloseable { @Override public int getFormat() { throwISEIfImageIsInvalid(); - int readerFormat = ImageReader.this.getImageFormat(); - // Assume opaque reader always produce opaque images. - mFormat = (readerFormat == ImageFormat.PRIVATE) ? readerFormat : - nativeGetFormat(readerFormat); + // update mFormat only if ImageReader is initialized by factory pattern. + // if using builder pattern, mFormat has been updated upon initialization. + // no need update here. + if (ImageReader.this.mUseLegacyImageFormat) { + int readerFormat = ImageReader.this.getImageFormat(); + // Assume opaque reader always produce opaque images. + mFormat = (readerFormat == ImageFormat.PRIVATE) ? readerFormat : + nativeGetFormat(readerFormat); + } return mFormat; } @@ -989,6 +1231,12 @@ public class ImageReader implements AutoCloseable { } @Override + public long getDataSpace() { + throwISEIfImageIsInvalid(); + return mDataSpace; + } + + @Override public void setTimestamp(long timestampNs) { throwISEIfImageIsInvalid(); mTimestamp = timestampNs; @@ -1125,6 +1373,8 @@ public class ImageReader implements AutoCloseable { private SurfacePlane[] mPlanes; private int mFormat = ImageFormat.UNKNOWN; + private int mHardwareBufferFormat = HardwareBuffer.RGBA_8888; + private long mDataSpace = DataSpace.DATASPACE_UNKNOWN; // If this image is detached from the ImageReader. private AtomicBoolean mIsDetached = new AtomicBoolean(false); @@ -1137,8 +1387,8 @@ public class ImageReader implements AutoCloseable { private synchronized native HardwareBuffer nativeGetHardwareBuffer(); } - private synchronized native void nativeInit(Object weakSelf, int w, int h, - int fmt, int maxImgs, long consumerUsage); + private synchronized native void nativeInit(Object weakSelf, int w, int h, int maxImgs, + long consumerUsage, int hardwareBufferFormat, long dataSpace); private synchronized native void nativeClose(); private synchronized native void nativeReleaseImage(Image i); private synchronized native Surface nativeGetSurface(); @@ -1152,7 +1402,7 @@ public class ImageReader implements AutoCloseable { * @see #ACQUIRE_NO_BUFS * @see #ACQUIRE_MAX_IMAGES */ - private synchronized native int nativeImageSetup(Image i); + private synchronized native int nativeImageSetup(Image i, boolean legacyValidateImageFormat); /** * @hide diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index 7837d7e39599..2f1a36cba9d0 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -18,6 +18,7 @@ package android.media; import android.graphics.ImageFormat; import android.graphics.PixelFormat; +import android.hardware.HardwareBuffer; import android.media.Image.Plane; import android.util.Size; @@ -77,6 +78,34 @@ class ImageUtils { } /** + * Only a subset of the formats defined in + * {@link android.graphics.HardwareBuffer.Format} constants are supported by ImageReader. + */ + public static int getNumPlanesForHardwareBufferFormat(int hardwareBufferFormat) { + switch(hardwareBufferFormat) { + case HardwareBuffer.YCBCR_420_888: + return 3; + case HardwareBuffer.RGBA_8888: + case HardwareBuffer.RGBX_8888: + case HardwareBuffer.RGB_888: + case HardwareBuffer.RGB_565: + case HardwareBuffer.RGBA_FP16: + case HardwareBuffer.RGBA_1010102: + case HardwareBuffer.BLOB: + case HardwareBuffer.D_16: + case HardwareBuffer.D_24: + case HardwareBuffer.DS_24UI8: + case HardwareBuffer.D_FP32: + case HardwareBuffer.DS_FP32UI8: + case HardwareBuffer.S_UI8: + return 1; + default: + throw new UnsupportedOperationException( + String.format("Invalid hardwareBuffer format specified %d", + hardwareBufferFormat)); + } + } + /** * <p> * Copy source image data to destination Image. * </p> diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 60d21c0b3b97..ad8fc0707f3e 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -1077,7 +1077,9 @@ public class MediaMetadataRetriever implements AutoCloseable { * Releases any acquired resources. Call it when done with the object. * * @throws IOException When an {@link IOException} is thrown while closing a {@link - * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. + * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. This throws clause exists + * since API 33, but this method can throw in earlier API versions where the exception is not + * declared. */ @Override public void close() throws IOException { @@ -1088,7 +1090,9 @@ public class MediaMetadataRetriever implements AutoCloseable { * Releases any acquired resources. Call it when done with the object. * * @throws IOException When an {@link IOException} is thrown while closing a {@link - * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. + * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. This throws clause exists + * since API 33, but this method can throw in earlier API versions where the exception is not + * declared. */ public native void release() throws IOException; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 77c1e55b08cb..d7857a01f7ea 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -365,6 +365,7 @@ public class MediaRecorder implements AudioRouting, */ public static final int VOICE_PERFORMANCE = 10; + /** * Source for an echo canceller to capture the reference signal to be cancelled. * <p> @@ -408,6 +409,15 @@ public class MediaRecorder implements AudioRouting, @SystemApi @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public static final int HOTWORD = 1999; + + /** Microphone audio source for ultrasound sound if available, behaves like + * {@link #DEFAULT} otherwise. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) + public static final int ULTRASOUND = 2000; + } /** @hide */ @@ -442,6 +452,7 @@ public class MediaRecorder implements AudioRouting, AudioSource.ECHO_REFERENCE, AudioSource.RADIO_TUNER, AudioSource.HOTWORD, + AudioSource.ULTRASOUND, }) @Retention(RetentionPolicy.SOURCE) public @interface SystemSource {} @@ -454,20 +465,20 @@ public class MediaRecorder implements AudioRouting, */ public static boolean isSystemOnlyAudioSource(int source) { switch(source) { - case AudioSource.DEFAULT: - case AudioSource.MIC: - case AudioSource.VOICE_UPLINK: - case AudioSource.VOICE_DOWNLINK: - case AudioSource.VOICE_CALL: - case AudioSource.CAMCORDER: - case AudioSource.VOICE_RECOGNITION: - case AudioSource.VOICE_COMMUNICATION: - //case REMOTE_SUBMIX: considered "system" as it requires system permissions - case AudioSource.UNPROCESSED: - case AudioSource.VOICE_PERFORMANCE: - return false; - default: - return true; + case AudioSource.DEFAULT: + case AudioSource.MIC: + case AudioSource.VOICE_UPLINK: + case AudioSource.VOICE_DOWNLINK: + case AudioSource.VOICE_CALL: + case AudioSource.CAMCORDER: + case AudioSource.VOICE_RECOGNITION: + case AudioSource.VOICE_COMMUNICATION: + //case REMOTE_SUBMIX: considered "system" as it requires system permissions + case AudioSource.UNPROCESSED: + case AudioSource.VOICE_PERFORMANCE: + return false; + default: + return true; } } @@ -491,6 +502,7 @@ public class MediaRecorder implements AudioRouting, case AudioSource.ECHO_REFERENCE: case AudioSource.RADIO_TUNER: case AudioSource.HOTWORD: + case AudioSource.ULTRASOUND: return true; default: return false; @@ -500,38 +512,40 @@ public class MediaRecorder implements AudioRouting, /** @hide */ public static final String toLogFriendlyAudioSource(int source) { switch(source) { - case AudioSource.DEFAULT: - return "DEFAULT"; - case AudioSource.MIC: - return "MIC"; - case AudioSource.VOICE_UPLINK: - return "VOICE_UPLINK"; - case AudioSource.VOICE_DOWNLINK: - return "VOICE_DOWNLINK"; - case AudioSource.VOICE_CALL: - return "VOICE_CALL"; - case AudioSource.CAMCORDER: - return "CAMCORDER"; - case AudioSource.VOICE_RECOGNITION: - return "VOICE_RECOGNITION"; - case AudioSource.VOICE_COMMUNICATION: - return "VOICE_COMMUNICATION"; - case AudioSource.REMOTE_SUBMIX: - return "REMOTE_SUBMIX"; - case AudioSource.UNPROCESSED: - return "UNPROCESSED"; - case AudioSource.ECHO_REFERENCE: - return "ECHO_REFERENCE"; - case AudioSource.VOICE_PERFORMANCE: - return "VOICE_PERFORMANCE"; - case AudioSource.RADIO_TUNER: - return "RADIO_TUNER"; - case AudioSource.HOTWORD: - return "HOTWORD"; - case AudioSource.AUDIO_SOURCE_INVALID: - return "AUDIO_SOURCE_INVALID"; - default: - return "unknown source " + source; + case AudioSource.DEFAULT: + return "DEFAULT"; + case AudioSource.MIC: + return "MIC"; + case AudioSource.VOICE_UPLINK: + return "VOICE_UPLINK"; + case AudioSource.VOICE_DOWNLINK: + return "VOICE_DOWNLINK"; + case AudioSource.VOICE_CALL: + return "VOICE_CALL"; + case AudioSource.CAMCORDER: + return "CAMCORDER"; + case AudioSource.VOICE_RECOGNITION: + return "VOICE_RECOGNITION"; + case AudioSource.VOICE_COMMUNICATION: + return "VOICE_COMMUNICATION"; + case AudioSource.REMOTE_SUBMIX: + return "REMOTE_SUBMIX"; + case AudioSource.UNPROCESSED: + return "UNPROCESSED"; + case AudioSource.ECHO_REFERENCE: + return "ECHO_REFERENCE"; + case AudioSource.VOICE_PERFORMANCE: + return "VOICE_PERFORMANCE"; + case AudioSource.RADIO_TUNER: + return "RADIO_TUNER"; + case AudioSource.HOTWORD: + return "HOTWORD"; + case AudioSource.ULTRASOUND: + return "ULTRASOUND"; + case AudioSource.AUDIO_SOURCE_INVALID: + return "AUDIO_SOURCE_INVALID"; + default: + return "unknown source " + source; } } diff --git a/media/java/android/media/PublicFormatUtils.java b/media/java/android/media/PublicFormatUtils.java new file mode 100644 index 000000000000..6268804128c6 --- /dev/null +++ b/media/java/android/media/PublicFormatUtils.java @@ -0,0 +1,34 @@ +/* + * Copyright 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 android.media; + +/** + * Package private utility class for PublicFormat related methods. + */ +class PublicFormatUtils { + public static int getHalFormat(int imageFormat) { + return nativeGetHalFormat(imageFormat); + } + public static long getHalDataspace(int imageFormat) { + return nativeGetHalDataspace(imageFormat); + } + public static int getPublicFormat(int imageFormat, long dataspace) { + return nativeGetPublicFormat(imageFormat, dataspace); + } + private static native int nativeGetHalFormat(int imageFormat); + private static native long nativeGetHalDataspace(int imageFormat); + private static native int nativeGetPublicFormat(int imageFormat, long dataspace); +} diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 3cf03417334b..86a94a9e0662 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -504,49 +504,51 @@ public class Ringtone { } private boolean playFallbackRingtone() { - if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) - != 0) { - int ringtoneType = RingtoneManager.getDefaultType(mUri); - if (ringtoneType == -1 || - RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) != null) { - // Default ringtone, try fallback ringtone. - try { - AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( - com.android.internal.R.raw.fallbackring); - if (afd != null) { - mLocalPlayer = new MediaPlayer(); - if (afd.getDeclaredLength() < 0) { - mLocalPlayer.setDataSource(afd.getFileDescriptor()); - } else { - mLocalPlayer.setDataSource(afd.getFileDescriptor(), - afd.getStartOffset(), - afd.getDeclaredLength()); - } - mLocalPlayer.setAudioAttributes(mAudioAttributes); - synchronized (mPlaybackSettingsLock) { - applyPlaybackProperties_sync(); - } - if (mVolumeShaperConfig != null) { - mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig); - } - mLocalPlayer.prepare(); - startLocalPlayer(); - afd.close(); - return true; - } else { - Log.e(TAG, "Could not load fallback ringtone"); - } - } catch (IOException ioe) { - destroyLocalPlayer(); - Log.e(TAG, "Failed to open fallback ringtone"); - } catch (NotFoundException nfe) { - Log.e(TAG, "Fallback ringtone does not exist"); - } + int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes); + if (mAudioManager.getStreamVolume(streamType) == 0) { + return false; + } + int ringtoneType = RingtoneManager.getDefaultType(mUri); + if (ringtoneType != -1 && + RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) { + Log.w(TAG, "not playing fallback for " + mUri); + return false; + } + // Default ringtone, try fallback ringtone. + try { + AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( + com.android.internal.R.raw.fallbackring); + if (afd == null) { + Log.e(TAG, "Could not load fallback ringtone"); + return false; + } + mLocalPlayer = new MediaPlayer(); + if (afd.getDeclaredLength() < 0) { + mLocalPlayer.setDataSource(afd.getFileDescriptor()); } else { - Log.w(TAG, "not playing fallback for " + mUri); + mLocalPlayer.setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), + afd.getDeclaredLength()); } + mLocalPlayer.setAudioAttributes(mAudioAttributes); + synchronized (mPlaybackSettingsLock) { + applyPlaybackProperties_sync(); + } + if (mVolumeShaperConfig != null) { + mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig); + } + mLocalPlayer.prepare(); + startLocalPlayer(); + afd.close(); + } catch (IOException ioe) { + destroyLocalPlayer(); + Log.e(TAG, "Failed to open fallback ringtone"); + return false; + } catch (NotFoundException nfe) { + Log.e(TAG, "Fallback ringtone does not exist"); + return false; } - return false; + return true; } void setTitle(String title) { diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index c0793ec58e53..030d212825ee 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -29,7 +29,6 @@ import android.media.permission.ClearCallingIdentityContext; import android.media.permission.SafeCloseable; import android.os.RemoteException; import android.util.Log; -import android.util.Pair; import com.android.internal.annotations.GuardedBy; @@ -216,6 +215,29 @@ public class Spatializer { public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; /** + * @hide + * Head tracking mode to string conversion + * @param mode a valid head tracking mode + * @return a string containing the matching constant name + */ + public static final String headtrackingModeToString(int mode) { + switch(mode) { + case HEAD_TRACKING_MODE_UNSUPPORTED: + return "HEAD_TRACKING_MODE_UNSUPPORTED"; + case HEAD_TRACKING_MODE_DISABLED: + return "HEAD_TRACKING_MODE_DISABLED"; + case HEAD_TRACKING_MODE_OTHER: + return "HEAD_TRACKING_MODE_OTHER"; + case HEAD_TRACKING_MODE_RELATIVE_WORLD: + return "HEAD_TRACKING_MODE_RELATIVE_WORLD"; + case HEAD_TRACKING_MODE_RELATIVE_DEVICE: + return "HEAD_TRACKING_MODE_RELATIVE_DEVICE"; + default: + return "head tracking mode unknown " + mode; + } + } + + /** * Return the level of support for the spatialization feature on this device. * This level of support is independent of whether the {@code Spatializer} is currently * enabled or available and will not change over time. @@ -376,16 +398,8 @@ public class Spatializer { public void addOnSpatializerStateChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnSpatializerStateChangedListener listener) { - synchronized (mStateListenerLock) { - final Pair<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>, - SpatializerInfoDispatcherStub> res = - CallbackUtil.addListener("addOnSpatializerStateChangedListener", - executor, listener, mStateListeners, mInfoDispatcherStub, - () -> new SpatializerInfoDispatcherStub(), - stub -> stub.register(true)); - mStateListeners = res.first; - mInfoDispatcherStub = res.second; - } + mStateListenerMgr.addListener(executor, listener, "addOnSpatializerStateChangedListener", + () -> new SpatializerInfoDispatcherStub()); } /** @@ -396,15 +410,7 @@ public class Spatializer { */ public void removeOnSpatializerStateChangedListener( @NonNull OnSpatializerStateChangedListener listener) { - synchronized (mStateListenerLock) { - final Pair<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>, - SpatializerInfoDispatcherStub> res = - CallbackUtil.removeListener("removeOnSpatializerStateChangedListener", - listener, mStateListeners, mInfoDispatcherStub, - stub -> stub.register(false)); - mStateListeners = res.first; - mInfoDispatcherStub = res.second; - } + mStateListenerMgr.removeListener(listener, "removeOnSpatializerStateChangedListener"); } /** @@ -459,18 +465,16 @@ public class Spatializer { } } - private final Object mStateListenerLock = new Object(); /** - * List of listeners for state listener and their associated Executor. - * List is lazy-initialized on first registration + * manages the OnSpatializerStateChangedListener listeners and the + * SpatializerInfoDispatcherStub */ - @GuardedBy("mStateListenerLock") - private @Nullable ArrayList<ListenerInfo<OnSpatializerStateChangedListener>> mStateListeners; - - @GuardedBy("mStateListenerLock") - private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub; + private final CallbackUtil.LazyListenerManager<OnSpatializerStateChangedListener> + mStateListenerMgr = new CallbackUtil.LazyListenerManager(); - private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub { + private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub + implements CallbackUtil.DispatcherStub { + @Override public void register(boolean register) { try { if (register) { @@ -486,7 +490,7 @@ public class Spatializer { @Override @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchSpatializerEnabledChanged(boolean enabled) { - CallbackUtil.callListeners(mStateListeners, mStateListenerLock, + mStateListenerMgr.callListeners( (listener) -> listener.onSpatializerEnabledChanged( Spatializer.this, enabled)); } @@ -494,7 +498,7 @@ public class Spatializer { @Override @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchSpatializerAvailableChanged(boolean available) { - CallbackUtil.callListeners(mStateListeners, mStateListenerLock, + mStateListenerMgr.callListeners( (listener) -> listener.onSpatializerAvailableChanged( Spatializer.this, available)); } @@ -612,16 +616,9 @@ public class Spatializer { public void addOnHeadTrackingModeChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnHeadTrackingModeChangedListener listener) { - synchronized (mHeadTrackingListenerLock) { - final Pair<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>, - SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.addListener( - "addOnHeadTrackingModeChangedListener", executor, listener, - mHeadTrackingListeners, mHeadTrackingDispatcherStub, - () -> new SpatializerHeadTrackingDispatcherStub(), - stub -> stub.register(true)); - mHeadTrackingListeners = res.first; - mHeadTrackingDispatcherStub = res.second; - } + mHeadTrackingListenerMgr.addListener(executor, listener, + "addOnHeadTrackingModeChangedListener", + () -> new SpatializerHeadTrackingDispatcherStub()); } /** @@ -634,15 +631,8 @@ public class Spatializer { @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void removeOnHeadTrackingModeChangedListener( @NonNull OnHeadTrackingModeChangedListener listener) { - synchronized (mHeadTrackingListenerLock) { - final Pair<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>, - SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.removeListener( - "removeOnHeadTrackingModeChangedListener", listener, - mHeadTrackingListeners, mHeadTrackingDispatcherStub, - stub -> stub.register(false)); - mHeadTrackingListeners = res.first; - mHeadTrackingDispatcherStub = res.second; - } + mHeadTrackingListenerMgr.removeListener(listener, + "removeOnHeadTrackingModeChangedListener"); } /** @@ -828,20 +818,17 @@ public class Spatializer { //----------------------------------------------------------------------------- // head tracking callback management and stub - private final Object mHeadTrackingListenerLock = new Object(); /** - * List of listeners for head tracking mode listener and their associated Executor. - * List is lazy-initialized on first registration + * manages the OnHeadTrackingModeChangedListener listeners and the + * SpatializerHeadTrackingDispatcherStub */ - @GuardedBy("mHeadTrackingListenerLock") - private @Nullable ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> - mHeadTrackingListeners; - - @GuardedBy("mHeadTrackingListenerLock") - private @Nullable SpatializerHeadTrackingDispatcherStub mHeadTrackingDispatcherStub; + private final CallbackUtil.LazyListenerManager<OnHeadTrackingModeChangedListener> + mHeadTrackingListenerMgr = new CallbackUtil.LazyListenerManager(); private final class SpatializerHeadTrackingDispatcherStub - extends ISpatializerHeadTrackingModeCallback.Stub { + extends ISpatializerHeadTrackingModeCallback.Stub + implements CallbackUtil.DispatcherStub { + @Override public void register(boolean register) { try { if (register) { @@ -857,14 +844,14 @@ public class Spatializer { @Override @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) { - CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock, + mHeadTrackingListenerMgr.callListeners( (listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode)); } @Override @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) { - CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock, + mHeadTrackingListenerMgr.callListeners( (listener) -> listener.onDesiredHeadTrackingModeChanged( Spatializer.this, mode)); } diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl index d3c8e0adb3ce..b03f78504635 100644 --- a/media/java/android/media/midi/IMidiManager.aidl +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -31,6 +31,8 @@ interface IMidiManager { MidiDeviceInfo[] getDevices(); + MidiDeviceInfo[] getDevicesForTransport(int transport); + // for device creation & removal notifications void registerListener(IBinder clientToken, in IMidiDeviceListener listener); void unregisterListener(IBinder clientToken, in IMidiDeviceListener listener); @@ -43,7 +45,7 @@ interface IMidiManager // for registering built-in MIDI devices MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts, int numOutputPorts, in String[] inputPortNames, in String[] outputPortNames, - in Bundle properties, int type); + in Bundle properties, int type, int defaultProtocol); // for unregistering built-in MIDI devices void unregisterDeviceServer(in IMidiDeviceServer server); diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java index dd3b6dbd6a39..b888fb00df9b 100644 --- a/media/java/android/media/midi/MidiDeviceInfo.java +++ b/media/java/android/media/midi/MidiDeviceInfo.java @@ -16,11 +16,15 @@ package android.media.midi; +import android.annotation.IntDef; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * This class contains information to describe a MIDI device. * For now we only have information that can be retrieved easily for USB devices, @@ -54,6 +58,110 @@ public final class MidiDeviceInfo implements Parcelable { public static final int TYPE_BLUETOOTH = 3; /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use UMP to negotiate with the device with MIDI-CI. + * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_USE_MIDI_CI = 0; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 64 bits. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 64 bits and jitter reduction timestamps. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 128 bits. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 128 bits and jitter reduction timestamps. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 2.0 through UMP. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_2_0 = 17; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 2.0 through UMP and jitter reduction timestamps. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18; + + /** + * Constant representing a device with an unknown default protocol. + * If Universal MIDI Packets (UMP) are needed, use MIDI-CI through MIDI 1.0. + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UNKNOWN = -1; + + /** + * @see MidiDeviceInfo#getDefaultProtocol + * @hide + */ + @IntDef(prefix = { "PROTOCOL_" }, value = { + PROTOCOL_UMP_USE_MIDI_CI, + PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS, + PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS, + PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS, + PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS, + PROTOCOL_UMP_MIDI_2_0, + PROTOCOL_UMP_MIDI_2_0_AND_JRTS, + PROTOCOL_UNKNOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DefaultProtocol {} + + /** * Bundle key for the device's user visible name property. * The value for this property is of type {@link java.lang.String}. * Used with the {@link android.os.Bundle} returned by {@link #getProperties}. @@ -196,6 +304,7 @@ public final class MidiDeviceInfo implements Parcelable { private final String[] mOutputPortNames; private final Bundle mProperties; private final boolean mIsPrivate; + private final int mDefaultProtocol; /** * MidiDeviceInfo should only be instantiated by MidiService implementation @@ -203,7 +312,7 @@ public final class MidiDeviceInfo implements Parcelable { */ public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, - boolean isPrivate) { + boolean isPrivate, int defaultProtocol) { // Check num ports for out-of-range values. Typical values will be // between zero and three. More than 16 would be very unlikely // because the port index field in the USB packet is only 4 bits. @@ -234,6 +343,7 @@ public final class MidiDeviceInfo implements Parcelable { } mProperties = properties; mIsPrivate = isPrivate; + mDefaultProtocol = defaultProtocol; } /** @@ -312,6 +422,18 @@ public final class MidiDeviceInfo implements Parcelable { return mIsPrivate; } + /** + * Returns the default protocol. For most devices, this will be {@link #PROTOCOL_UNKNOWN}. + * Returning {@link #PROTOCOL_UNKNOWN} is not an error; the device just doesn't support + * Universal MIDI Packets by default. + * + * @return the device's default protocol. + */ + @DefaultProtocol + public int getDefaultProtocol() { + return mDefaultProtocol; + } + @Override public boolean equals(Object o) { if (o instanceof MidiDeviceInfo) { @@ -331,11 +453,12 @@ public final class MidiDeviceInfo implements Parcelable { // This is a hack to force the mProperties Bundle to unparcel so we can // print all the names and values. mProperties.getString(PROPERTY_NAME); - return ("MidiDeviceInfo[mType=" + mType + - ",mInputPortCount=" + mInputPortCount + - ",mOutputPortCount=" + mOutputPortCount + - ",mProperties=" + mProperties + - ",mIsPrivate=" + mIsPrivate); + return ("MidiDeviceInfo[mType=" + mType + + ",mInputPortCount=" + mInputPortCount + + ",mOutputPortCount=" + mOutputPortCount + + ",mProperties=" + mProperties + + ",mIsPrivate=" + mIsPrivate + + ",mDefaultProtocol=" + mDefaultProtocol); } public static final @android.annotation.NonNull Parcelable.Creator<MidiDeviceInfo> CREATOR = @@ -349,10 +472,12 @@ public final class MidiDeviceInfo implements Parcelable { String[] inputPortNames = in.createStringArray(); String[] outputPortNames = in.createStringArray(); boolean isPrivate = (in.readInt() == 1); + int defaultProtocol = in.readInt(); Bundle basicPropertiesIgnored = in.readBundle(); Bundle properties = in.readBundle(); return new MidiDeviceInfo(type, id, inputPortCount, outputPortCount, - inputPortNames, outputPortNames, properties, isPrivate); + inputPortNames, outputPortNames, properties, isPrivate, + defaultProtocol); } public MidiDeviceInfo[] newArray(int size) { @@ -390,6 +515,7 @@ public final class MidiDeviceInfo implements Parcelable { parcel.writeStringArray(mInputPortNames); parcel.writeStringArray(mOutputPortNames); parcel.writeInt(mIsPrivate ? 1 : 0); + parcel.writeInt(mDefaultProtocol); // "Basic" properties only contain properties of primitive types // and thus can be read back by native code. "Extra" properties is // a superset that contains all properties. diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index dee94c681e87..a049a8891f48 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -16,18 +16,25 @@ package android.media.midi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.SystemService; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; -import android.os.IBinder; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; /** @@ -39,6 +46,39 @@ public final class MidiManager { private static final String TAG = "MidiManager"; /** + * Constant representing MIDI devices. + * These devices do NOT support Universal MIDI Packets by default. + * These support the original MIDI 1.0 byte stream. + * When communicating to a USB device, a raw byte stream will be padded for USB. + * Likewise, for a Bluetooth device, the raw bytes will be converted for Bluetooth. + * For virtual devices, the byte stream will be passed directly. + * If Universal MIDI Packets are needed, please use MIDI-CI. + * @see MidiManager#getDevicesForTransport + */ + public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; + + /** + * Constant representing Universal MIDI devices. + * These devices do support Universal MIDI Packets (UMP) by default. + * When sending data to these devices, please send UMP. + * Packets should always be a multiple of 4 bytes. + * UMP is defined in the USB MIDI 2.0 spec. Please read the standard for more info. + * @see MidiManager#getDevicesForTransport + */ + public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; + + /** + * @see MidiManager#getDevicesForTransport + * @hide + */ + @IntDef(prefix = { "TRANSPORT_" }, value = { + TRANSPORT_MIDI_BYTE_STREAM, + TRANSPORT_UNIVERSAL_MIDI_PACKETS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Transport {} + + /** * Intent for starting BluetoothMidiService * @hide */ @@ -68,37 +108,43 @@ public final class MidiManager { private class DeviceListener extends IMidiDeviceListener.Stub { private final DeviceCallback mCallback; private final Handler mHandler; + private final int mTransport; - public DeviceListener(DeviceCallback callback, Handler handler) { + DeviceListener(DeviceCallback callback, Handler handler, int transport) { mCallback = callback; mHandler = handler; + mTransport = transport; } @Override public void onDeviceAdded(MidiDeviceInfo device) { - if (mHandler != null) { - final MidiDeviceInfo deviceF = device; - mHandler.post(new Runnable() { - @Override public void run() { - mCallback.onDeviceAdded(deviceF); - } - }); - } else { - mCallback.onDeviceAdded(device); + if (shouldInvokeCallback(device)) { + if (mHandler != null) { + final MidiDeviceInfo deviceF = device; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceAdded(deviceF); + } + }); + } else { + mCallback.onDeviceAdded(device); + } } } @Override public void onDeviceRemoved(MidiDeviceInfo device) { - if (mHandler != null) { - final MidiDeviceInfo deviceF = device; - mHandler.post(new Runnable() { - @Override public void run() { - mCallback.onDeviceRemoved(deviceF); - } - }); - } else { - mCallback.onDeviceRemoved(device); + if (shouldInvokeCallback(device)) { + if (mHandler != null) { + final MidiDeviceInfo deviceF = device; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceRemoved(deviceF); + } + }); + } else { + mCallback.onDeviceRemoved(device); + } } } @@ -115,6 +161,25 @@ public final class MidiManager { mCallback.onDeviceStatusChanged(status); } } + + /** + * Used to figure out whether callbacks should be invoked. Only invoke callbacks of + * the correct type. + * + * @param MidiDeviceInfo the device to check + * @return whether to invoke a callback + */ + private boolean shouldInvokeCallback(MidiDeviceInfo device) { + // UMP devices have protocols that are not PROTOCOL_UNKNOWN + if (mTransport == TRANSPORT_UNIVERSAL_MIDI_PACKETS) { + return (device.getDefaultProtocol() != MidiDeviceInfo.PROTOCOL_UNKNOWN); + } else if (mTransport == TRANSPORT_MIDI_BYTE_STREAM) { + return (device.getDefaultProtocol() == MidiDeviceInfo.PROTOCOL_UNKNOWN); + } else { + Log.e(TAG, "Invalid transport type: " + mTransport); + return false; + } + } } /** @@ -167,8 +232,10 @@ public final class MidiManager { } /** - * Registers a callback to receive notifications when MIDI devices are added and removed. - * + * Registers a callback to receive notifications when MIDI 1.0 devices are added and removed. + * These are devices that do not default to Universal MIDI Packets. To register for a callback + * for those, call {@link #registerDeviceCallbackForTransport} instead. + * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately * for any devices that have open ports. This allows applications to know which input * ports are already in use and, therefore, unavailable. @@ -182,7 +249,30 @@ public final class MidiManager { * callback is unspecified. */ public void registerDeviceCallback(DeviceCallback callback, Handler handler) { - DeviceListener deviceListener = new DeviceListener(callback, handler); + registerDeviceCallbackForTransport(callback, handler, TRANSPORT_MIDI_BYTE_STREAM); + } + + /** + * Registers a callback to receive notifications when MIDI devices are added and removed + * for a specific transport type. + * + * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately + * for any devices that have open ports. This allows applications to know which input + * ports are already in use and, therefore, unavailable. + * + * Applications should call {@link #getDevicesForTransport} before registering the callback + * to get a list of devices already added. + * + * @param callback a {@link DeviceCallback} for MIDI device notifications + * @param handler The {@link android.os.Handler Handler} that will be used for delivering the + * device notifications. If handler is null, then the thread used for the + * callback is unspecified. + * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or + * TRANSPORT_UNIVERSAL_MIDI_PACKETS. + */ + public void registerDeviceCallbackForTransport(@NonNull DeviceCallback callback, + @Nullable Handler handler, @Transport int transport) { + DeviceListener deviceListener = new DeviceListener(callback, handler, transport); try { mService.registerListener(mToken, deviceListener); } catch (RemoteException e) { @@ -208,9 +298,11 @@ public final class MidiManager { } /** - * Gets the list of all connected MIDI devices. + * Gets a list of connected MIDI devices. This returns all devices that do + * not default to Universal MIDI Packets. To get those instead, please call + * {@link #getDevicesForTransport} instead. * - * @return an array of all MIDI devices + * @return an array of MIDI devices */ public MidiDeviceInfo[] getDevices() { try { @@ -220,6 +312,29 @@ public final class MidiManager { } } + /** + * Gets a list of connected MIDI devices by transport. TRANSPORT_MIDI_BYTE_STREAM + * is used for MIDI 1.0 and is the most common. + * For devices with built in Universal MIDI Packet support, use + * TRANSPORT_UNIVERSAL_MIDI_PACKETS instead. + * + * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or + * TRANSPORT_UNIVERSAL_MIDI_PACKETS. + * @return a collection of MIDI devices + */ + public @NonNull Collection<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) { + try { + MidiDeviceInfo[] devices = mService.getDevicesForTransport(transport); + Collection<MidiDeviceInfo> out = new ArrayList<MidiDeviceInfo>(devices.length); + for (int i = 0; i < devices.length; i++) { + out.add(devices[i]); + } + return out; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void sendOpenDeviceResponse(final MidiDevice device, final OnDeviceOpenedListener listener, Handler handler) { if (handler != null) { @@ -311,13 +426,14 @@ public final class MidiManager { /** @hide */ public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, - Bundle properties, int type, MidiDeviceServer.Callback callback) { + Bundle properties, int type, int defaultProtocol, + MidiDeviceServer.Callback callback) { try { MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers, numOutputPorts, callback); MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames, - properties, type); + properties, type, defaultProtocol); if (deviceInfo == null) { Log.e(TAG, "registerVirtualDevice failed"); return null; diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html index 33c54900cd07..67df1b2fa315 100644 --- a/media/java/android/media/midi/package.html +++ b/media/java/android/media/midi/package.html @@ -405,5 +405,46 @@ apps using the <a href="#get_list_of_already_plugged_in_entities">MIDI device discovery calls described above</a>. </p> +<h1 id=using_midi_2_0_over_usb>Using MIDI 2.0 over USB</h1> + +<p>An app can use MIDI 2.0 over USB starting in Android T. MIDI 2.0 packets are embedded in +Universal MIDI Packets, or UMP for short. A MIDI 2.0 USB device should create two interfaces, +one endpoint that accepts only MIDI 1.0 packets and one that accepts only UMP packets. +For more info about MIDI 2.0 and UMP, please read the MIDI 2.0 USB spec.</p> + +<p>MidiManager.getDevices() would simply return the 1.0 interface. This interface should work +exactly the same as before. In order to use the new UMP interface, retrieve the device with the +following code snippet.</p> + +<pre class=prettyprint> +Collection<MidiDeviceInfo> universalDeviceInfos = midiManager.getDevicesForTransport( + MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS); +</pre> + +<p>UMP Packets are always in multiple of 4 bytes. For each set of 4 bytes, they are sent in network +order. Compare the following NoteOn code snippet with the NoteOn code snippet above. </p> + +<pre class=prettyprint> +byte[] buffer = new byte[32]; +int numBytes = 0; +int channel = 3; // MIDI channels 1-16 are encoded as 0-15. +int group = 0; +buffer[numBytes++] = (byte)(0x20 + group); // MIDI 1.0 voice message +buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on +buffer[numBytes++] = (byte)60; // pitch is middle C +buffer[numBytes++] = (byte)127; // max velocity +int offset = 0; +// post is non-blocking +inputPort.send(buffer, offset, numBytes); +</pre> + +<p>MIDI 2.0 messages can be sent through UMP after negotiating with the device. This is called +MIDI-CI and is documented in the MIDI 2.0 spec. Some USB devices support pre-negotiated MIDI 2.0. +For a MidiDeviceInfo, you can query the defaultProtocol.</p> + +<pre class=prettyprint> +int defaultProtocol = info.getDefaultProtocol(); +</pre> + </body> </html> diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java index 85ad3cdb5700..6ba913353896 100644 --- a/media/java/android/media/tv/BroadcastInfoRequest.java +++ b/media/java/android/media/tv/BroadcastInfoRequest.java @@ -49,8 +49,10 @@ public abstract class BroadcastInfoRequest implements Parcelable { return StreamEventRequest.createFromParcelBody(source); case TvInputManager.BROADCAST_INFO_TYPE_DSMCC: return DsmccRequest.createFromParcelBody(source); - case TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION: + case TvInputManager.BROADCAST_INFO_TYPE_COMMAND: return CommandRequest.createFromParcelBody(source); + case TvInputManager.BROADCAST_INFO_TYPE_TIMELINE: + return TimelineRequest.createFromParcelBody(source); default: throw new IllegalStateException( "Unexpected broadcast info request type (value " diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java index e423abaf550d..67bdedc83f31 100644 --- a/media/java/android/media/tv/BroadcastInfoResponse.java +++ b/media/java/android/media/tv/BroadcastInfoResponse.java @@ -50,8 +50,10 @@ public abstract class BroadcastInfoResponse implements Parcelable { return StreamEventResponse.createFromParcelBody(source); case TvInputManager.BROADCAST_INFO_TYPE_DSMCC: return DsmccResponse.createFromParcelBody(source); - case TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION: + case TvInputManager.BROADCAST_INFO_TYPE_COMMAND: return CommandResponse.createFromParcelBody(source); + case TvInputManager.BROADCAST_INFO_TYPE_TIMELINE: + return TimelineResponse.createFromParcelBody(source); default: throw new IllegalStateException( "Unexpected broadcast info response type (value " diff --git a/media/java/android/media/tv/CommandRequest.java b/media/java/android/media/tv/CommandRequest.java index 2391fa3a4aef..d61c85849ad2 100644 --- a/media/java/android/media/tv/CommandRequest.java +++ b/media/java/android/media/tv/CommandRequest.java @@ -23,7 +23,7 @@ import android.os.Parcelable; /** @hide */ public final class CommandRequest extends BroadcastInfoRequest implements Parcelable { public static final @TvInputManager.BroadcastInfoType int requestType = - TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION; + TvInputManager.BROADCAST_INFO_TYPE_COMMAND; public static final @NonNull Parcelable.Creator<CommandRequest> CREATOR = new Parcelable.Creator<CommandRequest>() { diff --git a/media/java/android/media/tv/CommandResponse.java b/media/java/android/media/tv/CommandResponse.java index d34681f443c2..af3d00ce5b90 100644 --- a/media/java/android/media/tv/CommandResponse.java +++ b/media/java/android/media/tv/CommandResponse.java @@ -23,7 +23,7 @@ import android.os.Parcelable; /** @hide */ public final class CommandResponse extends BroadcastInfoResponse implements Parcelable { public static final @TvInputManager.BroadcastInfoType int responseType = - TvInputManager.BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION; + TvInputManager.BROADCAST_INFO_TYPE_COMMAND; public static final @NonNull Parcelable.Creator<CommandResponse> CREATOR = new Parcelable.Creator<CommandResponse>() { diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java index e43d31adaed5..4d496207051a 100644 --- a/media/java/android/media/tv/DsmccResponse.java +++ b/media/java/android/media/tv/DsmccResponse.java @@ -21,6 +21,9 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import java.util.ArrayList; +import java.util.List; + /** @hide */ public final class DsmccResponse extends BroadcastInfoResponse implements Parcelable { public static final @TvInputManager.BroadcastInfoType int responseType = @@ -41,20 +44,27 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel }; private final ParcelFileDescriptor mFileDescriptor; + private final boolean mIsDirectory; + private final List<String> mChildren; public static DsmccResponse createFromParcelBody(Parcel in) { return new DsmccResponse(in); } public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult, - ParcelFileDescriptor file) { + ParcelFileDescriptor file, boolean isDirectory, List<String> children) { super(responseType, requestId, sequence, responseResult); mFileDescriptor = file; + mIsDirectory = isDirectory; + mChildren = children; } protected DsmccResponse(Parcel source) { super(responseType, source); mFileDescriptor = source.readFileDescriptor(); + mIsDirectory = (source.readInt() == 1); + mChildren = new ArrayList<>(); + source.readStringList(mChildren); } public ParcelFileDescriptor getFile() { @@ -65,5 +75,7 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); mFileDescriptor.writeToParcel(dest, flags); + dest.writeInt(mIsDirectory ? 1 : 0); + dest.writeStringList(mChildren); } } diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index f4f55e44255e..49148ceb84e2 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -47,6 +47,7 @@ oneway interface ITvInputClient { void onTimeShiftStartPositionChanged(long timeMs, int seq); void onTimeShiftCurrentPositionChanged(long timeMs, int seq); void onAitInfoUpdated(in AitInfo aitInfo, int seq); + void onSignalStrength(int stength, int seq); void onTuned(in Uri channelUri, int seq); // For the recording session diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index d34e6360d95f..2a33ee6a6b4d 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -66,6 +66,7 @@ interface ITvInputManager { int seq, int userId); void releaseSession(in IBinder sessionToken, int userId); int getClientPid(in String sessionId); + int getClientPriority(int useCase, in String sessionId); void setMainSession(in IBinder sessionToken, int userId); void setSurface(in IBinder sessionToken, in Surface surface, int userId); @@ -76,7 +77,7 @@ interface ITvInputManager { void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId); void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId); - void setIAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId); + void setInteractiveAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId); void sendAppPrivateCommand(in IBinder sessionToken, in String action, in Bundle data, int userId); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index f427501ff908..98200345b85d 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -42,7 +42,7 @@ oneway interface ITvInputSession { void setCaptionEnabled(boolean enabled); void selectTrack(int type, in String trackId); - void setIAppNotificationEnabled(boolean enable); + void setInteractiveAppNotificationEnabled(boolean enable); void appPrivateCommand(in String action, in Bundle data); diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index 9830e78a7faa..9dfdb78061a7 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -44,6 +44,7 @@ oneway interface ITvInputSessionCallback { void onTimeShiftStartPositionChanged(long timeMs); void onTimeShiftCurrentPositionChanged(long timeMs); void onAitInfoUpdated(in AitInfo aitInfo); + void onSignalStrength(int strength); // For the recording session void onTuned(in Uri channelUri); diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 418ab2cd59af..8911f6c7d4be 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -247,7 +247,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand break; } case DO_SET_IAPP_NOTIFICATION_ENABLED: { - mTvInputSessionImpl.setIAppNotificationEnabled((Boolean) msg.obj); + mTvInputSessionImpl.setInteractiveAppNotificationEnabled((Boolean) msg.obj); break; } case DO_REQUEST_AD: { @@ -322,7 +322,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override - public void setIAppNotificationEnabled(boolean enabled) { + public void setInteractiveAppNotificationEnabled(boolean enabled) { mCaller.executeOrSendMessage( mCaller.obtainMessageO(DO_SET_IAPP_NOTIFICATION_ENABLED, enabled)); } diff --git a/media/java/android/media/tv/OWNERS b/media/java/android/media/tv/OWNERS index 33acd0d5e0e2..fa0429350a25 100644 --- a/media/java/android/media/tv/OWNERS +++ b/media/java/android/media/tv/OWNERS @@ -1,6 +1,6 @@ -nchalko@google.com quxiangfang@google.com shubang@google.com +hgchen@google.com # For android remote service per-file ITvRemoteServiceInput.aidl = file:/media/lib/tvremote/OWNERS diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java index 912cbce81e93..68d5f8aca21e 100644 --- a/media/java/android/media/tv/TableResponse.java +++ b/media/java/android/media/tv/TableResponse.java @@ -17,9 +17,9 @@ package android.media.tv; import android.annotation.NonNull; +import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -import android.net.Uri; /** @hide */ public final class TableResponse extends BroadcastInfoResponse implements Parcelable { diff --git a/media/java/android/media/tv/TimelineRequest.java b/media/java/android/media/tv/TimelineRequest.java new file mode 100644 index 000000000000..0714972befb2 --- /dev/null +++ b/media/java/android/media/tv/TimelineRequest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public final class TimelineRequest extends BroadcastInfoRequest implements Parcelable { + private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE = + TvInputManager.BROADCAST_INFO_TYPE_TIMELINE; + + public static final @NonNull Parcelable.Creator<TimelineRequest> CREATOR = + new Parcelable.Creator<TimelineRequest>() { + @Override + public TimelineRequest createFromParcel(Parcel source) { + source.readInt(); + return createFromParcelBody(source); + } + + @Override + public TimelineRequest[] newArray(int size) { + return new TimelineRequest[size]; + } + }; + + private final int mIntervalMs; + + static TimelineRequest createFromParcelBody(Parcel in) { + return new TimelineRequest(in); + } + + public TimelineRequest(int requestId, @RequestOption int option, int intervalMs) { + super(REQUEST_TYPE, requestId, option); + mIntervalMs = intervalMs; + } + + protected TimelineRequest(Parcel source) { + super(REQUEST_TYPE, source); + mIntervalMs = source.readInt(); + } + + public int getIntervalMs() { + return mIntervalMs; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mIntervalMs); + } +} diff --git a/media/java/android/media/tv/TimelineResponse.java b/media/java/android/media/tv/TimelineResponse.java new file mode 100644 index 000000000000..fee10b4e56d6 --- /dev/null +++ b/media/java/android/media/tv/TimelineResponse.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public final class TimelineResponse extends BroadcastInfoResponse implements Parcelable { + private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE = + TvInputManager.BROADCAST_INFO_TYPE_TIMELINE; + + public static final @NonNull Parcelable.Creator<TimelineResponse> CREATOR = + new Parcelable.Creator<TimelineResponse>() { + @Override + public TimelineResponse createFromParcel(Parcel source) { + source.readInt(); + return createFromParcelBody(source); + } + + @Override + public TimelineResponse[] newArray(int size) { + return new TimelineResponse[size]; + } + }; + + private final String mSelector; + private final int mUnitsPerTick; + private final int mUnitsPerSecond; + private final long mWallClock; + private final long mTicks; + + static TimelineResponse createFromParcelBody(Parcel in) { + return new TimelineResponse(in); + } + + public TimelineResponse(int requestId, int sequence, + @ResponseResult int responseResult, String selector, int unitsPerTick, + int unitsPerSecond, long wallClock, long ticks) { + super(RESPONSE_TYPE, requestId, sequence, responseResult); + mSelector = selector; + mUnitsPerTick = unitsPerTick; + mUnitsPerSecond = unitsPerSecond; + mWallClock = wallClock; + mTicks = ticks; + } + + protected TimelineResponse(Parcel source) { + super(RESPONSE_TYPE, source); + mSelector = source.readString(); + mUnitsPerTick = source.readInt(); + mUnitsPerSecond = source.readInt(); + mWallClock = source.readLong(); + mTicks = source.readLong(); + } + + public String getSelector() { + return mSelector; + } + + public int getUnitsPerTick() { + return mUnitsPerTick; + } + + public int getUnitsPerSecond() { + return mUnitsPerSecond; + } + + public long getWallClock() { + return mWallClock; + } + + public long getTicks() { + return mTicks; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mSelector); + dest.writeInt(mUnitsPerTick); + dest.writeInt(mUnitsPerSecond); + dest.writeLong(mWallClock); + dest.writeLong(mTicks); + } +} diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index ad86002f862d..98d1599e62e9 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -18,6 +18,7 @@ package android.media.tv; import android.annotation.CallbackExecutor; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -27,6 +28,8 @@ import android.annotation.TestApi; import android.content.Context; import android.content.Intent; import android.graphics.Rect; +import android.media.AudioDeviceInfo; +import android.media.AudioFormat.Encoding; import android.media.PlaybackParams; import android.media.tv.interactive.TvIAppManager; import android.net.Uri; @@ -60,6 +63,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -359,9 +363,10 @@ public final class TvInputManager { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({BROADCAST_INFO_TYPE_TS, BROADCAST_INFO_TYPE_TABLE, BROADCAST_INFO_TYPE_SECTION, + @IntDef(prefix = "BROADCAST_INFO_TYPE_", value = + {BROADCAST_INFO_TYPE_TS, BROADCAST_INFO_TYPE_TABLE, BROADCAST_INFO_TYPE_SECTION, BROADCAST_INFO_TYPE_PES, BROADCAST_INFO_STREAM_EVENT, BROADCAST_INFO_TYPE_DSMCC, - BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION}) + BROADCAST_INFO_TYPE_COMMAND, BROADCAST_INFO_TYPE_TIMELINE}) public @interface BroadcastInfoType {} /** @hide */ @@ -377,7 +382,31 @@ public final class TvInputManager { /** @hide */ public static final int BROADCAST_INFO_TYPE_DSMCC = 6; /** @hide */ - public static final int BROADCAST_INFO_TYPE_TV_PROPRIETARY_FUNCTION = 7; + public static final int BROADCAST_INFO_TYPE_COMMAND = 7; + /** @hide */ + public static final int BROADCAST_INFO_TYPE_TIMELINE = 8; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "SIGNAL_STRENGTH_", + value = {SIGNAL_STRENGTH_LOST, SIGNAL_STRENGTH_WEAK, SIGNAL_STRENGTH_STRONG}) + public @interface SignalStrength {} + + /** + * Signal lost. + * @hide + */ + public static final int SIGNAL_STRENGTH_LOST = 1; + /** + * Weak signal. + * @hide + */ + public static final int SIGNAL_STRENGTH_WEAK = 2; + /** + * Strong signal. + * @hide + */ + public static final int SIGNAL_STRENGTH_STRONG = 3; /** * An unknown state of the client pid gets from the TvInputManager. Client gets this value when @@ -658,6 +687,14 @@ public final class TvInputManager { } /** + * This is called when signal strength is updated. + * @param session A {@link TvInputManager.Session} associated with this callback. + * @param strength The current signal strength. + */ + public void onSignalStrength(Session session, @SignalStrength int strength) { + } + + /** * This is called when the session has been tuned to the given channel. * * @param channelUri The URI of a channel. @@ -731,8 +768,9 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onTracksChanged(mSession, tracks); - if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) { - mSession.getIAppSession().notifyTracksChanged(tracks); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyTracksChanged(tracks); } } }); @@ -743,8 +781,9 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onTrackSelected(mSession, type, trackId); - if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) { - mSession.getIAppSession().notifyTrackSelected(type, trackId); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyTrackSelected(type, trackId); } } }); @@ -764,6 +803,10 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onVideoAvailable(mSession); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyVideoAvailable(); + } } }); } @@ -773,6 +816,10 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onVideoUnavailable(mSession, reason); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyVideoUnavailable(reason); + } } }); } @@ -782,6 +829,10 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onContentAllowed(mSession); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyContentAllowed(); + } } }); } @@ -791,6 +842,10 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onContentBlocked(mSession, rating); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyContentBlocked(rating); + } } }); } @@ -850,13 +905,27 @@ public final class TvInputManager { }); } + void postSignalStrength(final int strength) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onSignalStrength(mSession, strength); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifySignalStrength(strength); + } + } + }); + } + void postTuned(final Uri channelUri) { mHandler.post(new Runnable() { @Override public void run() { mSessionCallback.onTuned(mSession, channelUri); - if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) { - mSession.getIAppSession().notifyTuned(channelUri); + if (mSession.mIAppNotificationEnabled + && mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyTuned(channelUri); } } }); @@ -887,8 +956,9 @@ public final class TvInputManager { mHandler.post(new Runnable() { @Override public void run() { - if (mSession.getIAppSession() != null) { - mSession.getIAppSession().notifyBroadcastInfoResponse(response); + if (mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession() + .notifyBroadcastInfoResponse(response); } } }); @@ -900,8 +970,8 @@ public final class TvInputManager { mHandler.post(new Runnable() { @Override public void run() { - if (mSession.getIAppSession() != null) { - mSession.getIAppSession().notifyAdResponse(response); + if (mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyAdResponse(response); } } }); @@ -1283,6 +1353,18 @@ public final class TvInputManager { } @Override + public void onSignalStrength(int strength, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postSignalStrength(strength); + } + } + + @Override public void onTuned(Uri channelUri, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); @@ -1786,6 +1868,29 @@ public final class TvInputManager { }; /** + * Returns a priority for the given use case type and the client's foreground or background + * status. + * + * @param useCase the use case type of the client. When the given use case type is invalid, + * the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}. + * @param sessionId the unique id of the session owned by the client. When {@code null}, + * the caller will be used as a client. When the session is invalid, background status + * will be used as a client's status. Otherwise, TV app corresponding to the given + * session id will be used as a client. + * {@see TvInputService#onCreateSession(String, String)}. + * + * @return the use case priority value for the given use case type and the client's foreground + * or background status. + * + * @hide + */ + @SystemApi + public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase, + @Nullable String sessionId) { + return getClientPriorityInternal(useCase, sessionId); + }; + + /** * Creates a recording {@link Session} for a given TV input. * * <p>The number of sessions that can be created at the same time is limited by the capability @@ -1829,6 +1934,14 @@ public final class TvInputManager { return clientPid; } + private int getClientPriorityInternal(int useCase, String sessionId) { + try { + return mService.getClientPriority(useCase, sessionId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Returns the TvStreamConfig list of the given TV input. * @@ -2218,12 +2331,12 @@ public final class TvInputManager { mSessionCallbackRecordMap = sessionCallbackRecordMap; } - public TvIAppManager.Session getIAppSession() { + public TvIAppManager.Session getInteractiveAppSession() { return mIAppSession; } - public void setIAppSession(TvIAppManager.Session IAppSession) { - this.mIAppSession = IAppSession; + public void setInteractiveAppSession(TvIAppManager.Session iAppSession) { + this.mIAppSession = iAppSession; } /** @@ -2484,13 +2597,13 @@ public final class TvInputManager { * {@code false} otherwise. * @hide */ - public void setIAppNotificationEnabled(boolean enabled) { + public void setInteractiveAppNotificationEnabled(boolean enabled) { if (mToken == null) { Log.w(TAG, "The session has been already released"); return; } try { - mService.setIAppNotificationEnabled(mToken, enabled, mUserId); + mService.setInteractiveAppNotificationEnabled(mToken, enabled, mUserId); mIAppNotificationEnabled = enabled; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -3178,6 +3291,16 @@ public final class TvInputManager { return false; } + /** + * Override default audio sink from audio policy. + * + * @param audioType device type of the audio sink to override with. + * @param audioAddress device address of the audio sink to override with. + * @param samplingRate desired sampling rate. Use default when it's 0. + * @param channelMask desired channel mask. Use default when it's + * AudioFormat.CHANNEL_OUT_DEFAULT. + * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT. + */ public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, int channelMask, int format) { try { @@ -3187,5 +3310,27 @@ public final class TvInputManager { throw new RuntimeException(e); } } + + /** + * Override default audio sink from audio policy. + * + * @param device {@link android.media.AudioDeviceInfo} to use. + * @param samplingRate desired sampling rate. Use default when it's 0. + * @param channelMask desired channel mask. Use default when it's + * AudioFormat.CHANNEL_OUT_DEFAULT. + * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT. + */ + public void overrideAudioSink(@NonNull AudioDeviceInfo device, + @IntRange(from = 0) int samplingRate, + int channelMask, @Encoding int format) { + Objects.requireNonNull(device); + try { + mInterface.overrideAudioSink( + AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()), + device.getAddress(), samplingRate, channelMask, format); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } } } diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 3a40d6ff1fb4..524ba34685b5 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -966,6 +966,27 @@ public abstract class TvInputService extends Service { } /** + * Notifies signal strength. + * @hide + */ + public void notifySignalStrength(@TvInputManager.SignalStrength final int strength) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifySignalStrength"); + if (mSessionCallback != null) { + mSessionCallback.onSignalStrength(strength); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifySignalStrength", e); + } + } + }); + } + + /** * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position * is relative to the overlay view that sits on top of this surface. * @@ -1180,7 +1201,7 @@ public abstract class TvInputService extends Service { * @param enabled {@code true} to enable, {@code false} to disable. * @hide */ - public void onSetIAppNotificationEnabled(boolean enabled) { + public void onSetInteractiveAppNotificationEnabled(boolean enabled) { } /** @@ -1532,10 +1553,10 @@ public abstract class TvInputService extends Service { } /** - * Calls {@link #onSetIAppNotificationEnabled}. + * Calls {@link #onSetInteractiveAppNotificationEnabled}. */ - void setIAppNotificationEnabled(boolean enabled) { - onSetIAppNotificationEnabled(enabled); + void setInteractiveAppNotificationEnabled(boolean enabled) { + onSetInteractiveAppNotificationEnabled(enabled); } /** diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 4a12cd7eb534..71f6ad6dd034 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -486,9 +486,9 @@ public class TvView extends ViewGroup { * {@code false} otherwise. * @hide */ - public void setIAppNotificationEnabled(boolean enabled) { + public void setInteractiveAppNotificationEnabled(boolean enabled) { if (mSession != null) { - mSession.setIAppNotificationEnabled(enabled); + mSession.setInteractiveAppNotificationEnabled(enabled); } } @@ -1071,6 +1071,16 @@ public class TvView extends ViewGroup { } /** + * This is called when signal strength is updated. + * @param inputId The ID of the TV input bound to this view. + * @param strength The current signal strength. + * + * @hide + */ + public void onSignalStrength(String inputId, @TvInputManager.SignalStrength int strength) { + } + + /** * This is called when the session has been tuned to the given channel. * * @param channelUri The URI of a channel. @@ -1390,6 +1400,20 @@ public class TvView extends ViewGroup { } @Override + public void onSignalStrength(Session session, int strength) { + if (DEBUG) { + Log.d(TAG, "onSignalStrength(strength=" + strength + ")"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onSignalStrength - session not created"); + return; + } + if (mCallback != null) { + mCallback.onSignalStrength(mInputId, strength); + } + } + + @Override public void onTuned(Session session, Uri channelUri) { if (DEBUG) { Log.d(TAG, "onTuned(channelUri=" + channelUri + ")"); diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl index 23201faa1c5a..a19a2d2d6135 100644 --- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl @@ -20,9 +20,9 @@ import android.graphics.Rect; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; -import android.media.tv.interactive.ITvIAppClient; -import android.media.tv.interactive.ITvIAppManagerCallback; -import android.media.tv.interactive.TvIAppInfo; +import android.media.tv.interactive.ITvInteractiveAppClient; +import android.media.tv.interactive.ITvInteractiveAppManagerCallback; +import android.media.tv.interactive.TvInteractiveAppInfo; import android.net.Uri; import android.os.Bundle; import android.view.Surface; @@ -32,25 +32,34 @@ import android.view.Surface; * @hide */ interface ITvIAppManager { - List<TvIAppInfo> getTvIAppServiceList(int userId); + List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId); void prepare(String tiasId, int type, int userId); - void notifyAppLinkInfo(String tiasId, in Bundle info, int userId); + void registerAppLinkInfo(String tiasId, in Bundle info, int userId); + void unregisterAppLinkInfo(String tiasId, in Bundle info, int userId); void sendAppLinkCommand(String tiasId, in Bundle command, int userId); - void startIApp(in IBinder sessionToken, int userId); - void stopIApp(in IBinder sessionToken, int userId); + void startInteractiveApp(in IBinder sessionToken, int userId); + void stopInteractiveApp(in IBinder sessionToken, int userId); + void resetInteractiveApp(in IBinder sessionToken, int userId); void createBiInteractiveApp( in IBinder sessionToken, in Uri biIAppUri, in Bundle params, int userId); void destroyBiInteractiveApp(in IBinder sessionToken, in String biIAppId, int userId); + void setTeletextAppEnabled(in IBinder sessionToken, boolean enable, int userId); void sendCurrentChannelUri(in IBinder sessionToken, in Uri channelUri, int userId); void sendCurrentChannelLcn(in IBinder sessionToken, int lcn, int userId); void sendStreamVolume(in IBinder sessionToken, float volume, int userId); void sendTrackInfoList(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId); - void createSession( - in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId); + void sendCurrentTvInputId(in IBinder sessionToken, in String inputId, int userId); + void createSession(in ITvInteractiveAppClient client, in String iAppServiceId, int type, + int seq, int userId); void releaseSession(in IBinder sessionToken, int userId); void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId); void notifyTrackSelected(in IBinder sessionToken, int type, in String trackId, int userId); void notifyTracksChanged(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId); + void notifyVideoAvailable(in IBinder sessionToken, int userId); + void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId); + void notifyContentAllowed(in IBinder sessionToken, int userId); + void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId); + void notifySignalStrength(in IBinder sessionToken, int stength, int userId); void setSurface(in IBinder sessionToken, in Surface surface, int userId); void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height, int userId); @@ -63,6 +72,6 @@ interface ITvIAppManager { void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId); void removeMediaView(in IBinder sessionToken, int userId); - void registerCallback(in ITvIAppManagerCallback callback, int userId); - void unregisterCallback(in ITvIAppManagerCallback callback, int userId); + void registerCallback(in ITvInteractiveAppManagerCallback callback, int userId); + void unregisterCallback(in ITvInteractiveAppManagerCallback callback, int userId); } diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl index 892a800ca68d..1a8fc4671ec3 100644 --- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl @@ -24,11 +24,11 @@ import android.os.Bundle; import android.view.InputChannel; /** - * Interface a client of the ITvIAppManager implements, to identify itself and receive information - * about changes to the state of each TV interactive application service. + * Interface a client of the ITvInteractiveAppManager implements, to identify itself and receive + * information about changes to the state of each TV interactive application service. * @hide */ -oneway interface ITvIAppClient { +oneway interface ITvInteractiveAppClient { void onSessionCreated(in String iAppServiceId, IBinder token, in InputChannel channel, int seq); void onSessionReleased(int seq); void onLayoutSurface(int left, int top, int right, int bottom, int seq); @@ -36,11 +36,13 @@ oneway interface ITvIAppClient { void onRemoveBroadcastInfo(int id, int seq); void onSessionStateChanged(int state, int seq); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq); + void onTeletextAppStateChanged(int state, int seq); void onCommandRequest(in String cmdType, in Bundle parameters, int seq); void onSetVideoBounds(in Rect rect, int seq); void onRequestCurrentChannelUri(int seq); void onRequestCurrentChannelLcn(int seq); void onRequestStreamVolume(int seq); void onRequestTrackInfoList(int seq); + void onRequestCurrentTvInputId(int seq); void onAdRequest(in AdRequest request, int Seq); } diff --git a/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl index d5e0c639acc3..f4510f6c60a3 100644 --- a/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl @@ -16,16 +16,16 @@ package android.media.tv.interactive; -import android.media.tv.interactive.TvIAppInfo; +import android.media.tv.interactive.TvInteractiveAppInfo; /** - * Interface to receive callbacks from ITvIAppManager regardless of sessions. + * Interface to receive callbacks from ITvInteractiveAppManager regardless of sessions. * @hide */ -interface ITvIAppManagerCallback { - void onIAppServiceAdded(in String iAppServiceId); - void onIAppServiceRemoved(in String iAppServiceId); - void onIAppServiceUpdated(in String iAppServiceId); - void onTvIAppInfoUpdated(in TvIAppInfo tvIAppInfo); +interface ITvInteractiveAppManagerCallback { + void onInteractiveAppServiceAdded(in String iAppServiceId); + void onInteractiveAppServiceRemoved(in String iAppServiceId); + void onInteractiveAppServiceUpdated(in String iAppServiceId); + void onTvInteractiveAppInfoUpdated(in TvInteractiveAppInfo tvIAppInfo); void onStateChanged(in String iAppServiceId, int type, int state); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl index 8acb75f6b8b9..c1e66229670a 100644 --- a/media/java/android/media/tv/interactive/ITvIAppService.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl @@ -16,22 +16,23 @@ package android.media.tv.interactive; -import android.media.tv.interactive.ITvIAppServiceCallback; -import android.media.tv.interactive.ITvIAppSessionCallback; +import android.media.tv.interactive.ITvInteractiveAppServiceCallback; +import android.media.tv.interactive.ITvInteractiveAppSessionCallback; import android.os.Bundle; import android.view.InputChannel; /** - * Top-level interface to a TV IApp component (implemented in a Service). It's used for + * Top-level interface to a TV Interactive App component (implemented in a Service). It's used for * TvIAppManagerService to communicate with TvIAppService. * @hide */ -oneway interface ITvIAppService { - void registerCallback(in ITvIAppServiceCallback callback); - void unregisterCallback(in ITvIAppServiceCallback callback); - void createSession(in InputChannel channel, in ITvIAppSessionCallback callback, +oneway interface ITvInteractiveAppService { + void registerCallback(in ITvInteractiveAppServiceCallback callback); + void unregisterCallback(in ITvInteractiveAppServiceCallback callback); + void createSession(in InputChannel channel, in ITvInteractiveAppSessionCallback callback, in String iAppServiceId, int type); void prepare(int type); - void notifyAppLinkInfo(in Bundle info); + void registerAppLinkInfo(in Bundle info); + void unregisterAppLinkInfo(in Bundle info); void sendAppLinkCommand(in Bundle command); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl index fec7d7804f30..f56d3bd284da 100644 --- a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl @@ -17,10 +17,10 @@ package android.media.tv.interactive; /** - * Helper interface for ITvIAppService to allow the TvIAppService to notify the + * Helper interface for ITvInteractiveAppService to allow the TvIAppService to notify the * TvIAppManagerService. * @hide */ -oneway interface ITvIAppServiceCallback { +oneway interface ITvInteractiveAppServiceCallback { void onStateChanged(int type, int state); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl index 52f9a874aca2..c449d2475428 100644 --- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl @@ -26,22 +26,31 @@ import android.os.Bundle; import android.view.Surface; /** - * Sub-interface of ITvIAppService.aidl which is created per session and has its own context. + * Sub-interface of ITvInteractiveAppService.aidl which is created per session and has its own + * context. * @hide */ -oneway interface ITvIAppSession { - void startIApp(); - void stopIApp(); +oneway interface ITvInteractiveAppSession { + void startInteractiveApp(); + void stopInteractiveApp(); + void resetInteractiveApp(); void createBiInteractiveApp(in Uri biIAppUri, in Bundle params); void destroyBiInteractiveApp(in String biIAppId); + void setTeletextAppEnabled(boolean enable); void sendCurrentChannelUri(in Uri channelUri); void sendCurrentChannelLcn(int lcn); void sendStreamVolume(float volume); void sendTrackInfoList(in List<TvTrackInfo> tracks); + void sendCurrentTvInputId(in String inputId); void release(); void notifyTuned(in Uri channelUri); void notifyTrackSelected(int type, in String trackId); void notifyTracksChanged(in List<TvTrackInfo> tracks); + void notifyVideoAvailable(); + void notifyVideoUnavailable(int reason); + void notifyContentAllowed(); + void notifyContentBlocked(in String rating); + void notifySignalStrength(int strength); void setSurface(in Surface surface); void dispatchSurfaceChanged(int format, int width, int height); void notifyBroadcastInfoResponse(in BroadcastInfoResponse response); diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl index 9b9e6afb786b..c270424b4067 100644 --- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl @@ -19,27 +19,29 @@ package android.media.tv.interactive; import android.graphics.Rect; import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; -import android.media.tv.interactive.ITvIAppSession; +import android.media.tv.interactive.ITvInteractiveAppSession; import android.net.Uri; import android.os.Bundle; /** - * Helper interface for ITvIAppSession to allow TvIAppService to notify the system service when - * there is a related event. + * Helper interface for ITvInteractiveAppSession to allow TvIAppService to notify the + * system service when there is a related event. * @hide */ -oneway interface ITvIAppSessionCallback { - void onSessionCreated(in ITvIAppSession session); +oneway interface ITvInteractiveAppSessionCallback { + void onSessionCreated(in ITvInteractiveAppSession session); void onLayoutSurface(int left, int top, int right, int bottom); void onBroadcastInfoRequest(in BroadcastInfoRequest request); void onRemoveBroadcastInfo(int id); void onSessionStateChanged(int state); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId); + void onTeletextAppStateChanged(int state); void onCommandRequest(in String cmdType, in Bundle parameters); void onSetVideoBounds(in Rect rect); void onRequestCurrentChannelUri(); void onRequestCurrentChannelLcn(); void onRequestStreamVolume(); void onRequestTrackInfoList(); + void onRequestCurrentTvInputId(); void onAdRequest(in AdRequest request); } diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.java b/media/java/android/media/tv/interactive/TvIAppInfo.java deleted file mode 100644 index b5245fc0856e..000000000000 --- a/media/java/android/media/tv/interactive/TvIAppInfo.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.tv.interactive; - -import android.annotation.NonNull; -import android.annotation.StringDef; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.util.Xml; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; - -/** - * This class is used to specify meta information of a TV interactive app. - * @hide - */ -public final class TvIAppInfo implements Parcelable { - private static final boolean DEBUG = false; - private static final String TAG = "TvIAppInfo"; - - @Retention(RetentionPolicy.SOURCE) - @StringDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = { - INTERACTIVE_APP_TYPE_HBBTV, - INTERACTIVE_APP_TYPE_ATSC, - INTERACTIVE_APP_TYPE_GINGA, - }) - @interface InteractiveAppType {} - - /** HbbTV interactive app type */ - public static final String INTERACTIVE_APP_TYPE_HBBTV = "hbbtv"; - /** ATSC interactive app type */ - public static final String INTERACTIVE_APP_TYPE_ATSC = "atsc"; - /** Ginga interactive app type */ - public static final String INTERACTIVE_APP_TYPE_GINGA = "ginga"; - - private final ResolveInfo mService; - private final String mId; - private List<String> mTypes = new ArrayList<>(); - - private TvIAppInfo(ResolveInfo service, String id, List<String> types) { - mService = service; - mId = id; - mTypes = types; - } - - private TvIAppInfo(@NonNull Parcel in) { - mService = ResolveInfo.CREATOR.createFromParcel(in); - mId = in.readString(); - in.readStringList(mTypes); - } - - public static final @NonNull Creator<TvIAppInfo> CREATOR = new Creator<TvIAppInfo>() { - @Override - public TvIAppInfo createFromParcel(Parcel in) { - return new TvIAppInfo(in); - } - - @Override - public TvIAppInfo[] newArray(int size) { - return new TvIAppInfo[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - mService.writeToParcel(dest, flags); - dest.writeString(mId); - dest.writeStringList(mTypes); - } - - @NonNull - public String getId() { - return mId; - } - - /** - * Returns the component of the TV IApp service. - * @hide - */ - public ComponentName getComponent() { - return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); - } - - /** - * Returns the information of the service that implements this TV IApp service. - */ - public ServiceInfo getServiceInfo() { - return mService.serviceInfo; - } - - /** - * Gets supported interactive app types - */ - @NonNull - public List<String> getSupportedTypes() { - return new ArrayList<>(mTypes); - } - - /** - * A convenience builder for creating {@link TvIAppInfo} objects. - */ - public static final class Builder { - private static final String XML_START_TAG_NAME = "tv-iapp"; - private final Context mContext; - private final ResolveInfo mResolveInfo; - private final List<String> mTypes = new ArrayList<>(); - - /** - * Constructs a new builder for {@link TvIAppInfo}. - * - * @param context A Context of the application package implementing this class. - * @param component The name of the application component to be used for the - * {@link TvIAppService}. - */ - public Builder(@NonNull Context context, @NonNull ComponentName component) { - if (context == null) { - throw new IllegalArgumentException("context cannot be null."); - } - Intent intent = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component); - mResolveInfo = context.getPackageManager().resolveService(intent, - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); - if (mResolveInfo == null) { - throw new IllegalArgumentException("Invalid component. Can't find the service."); - } - mContext = context; - } - - /** - * Creates a {@link TvIAppInfo} instance with the specified fields. Most of the information - * is obtained by parsing the AndroidManifest and {@link TvIAppService#SERVICE_META_DATA} - * for the {@link TvIAppService} this TV IApp implements. - * - * @return TvIAppInfo containing information about this TV IApp service. - */ - @NonNull - public TvIAppInfo build() { - ComponentName componentName = new ComponentName(mResolveInfo.serviceInfo.packageName, - mResolveInfo.serviceInfo.name); - String id; - id = generateIAppServiceId(componentName); - parseServiceMetadata(); - return new TvIAppInfo(mResolveInfo, id, mTypes); - } - - private static String generateIAppServiceId(ComponentName name) { - return name.flattenToShortString(); - } - - private void parseServiceMetadata() { - ServiceInfo si = mResolveInfo.serviceInfo; - PackageManager pm = mContext.getPackageManager(); - try (XmlResourceParser parser = - si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) { - if (parser == null) { - throw new IllegalStateException("No " + TvIAppService.SERVICE_META_DATA - + " meta-data found for " + si.name); - } - - Resources res = pm.getResourcesForApplication(si.applicationInfo); - AttributeSet attrs = Xml.asAttributeSet(parser); - - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && type != XmlPullParser.START_TAG) { - // move to the START_TAG - } - - String nodeName = parser.getName(); - if (!XML_START_TAG_NAME.equals(nodeName)) { - throw new IllegalStateException("Meta-data does not start with " - + XML_START_TAG_NAME + " tag for " + si.name); - } - - TypedArray sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.TvIAppService); - CharSequence[] types = sa.getTextArray( - com.android.internal.R.styleable.TvIAppService_supportedTypes); - for (CharSequence cs : types) { - mTypes.add(cs.toString().toLowerCase()); - } - - sa.recycle(); - } catch (IOException | XmlPullParserException e) { - throw new IllegalStateException( - "Failed reading meta-data for " + si.packageName, e); - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalStateException("No resources found for " + si.packageName, e); - } - } - } -} diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java index d1fd1df74e1b..f819438944f4 100644..100755 --- a/media/java/android/media/tv/interactive/TvIAppManager.java +++ b/media/java/android/media/tv/interactive/TvIAppManager.java @@ -26,6 +26,7 @@ import android.media.tv.AdRequest; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; +import android.media.tv.TvContentRating; import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; import android.net.Uri; @@ -63,39 +64,63 @@ public final class TvIAppManager { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = false, prefix = "TV_IAPP_RTE_STATE_", value = { - TV_IAPP_RTE_STATE_UNREALIZED, - TV_IAPP_RTE_STATE_PREPARING, - TV_IAPP_RTE_STATE_READY, - TV_IAPP_RTE_STATE_ERROR}) - public @interface TvIAppRteState {} + @IntDef(flag = false, prefix = "TV_INTERACTIVE_APP_RTE_STATE_", value = { + TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED, + TV_INTERACTIVE_APP_RTE_STATE_PREPARING, + TV_INTERACTIVE_APP_RTE_STATE_READY, + TV_INTERACTIVE_APP_RTE_STATE_ERROR}) + public @interface TvInteractiveAppRteState {} /** * Unrealized state of interactive app RTE. * @hide */ - public static final int TV_IAPP_RTE_STATE_UNREALIZED = 1; + public static final int TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED = 1; /** * Preparing state of interactive app RTE. * @hide */ - public static final int TV_IAPP_RTE_STATE_PREPARING = 2; + public static final int TV_INTERACTIVE_APP_RTE_STATE_PREPARING = 2; /** * Ready state of interactive app RTE. * @hide */ - public static final int TV_IAPP_RTE_STATE_READY = 3; + public static final int TV_INTERACTIVE_APP_RTE_STATE_READY = 3; /** * Error state of interactive app RTE. * @hide */ - public static final int TV_IAPP_RTE_STATE_ERROR = 4; + public static final int TV_INTERACTIVE_APP_RTE_STATE_ERROR = 4; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = { + TELETEXT_APP_STATE_SHOW, + TELETEXT_APP_STATE_HIDE, + TELETEXT_APP_STATE_ERROR}) + public @interface TeletextAppState {} + + /** + * Show state of Teletext app. + * @hide + */ + public static final int TELETEXT_APP_STATE_SHOW = 1; + /** + * Hide state of Teletext app. + * @hide + */ + public static final int TELETEXT_APP_STATE_HIDE = 2; + /** + * Error state of Teletext app. + * @hide + */ + public static final int TELETEXT_APP_STATE_ERROR = 3; /** * Key for package name in app link. * <p>Type: String * - * @see #notifyAppLinkInfo(String, Bundle) + * @see #registerAppLinkInfo(String, Bundle) * @see #sendAppLinkCommand(String, Bundle) * @hide */ @@ -105,7 +130,7 @@ public final class TvIAppManager { * Key for class name in app link. * <p>Type: String * - * @see #notifyAppLinkInfo(String, Bundle) + * @see #registerAppLinkInfo(String, Bundle) * @see #sendAppLinkCommand(String, Bundle) * @hide */ @@ -115,7 +140,7 @@ public final class TvIAppManager { * Key for URI scheme in app link. * <p>Type: String * - * @see #notifyAppLinkInfo(String, Bundle) + * @see #registerAppLinkInfo(String, Bundle) * @hide */ public static final String KEY_URI_SCHEME = "uri_scheme"; @@ -124,7 +149,7 @@ public final class TvIAppManager { * Key for URI host in app link. * <p>Type: String * - * @see #notifyAppLinkInfo(String, Bundle) + * @see #registerAppLinkInfo(String, Bundle) * @hide */ public static final String KEY_URI_HOST = "uri_host"; @@ -133,7 +158,7 @@ public final class TvIAppManager { * Key for URI prefix in app link. * <p>Type: String * - * @see #notifyAppLinkInfo(String, Bundle) + * @see #registerAppLinkInfo(String, Bundle) * @hide */ public static final String KEY_URI_PREFIX = "uri_prefix"; @@ -173,7 +198,7 @@ public final class TvIAppManager { new SparseArray<>(); // @GuardedBy("mLock") - private final List<TvIAppCallbackRecord> mCallbackRecords = new LinkedList<>(); + private final List<TvInteractiveAppCallbackRecord> mCallbackRecords = new LinkedList<>(); // A sequence number for the next session to be created. Should be protected by a lock // {@code mSessionCallbackRecordMap}. @@ -181,13 +206,13 @@ public final class TvIAppManager { private final Object mLock = new Object(); - private final ITvIAppClient mClient; + private final ITvInteractiveAppClient mClient; /** @hide */ public TvIAppManager(ITvIAppManager service, int userId) { mService = service; mUserId = userId; - mClient = new ITvIAppClient.Stub() { + mClient = new ITvInteractiveAppClient.Stub() { @Override public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel, int seq) { @@ -259,8 +284,10 @@ public final class TvIAppManager { } @Override - public void onCommandRequest(@TvIAppService.IAppServiceCommandType String cmdType, - Bundle parameters, int seq) { + public void onCommandRequest( + @TvIAppService.InteractiveAppServiceCommandType String cmdType, + Bundle parameters, + int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { @@ -344,6 +371,18 @@ public final class TvIAppManager { } @Override + public void onRequestCurrentTvInputId(int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postRequestCurrentTvInputId(); + } + } + + @Override public void onSessionStateChanged(int state, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); @@ -366,41 +405,54 @@ public final class TvIAppManager { record.postBiInteractiveAppCreated(biIAppUri, biIAppId); } } + + @Override + public void onTeletextAppStateChanged(int state, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postTeletextAppStateChanged(state); + } + } }; - ITvIAppManagerCallback managerCallback = new ITvIAppManagerCallback.Stub() { + ITvInteractiveAppManagerCallback managerCallback = + new ITvInteractiveAppManagerCallback.Stub() { @Override - public void onIAppServiceAdded(String iAppServiceId) { + public void onInteractiveAppServiceAdded(String iAppServiceId) { synchronized (mLock) { - for (TvIAppCallbackRecord record : mCallbackRecords) { - record.postIAppServiceAdded(iAppServiceId); + for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { + record.postInteractiveAppServiceAdded(iAppServiceId); } } } @Override - public void onIAppServiceRemoved(String iAppServiceId) { + public void onInteractiveAppServiceRemoved(String iAppServiceId) { synchronized (mLock) { - for (TvIAppCallbackRecord record : mCallbackRecords) { - record.postIAppServiceRemoved(iAppServiceId); + for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { + record.postInteractiveAppServiceRemoved(iAppServiceId); } } } @Override - public void onIAppServiceUpdated(String iAppServiceId) { + public void onInteractiveAppServiceUpdated(String iAppServiceId) { synchronized (mLock) { - for (TvIAppCallbackRecord record : mCallbackRecords) { - record.postIAppServiceUpdated(iAppServiceId); + for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { + record.postInteractiveAppServiceUpdated(iAppServiceId); } } } @Override - public void onTvIAppInfoUpdated(TvIAppInfo iAppInfo) { - // TODO: add public API updateIAppInfo() + public void onTvInteractiveAppInfoUpdated(TvInteractiveAppInfo iAppInfo) { + // TODO: add public API updateInteractiveAppInfo() synchronized (mLock) { - for (TvIAppCallbackRecord record : mCallbackRecords) { - record.postTvIAppInfoUpdated(iAppInfo); + for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { + record.postTvInteractiveAppInfoUpdated(iAppInfo); } } } @@ -408,7 +460,7 @@ public final class TvIAppManager { @Override public void onStateChanged(String iAppServiceId, int type, int state) { synchronized (mLock) { - for (TvIAppCallbackRecord record : mCallbackRecords) { + for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { record.postStateChanged(iAppServiceId, type, state); } } @@ -424,110 +476,112 @@ public final class TvIAppManager { } /** - * Callback used to monitor status of the TV IApp. + * Callback used to monitor status of the TV Interactive App. * @hide */ - public abstract static class TvIAppCallback { + public abstract static class TvInteractiveAppCallback { /** - * This is called when a TV IApp service is added to the system. + * This is called when a TV Interactive App service is added to the system. * - * <p>Normally it happens when the user installs a new TV IApp service package that - * implements {@link TvIAppService} interface. + * <p>Normally it happens when the user installs a new TV Interactive App service package + * that implements {@link TvIAppService} interface. * - * @param iAppServiceId The ID of the TV IApp service. + * @param iAppServiceId The ID of the TV Interactive App service. */ - public void onIAppServiceAdded(@NonNull String iAppServiceId) { + public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) { } /** - * This is called when a TV IApp service is removed from the system. + * This is called when a TV Interactive App service is removed from the system. * - * <p>Normally it happens when the user uninstalls the previously installed TV IApp service - * package. + * <p>Normally it happens when the user uninstalls the previously installed TV Interactive + * App service package. * - * @param iAppServiceId The ID of the TV IApp service. + * @param iAppServiceId The ID of the TV Interactive App service. */ - public void onIAppServiceRemoved(@NonNull String iAppServiceId) { + public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) { } /** - * This is called when a TV IApp service is updated on the system. + * This is called when a TV Interactive App service is updated on the system. * - * <p>Normally it happens when a previously installed TV IApp service package is + * <p>Normally it happens when a previously installed TV Interactive App service package is * re-installed or a newer version of the package exists becomes available/unavailable. * - * @param iAppServiceId The ID of the TV IApp service. + * @param iAppServiceId The ID of the TV Interactive App service. */ - public void onIAppServiceUpdated(@NonNull String iAppServiceId) { + public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) { } /** - * This is called when the information about an existing TV IApp service has been updated. + * This is called when the information about an existing TV Interactive App service has been + * updated. * - * <p>Because the system automatically creates a <code>TvIAppInfo</code> object for each TV - * IApp service based on the information collected from the + * <p>Because the system automatically creates a <code>TvInteractiveAppInfo</code> object + * for each TV Interactive App service based on the information collected from the * <code>AndroidManifest.xml</code>, this method is only called back when such information * has changed dynamically. * - * @param iAppInfo The <code>TvIAppInfo</code> object that contains new information. + * @param iAppInfo The <code>TvInteractiveAppInfo</code> object that contains new + * information. */ - public void onTvIAppInfoUpdated(@NonNull TvIAppInfo iAppInfo) { + public void onTvInteractiveAppInfoUpdated(@NonNull TvInteractiveAppInfo iAppInfo) { } /** * This is called when the state of the interactive app service is changed. * @hide */ - public void onTvIAppServiceStateChanged( - @NonNull String iAppServiceId, int type, @TvIAppRteState int state) { + public void onTvInteractiveAppServiceStateChanged( + @NonNull String iAppServiceId, int type, @TvInteractiveAppRteState int state) { } } - private static final class TvIAppCallbackRecord { - private final TvIAppCallback mCallback; + private static final class TvInteractiveAppCallbackRecord { + private final TvInteractiveAppCallback mCallback; private final Handler mHandler; - TvIAppCallbackRecord(TvIAppCallback callback, Handler handler) { + TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Handler handler) { mCallback = callback; mHandler = handler; } - public TvIAppCallback getCallback() { + public TvInteractiveAppCallback getCallback() { return mCallback; } - public void postIAppServiceAdded(final String iAppServiceId) { + public void postInteractiveAppServiceAdded(final String iAppServiceId) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onIAppServiceAdded(iAppServiceId); + mCallback.onInteractiveAppServiceAdded(iAppServiceId); } }); } - public void postIAppServiceRemoved(final String iAppServiceId) { + public void postInteractiveAppServiceRemoved(final String iAppServiceId) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onIAppServiceRemoved(iAppServiceId); + mCallback.onInteractiveAppServiceRemoved(iAppServiceId); } }); } - public void postIAppServiceUpdated(final String iAppServiceId) { + public void postInteractiveAppServiceUpdated(final String iAppServiceId) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onIAppServiceUpdated(iAppServiceId); + mCallback.onInteractiveAppServiceUpdated(iAppServiceId); } }); } - public void postTvIAppInfoUpdated(final TvIAppInfo iAppInfo) { + public void postTvInteractiveAppInfoUpdated(final TvInteractiveAppInfo iAppInfo) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onTvIAppInfoUpdated(iAppInfo); + mCallback.onTvInteractiveAppInfoUpdated(iAppInfo); } }); } @@ -536,7 +590,7 @@ public final class TvIAppManager { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onTvIAppServiceStateChanged(iAppServiceId, type, state); + mCallback.onTvInteractiveAppServiceStateChanged(iAppServiceId, type, state); } }); } @@ -577,23 +631,23 @@ public final class TvIAppManager { } /** - * Returns the complete list of TV IApp service on the system. + * Returns the complete list of TV Interactive App service on the system. * - * @return List of {@link TvIAppInfo} for each TV IApp service that describes its meta - * information. + * @return List of {@link TvInteractiveAppInfo} for each TV Interactive App service that + * describes its meta information. * @hide */ @NonNull - public List<TvIAppInfo> getTvIAppServiceList() { + public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList() { try { - return mService.getTvIAppServiceList(mUserId); + return mService.getTvInteractiveAppServiceList(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Prepares TV IApp service for the given type. + * Prepares TV Interactive App service for the given type. * @hide */ public void prepare(@NonNull String tvIAppServiceId, int type) { @@ -605,12 +659,25 @@ public final class TvIAppManager { } /** - * Notifies app link info. + * Registers app link info. + * @hide + */ + public void registerAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) { + try { + mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters app link info. * @hide */ - public void notifyAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) { + public void unregisterAppLinkInfo( + @NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) { try { - mService.notifyAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); + mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -629,32 +696,33 @@ public final class TvIAppManager { } /** - * Registers a {@link TvIAppManager.TvIAppCallback}. + * Registers a {@link TvInteractiveAppCallback}. * - * @param callback A callback used to monitor status of the TV IApp services. + * @param callback A callback used to monitor status of the TV Interactive App services. * @param handler A {@link Handler} that the status change will be delivered to. * @hide */ - public void registerCallback(@NonNull TvIAppCallback callback, @NonNull Handler handler) { + public void registerCallback( + @NonNull TvInteractiveAppCallback callback, @NonNull Handler handler) { Preconditions.checkNotNull(callback); Preconditions.checkNotNull(handler); synchronized (mLock) { - mCallbackRecords.add(new TvIAppCallbackRecord(callback, handler)); + mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, handler)); } } /** - * Unregisters the existing {@link TvIAppManager.TvIAppCallback}. + * Unregisters the existing {@link TvInteractiveAppCallback}. * * @param callback The existing callback to remove. * @hide */ - public void unregisterCallback(@NonNull final TvIAppCallback callback) { + public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) { Preconditions.checkNotNull(callback); synchronized (mLock) { - for (Iterator<TvIAppCallbackRecord> it = mCallbackRecords.iterator(); + for (Iterator<TvInteractiveAppCallbackRecord> it = mCallbackRecords.iterator(); it.hasNext(); ) { - TvIAppCallbackRecord record = it.next(); + TvInteractiveAppCallbackRecord record = it.next(); if (record.getCallback() == callback) { it.remove(); break; @@ -691,8 +759,8 @@ public final class TvIAppManager { private TvInputEventSender mSender; private InputChannel mInputChannel; - private Session(IBinder token, InputChannel channel, ITvIAppManager service, int userId, - int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { + private Session(IBinder token, InputChannel channel, ITvIAppManager service, + int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { mToken = token; mInputChannel = channel; mService = service; @@ -709,25 +777,37 @@ public final class TvIAppManager { mInputSession = inputSession; } - void startIApp() { + void startInteractiveApp() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.startInteractiveApp(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + void stopInteractiveApp() { if (mToken == null) { Log.w(TAG, "The session has been already released"); return; } try { - mService.startIApp(mToken, mUserId); + mService.stopInteractiveApp(mToken, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - void stopIApp() { + void resetInteractiveApp() { if (mToken == null) { Log.w(TAG, "The session has been already released"); return; } try { - mService.stopIApp(mToken, mUserId); + mService.resetInteractiveApp(mToken, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -757,6 +837,18 @@ public final class TvIAppManager { } } + void setTeletextAppEnabled(boolean enable) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.setTeletextAppEnabled(mToken, enable, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + void sendCurrentChannelUri(@Nullable Uri channelUri) { if (mToken == null) { Log.w(TAG, "The session has been already released"); @@ -805,6 +897,18 @@ public final class TvIAppManager { } } + void sendCurrentTvInputId(@Nullable String inputId) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.sendCurrentTvInputId(mToken, inputId, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Sets the {@link android.view.Surface} for this session. * @@ -993,7 +1097,7 @@ public final class TvIAppManager { } /** - * Notifies IAPP session when a channel is tuned. + * Notifies Interactive APP session when a channel is tuned. */ public void notifyTuned(Uri channelUri) { if (mToken == null) { @@ -1008,7 +1112,7 @@ public final class TvIAppManager { } /** - * Notifies IAPP session when a track is selected. + * Notifies Interactive APP session when a track is selected. */ public void notifyTrackSelected(int type, String trackId) { if (mToken == null) { @@ -1023,7 +1127,7 @@ public final class TvIAppManager { } /** - * Notifies IAPP session when tracks are changed. + * Notifies Interactive APP session when tracks are changed. */ public void notifyTracksChanged(List<TvTrackInfo> tracks) { if (mToken == null) { @@ -1037,6 +1141,81 @@ public final class TvIAppManager { } } + /** + * Notifies IAPP session when video is available. + */ + public void notifyVideoAvailable() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyVideoAvailable(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notifies IAPP session when video is unavailable. + */ + public void notifyVideoUnavailable(int reason) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyVideoUnavailable(mToken, reason, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notifies IAPP session when content is allowed. + */ + public void notifyContentAllowed() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyContentAllowed(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notifies IAPP session when content is blocked. + */ + public void notifyContentBlocked(TvContentRating rating) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyContentBlocked(mToken, rating.flattenToString(), mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notifies Interactive APP session when signal strength is changed. + */ + public void notifySignalStrength(int strength) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifySignalStrength(mToken, strength, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void flushPendingEventsLocked() { mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); @@ -1298,7 +1477,8 @@ public final class TvIAppManager { }); } - void postCommandRequest(final @TvIAppService.IAppServiceCommandType String cmdType, + void postCommandRequest( + final @TvIAppService.InteractiveAppServiceCommandType String cmdType, final Bundle parameters) { mHandler.post(new Runnable() { @Override @@ -1353,6 +1533,15 @@ public final class TvIAppManager { }); } + void postRequestCurrentTvInputId() { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRequestCurrentTvInputId(mSession); + } + }); + } + void postAdRequest(final AdRequest request) { mHandler.post(new Runnable() { @Override @@ -1381,6 +1570,15 @@ public final class TvIAppManager { } }); } + + void postTeletextAppStateChanged(int state) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onTeletextAppStateChanged(mSession, state); + } + }); + } } /** @@ -1391,8 +1589,8 @@ public final class TvIAppManager { /** * This is called after {@link TvIAppManager#createSession} has been processed. * - * @param session A {@link TvIAppManager.Session} instance created. This can be {@code null} - * if the creation request failed. + * @param session A {@link TvIAppManager.Session} instance created. This can be + * {@code null} if the creation request failed. */ public void onSessionCreated(@Nullable Session session) { } @@ -1407,8 +1605,8 @@ public final class TvIAppManager { } /** - * This is called when {@link TvIAppService.Session#layoutSurface} is called to change the - * layout of surface. + * This is called when {@link TvIAppService.Session#layoutSurface} is called to + * change the layout of surface. * * @param session A {@link TvIAppManager.Session} associated with this callback. * @param left Left position. @@ -1426,8 +1624,10 @@ public final class TvIAppManager { * @param cmdType type of the command. * @param parameters parameters of the command. */ - public void onCommandRequest(Session session, - @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) { + public void onCommandRequest( + Session session, + @TvIAppService.InteractiveAppServiceCommandType String cmdType, + Bundle parameters) { } /** @@ -1439,7 +1639,8 @@ public final class TvIAppManager { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is called. + * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is + * called. * * @param session A {@link TvIAppManager.Session} associated with this callback. */ @@ -1447,7 +1648,8 @@ public final class TvIAppManager { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is called. + * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is + * called. * * @param session A {@link TvIAppManager.Session} associated with this callback. */ @@ -1455,7 +1657,8 @@ public final class TvIAppManager { } /** - * This is called when {@link TvIAppService.Session#RequestStreamVolume} is called. + * This is called when {@link TvIAppService.Session#RequestStreamVolume} is + * called. * * @param session A {@link TvIAppManager.Session} associated with this callback. */ @@ -1463,7 +1666,8 @@ public final class TvIAppManager { } /** - * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is called. + * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is + * called. * * @param session A {@link TvIAppManager.Session} associated with this callback. */ @@ -1471,6 +1675,15 @@ public final class TvIAppManager { } /** + * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called. + * + * @param session A {@link TvIAppManager.Session} associated with this callback. + * @hide + */ + public void onRequestCurrentTvInputId(Session session) { + } + + /** * This is called when {@link TvIAppService.Session#notifySessionStateChanged} is called. * * @param session A {@link TvIAppManager.Session} associated with this callback. @@ -1480,8 +1693,8 @@ public final class TvIAppManager { } /** - * This is called when {@link TvIAppService.Session#notifyBiInteractiveAppCreated} is - * called. + * This is called when {@link TvIAppService.Session#notifyBiInteractiveAppCreated} + * is called. * * @param session A {@link TvIAppManager.Session} associated with this callback. * @param biIAppUri URI associated this BI interactive app. This is the same URI in @@ -1491,5 +1704,16 @@ public final class TvIAppManager { */ public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) { } + + /** + * This is called when {@link TvIAppService.Session#notifyTeletextAppStateChanged} is + * called. + * + * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param state the current state. + */ + public void onTeletextAppStateChanged( + Session session, @TvIAppManager.TeletextAppState int state) { + } } } diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java index 04560415ed37..c0ec76b10655 100644..100755 --- a/media/java/android/media/tv/interactive/TvIAppService.java +++ b/media/java/android/media/tv/interactive/TvIAppService.java @@ -31,6 +31,8 @@ import android.media.tv.AdRequest; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; +import android.media.tv.TvContentRating; +import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; import android.net.Uri; import android.os.AsyncTask; @@ -74,45 +76,48 @@ public abstract class TvIAppService extends Service { // TODO: cleanup and unhide APIs. /** - * This is the interface name that a service implementing a TV IApp service should say that it - * supports -- that is, this is the action it uses for its intent filter. To be supported, the - * service must also require the android.Manifest.permission#BIND_TV_IAPP permission so - * that other applications cannot abuse it. + * This is the interface name that a service implementing a TV Interactive App service should + * say that it supports -- that is, this is the action it uses for its intent filter. To be + * supported, the service must also require the + * android.Manifest.permission#BIND_TV_INTERACTIVE_APP permission so that other applications + * cannot abuse it. */ - public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService"; + public static final String SERVICE_INTERFACE = + "android.media.tv.interactive.TvIAppService"; /** - * Name under which a TvIAppService component publishes information about itself. This meta-data - * must reference an XML resource containing an - * <code><{@link android.R.styleable#TvIAppService tv-iapp}></code> + * Name under which a TvIAppService component publishes information about itself. This + * meta-data must reference an XML resource containing an + * <code><{@link android.R.styleable#TvIAppService tv-interactive-app}></code> * tag. */ public static final String SERVICE_META_DATA = "android.media.tv.interactive.app"; /** @hide */ @Retention(RetentionPolicy.SOURCE) - @StringDef(prefix = "IAPP_SERVICE_COMMAND_TYPE_", value = { - IAPP_SERVICE_COMMAND_TYPE_TUNE, - IAPP_SERVICE_COMMAND_TYPE_TUNE_NEXT, - IAPP_SERVICE_COMMAND_TYPE_TUNE_PREV, - IAPP_SERVICE_COMMAND_TYPE_STOP, - IAPP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME, - IAPP_SERVICE_COMMAND_TYPE_SELECT_TRACK + @StringDef(prefix = "INTERACTIVE_APP_SERVICE_COMMAND_TYPE_", value = { + INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE, + INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_NEXT, + INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_PREV, + INTERACTIVE_APP_SERVICE_COMMAND_TYPE_STOP, + INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME, + INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SELECT_TRACK }) - public @interface IAppServiceCommandType {} + public @interface InteractiveAppServiceCommandType {} /** @hide */ - public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE = "tune"; + public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE = "tune"; /** @hide */ - public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE_NEXT = "tune_next"; + public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_NEXT = "tune_next"; /** @hide */ - public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE_PREV = "tune_previous"; + public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_TUNE_PREV = "tune_previous"; /** @hide */ - public static final String IAPP_SERVICE_COMMAND_TYPE_STOP = "stop"; + public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_STOP = "stop"; /** @hide */ - public static final String IAPP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME = "set_stream_volume"; + public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SET_STREAM_VOLUME = + "set_stream_volume"; /** @hide */ - public static final String IAPP_SERVICE_COMMAND_TYPE_SELECT_TRACK = "select_track"; + public static final String INTERACTIVE_APP_SERVICE_COMMAND_TYPE_SELECT_TRACK = "select_track"; /** @hide */ public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri"; /** @hide */ @@ -128,29 +133,29 @@ public abstract class TvIAppService extends Service { "command_track_select_mode"; private final Handler mServiceHandler = new ServiceHandler(); - private final RemoteCallbackList<ITvIAppServiceCallback> mCallbacks = + private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks = new RemoteCallbackList<>(); /** @hide */ @Override public final IBinder onBind(Intent intent) { - ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() { + ITvInteractiveAppService.Stub tvIAppServiceBinder = new ITvInteractiveAppService.Stub() { @Override - public void registerCallback(ITvIAppServiceCallback cb) { + public void registerCallback(ITvInteractiveAppServiceCallback cb) { if (cb != null) { mCallbacks.register(cb); } } @Override - public void unregisterCallback(ITvIAppServiceCallback cb) { + public void unregisterCallback(ITvInteractiveAppServiceCallback cb) { if (cb != null) { mCallbacks.unregister(cb); } } @Override - public void createSession(InputChannel channel, ITvIAppSessionCallback cb, + public void createSession(InputChannel channel, ITvInteractiveAppSessionCallback cb, String iAppServiceId, int type) { if (cb == null) { return; @@ -170,8 +175,13 @@ public abstract class TvIAppService extends Service { } @Override - public void notifyAppLinkInfo(Bundle appLinkInfo) { - onAppLinkInfo(appLinkInfo); + public void registerAppLinkInfo(Bundle appLinkInfo) { + onRegisterAppLinkInfo(appLinkInfo); + } + + @Override + public void unregisterAppLinkInfo(Bundle appLinkInfo) { + onUnregisterAppLinkInfo(appLinkInfo); } @Override @@ -183,7 +193,7 @@ public abstract class TvIAppService extends Service { } /** - * Prepares TV IApp service for the given type. + * Prepares TV Interactive App service for the given type. * @hide */ public void onPrepare(int type) { @@ -194,7 +204,15 @@ public abstract class TvIAppService extends Service { * Registers App link info. * @hide */ - public void onAppLinkInfo(Bundle appLinkInfo) { + public void onRegisterAppLinkInfo(Bundle appLinkInfo) { + // TODO: make it abstract when unhide + } + + /** + * Unregisters App link info. + * @hide + */ + public void onUnregisterAppLinkInfo(Bundle appLinkInfo) { // TODO: make it abstract when unhide } @@ -210,11 +228,11 @@ public abstract class TvIAppService extends Service { /** * Returns a concrete implementation of {@link Session}. * - * <p>May return {@code null} if this TV IApp service fails to create a session for some - * reason. + * <p>May return {@code null} if this TV Interactive App service fails to create a session for + * some reason. * - * @param iAppServiceId The ID of the TV IApp associated with the session. - * @param type The type of the TV IApp associated with the session. + * @param iAppServiceId The ID of the TV Interactive App associated with the session. + * @param type The type of the TV Interactive App associated with the session. * @hide */ @Nullable @@ -228,7 +246,8 @@ public abstract class TvIAppService extends Service { * @param state the current state * @hide */ - public final void notifyStateChanged(int type, @TvIAppManager.TvIAppRteState int state) { + public final void notifyStateChanged( + int type, @TvIAppManager.TvInteractiveAppRteState int state) { mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED, type, state).sendToTarget(); } @@ -242,7 +261,7 @@ public abstract class TvIAppService extends Service { private final Object mLock = new Object(); // @GuardedBy("mLock") - private ITvIAppSessionCallback mSessionCallback; + private ITvInteractiveAppSessionCallback mSessionCallback; // @GuardedBy("mLock") private final List<Runnable> mPendingActions = new ArrayList<>(); @@ -275,7 +294,7 @@ public abstract class TvIAppService extends Service { * <p>By default, the media view is disabled. Must be called explicitly after the * session is created to enable the media view. * - * <p>The TV IApp service can disable its media view when needed. + * <p>The TV Interactive App service can disable its media view when needed. * * @param enable {@code true} if you want to enable the media view. {@code false} * otherwise. @@ -303,14 +322,21 @@ public abstract class TvIAppService extends Service { * Starts TvIAppService session. * @hide */ - public void onStartIApp() { + public void onStartInteractiveApp() { } /** * Stops TvIAppService session. * @hide */ - public void onStopIApp() { + public void onStopInteractiveApp() { + } + + /** + * Resets TvIAppService session. + * @hide + */ + public void onResetInteractiveApp() { } /** @@ -336,6 +362,13 @@ public abstract class TvIAppService extends Service { } /** + * To toggle Digital Teletext Application if there is one in AIT app list. + * @param enable + */ + public void onSetTeletextAppEnabled(boolean enable) { + } + + /** * Receives current channel URI. * @hide */ @@ -364,11 +397,18 @@ public abstract class TvIAppService extends Service { } /** + * Receives current TV input ID. + * @hide + */ + public void onCurrentTvInputId(@Nullable String inputId) { + } + + /** * Called when the application sets the surface. * - * <p>The TV IApp service should render interactive app UI onto the given surface. When - * called with {@code null}, the IApp service should immediately free any references to the - * currently set surface and stop using it. + * <p>The TV Interactive App service should render interactive app UI onto the given + * surface. When called with {@code null}, the Interactive App service should immediately + * free any references to the currently set surface and stop using it. * * @param surface The surface to be used for interactive app UI rendering. Can be * {@code null}. @@ -393,8 +433,8 @@ public abstract class TvIAppService extends Service { * * <p>This is always called at least once when the session is created regardless of whether * the media view is enabled or not. The media view container size is the same as the - * containing {@link TvIAppView}. Note that the size of the underlying surface can be - * different if the surface was changed by calling {@link #layoutSurface}. + * containing {@link TvInteractiveAppView}. Note that the size of the underlying surface can + * be different if the surface was changed by calling {@link #layoutSurface}. * * @param width The width of the media view. * @param height The height of the media view. @@ -442,6 +482,41 @@ public abstract class TvIAppService extends Service { } /** + * Called when video is available. + * @hide + */ + public void onVideoAvailable() { + } + + /** + * Called when video is unavailable. + * @hide + */ + public void onVideoUnavailable(int reason) { + } + + /** + * Called when content is allowed. + * @hide + */ + public void onContentAllowed() { + } + + /** + * Called when content is blocked. + * @hide + */ + public void onContentBlocked(TvContentRating rating) { + } + + /** + * Called when signal strength is changed. + * @hide + */ + public void onSignalStrength(@TvInputManager.SignalStrength int strength) { + } + + /** * Called when a broadcast info response is received. * @hide */ @@ -595,7 +670,8 @@ public abstract class TvIAppService extends Service { * @param cmdType type of the specific command * @param parameters parameters of the specific command */ - public void requestCommand(@IAppServiceCommandType String cmdType, Bundle parameters) { + public void requestCommand( + @InteractiveAppServiceCommandType String cmdType, Bundle parameters) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -726,6 +802,31 @@ public abstract class TvIAppService extends Service { } /** + * Requests current TV input ID. + * + * @see android.media.tv.TvInputInfo + * @hide + */ + public void requestCurrentTvInputId() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestCurrentTvInputId"); + } + if (mSessionCallback != null) { + mSessionCallback.onRequestCurrentTvInputId(); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestCurrentTvInputId", e); + } + } + }); + } + + /** * requests an advertisement request to be processed by the related TV input. * @param request advertisement request */ @@ -748,12 +849,16 @@ public abstract class TvIAppService extends Service { }); } - void startIApp() { - onStartIApp(); + void startInteractiveApp() { + onStartInteractiveApp(); } - void stopIApp() { - onStopIApp(); + void stopInteractiveApp() { + onStopInteractiveApp(); + } + + void resetInteractiveApp() { + onResetInteractiveApp(); } void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) { @@ -764,6 +869,10 @@ public abstract class TvIAppService extends Service { onDestroyBiInteractiveApp(biIAppId); } + void setTeletextAppEnabled(boolean enable) { + onSetTeletextAppEnabled(enable); + } + void sendCurrentChannelUri(@Nullable Uri channelUri) { onCurrentChannelUri(channelUri); } @@ -780,6 +889,10 @@ public abstract class TvIAppService extends Service { onTrackInfoList(tracks); } + void sendCurrentTvInputId(@Nullable String inputId) { + onCurrentTvInputId(inputId); + } + void release() { onRelease(); if (mSurface != null) { @@ -816,6 +929,40 @@ public abstract class TvIAppService extends Service { onTracksChanged(tracks); } + void notifyVideoAvailable() { + if (DEBUG) { + Log.d(TAG, "notifyVideoAvailable"); + } + onVideoAvailable(); + } + + void notifyVideoUnavailable(int reason) { + if (DEBUG) { + Log.d(TAG, "notifyVideoAvailable (reason=" + reason + ")"); + } + onVideoUnavailable(reason); + } + + void notifyContentAllowed() { + if (DEBUG) { + Log.d(TAG, "notifyContentAllowed"); + } + notifyContentAllowed(); + } + + void notifyContentBlocked(TvContentRating rating) { + if (DEBUG) { + Log.d(TAG, "notifyContentBlocked (rating=" + rating.flattenToString() + ")"); + } + onContentBlocked(rating); + } + + void notifySignalStrength(int strength) { + if (DEBUG) { + Log.d(TAG, "notifySignalStrength (strength=" + strength + ")"); + } + onSignalStrength(strength); + } /** * Calls {@link #onBroadcastInfoResponse}. @@ -842,7 +989,8 @@ public abstract class TvIAppService extends Service { * Notifies when the session state is changed. * @param state the current state. */ - public void notifySessionStateChanged(@TvIAppManager.TvIAppRteState int state) { + public void notifySessionStateChanged( + @TvIAppManager.TvInteractiveAppRteState int state) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -889,6 +1037,30 @@ public abstract class TvIAppService extends Service { } /** + * Notifies when the digital teletext app state is changed. + * @param state the current state. + */ + public final void notifyTeletextAppStateChanged(@TvIAppManager.TeletextAppState int state) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "notifyTeletextAppState (state=" + + state + ")"); + } + if (mSessionCallback != null) { + mSessionCallback.onTeletextAppStateChanged(state); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyTeletextAppState", e); + } + } + }); + } + + /** * Takes care of dispatching incoming input events and tells whether the event was handled. */ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { @@ -921,7 +1093,7 @@ public abstract class TvIAppService extends Service { return TvIAppManager.Session.DISPATCH_NOT_HANDLED; } - private void initialize(ITvIAppSessionCallback callback) { + private void initialize(ITvInteractiveAppSessionCallback callback) { synchronized (mLock) { mSessionCallback = callback; for (Runnable runnable : mPendingActions) { @@ -1031,8 +1203,8 @@ public abstract class TvIAppService extends Service { if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")"); if (mMediaFrame == null || mMediaFrame.width() != frame.width() || mMediaFrame.height() != frame.height()) { - // Note: relayoutMediaView is called whenever TvIAppView's layout is changed - // regardless of setMediaViewEnabled. + // Note: relayoutMediaView is called whenever TvInteractiveAppView's layout is + // changed regardless of setMediaViewEnabled. onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top); } mMediaFrame = frame; @@ -1103,31 +1275,37 @@ public abstract class TvIAppService extends Service { } /** - * Implements the internal ITvIAppSession interface. + * Implements the internal ITvInteractiveAppSession interface. * @hide */ - public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub { - // TODO: put ITvIAppSessionWrapper in a separate Java file + public static class ITvInteractiveAppSessionWrapper extends ITvInteractiveAppSession.Stub { + // TODO: put ITvInteractiveAppSessionWrapper in a separate Java file private final Session mSessionImpl; private InputChannel mChannel; - private TvIAppEventReceiver mReceiver; + private TvInteractiveAppEventReceiver mReceiver; - public ITvIAppSessionWrapper(Context context, Session mSessionImpl, InputChannel channel) { + public ITvInteractiveAppSessionWrapper( + Context context, Session mSessionImpl, InputChannel channel) { this.mSessionImpl = mSessionImpl; mChannel = channel; if (channel != null) { - mReceiver = new TvIAppEventReceiver(channel, context.getMainLooper()); + mReceiver = new TvInteractiveAppEventReceiver(channel, context.getMainLooper()); } } @Override - public void startIApp() { - mSessionImpl.startIApp(); + public void startInteractiveApp() { + mSessionImpl.startInteractiveApp(); } @Override - public void stopIApp() { - mSessionImpl.stopIApp(); + public void stopInteractiveApp() { + mSessionImpl.stopInteractiveApp(); + } + + @Override + public void resetInteractiveApp() { + mSessionImpl.resetInteractiveApp(); } @Override @@ -1136,6 +1314,11 @@ public abstract class TvIAppService extends Service { } @Override + public void setTeletextAppEnabled(boolean enable) { + mSessionImpl.setTeletextAppEnabled(enable); + } + + @Override public void destroyBiInteractiveApp(@NonNull String biIAppId) { mSessionImpl.destroyBiInteractiveApp(biIAppId); } @@ -1161,6 +1344,11 @@ public abstract class TvIAppService extends Service { } @Override + public void sendCurrentTvInputId(@Nullable String inputId) { + mSessionImpl.sendCurrentTvInputId(inputId); + } + + @Override public void release() { mSessionImpl.scheduleMediaViewCleanup(); mSessionImpl.release(); @@ -1182,6 +1370,31 @@ public abstract class TvIAppService extends Service { } @Override + public void notifyVideoAvailable() { + mSessionImpl.notifyVideoAvailable(); + } + + @Override + public void notifyVideoUnavailable(int reason) { + mSessionImpl.notifyVideoUnavailable(reason); + } + + @Override + public void notifyContentAllowed() { + mSessionImpl.notifyContentAllowed(); + } + + @Override + public void notifyContentBlocked(String rating) { + mSessionImpl.notifyContentBlocked(TvContentRating.unflattenFromString(rating)); + } + + @Override + public void notifySignalStrength(int strength) { + mSessionImpl.notifySignalStrength(strength); + } + + @Override public void setSurface(Surface surface) { mSessionImpl.setSurface(surface); } @@ -1216,8 +1429,8 @@ public abstract class TvIAppService extends Service { mSessionImpl.removeMediaView(true); } - private final class TvIAppEventReceiver extends InputEventReceiver { - TvIAppEventReceiver(InputChannel inputChannel, Looper looper) { + private final class TvInteractiveAppEventReceiver extends InputEventReceiver { + TvInteractiveAppEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @@ -1231,7 +1444,8 @@ public abstract class TvIAppService extends Service { int handled = mSessionImpl.dispatchInputEvent(event, this); if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) { - finishInputEvent(event, handled == TvIAppManager.Session.DISPATCH_HANDLED); + finishInputEvent( + event, handled == TvIAppManager.Session.DISPATCH_HANDLED); } } } @@ -1261,7 +1475,8 @@ public abstract class TvIAppService extends Service { case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs) msg.obj; InputChannel channel = (InputChannel) args.arg1; - ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg2; + ITvInteractiveAppSessionCallback cb = + (ITvInteractiveAppSessionCallback) args.arg2; String iAppServiceId = (String) args.arg3; int type = (int) args.arg4; args.recycle(); @@ -1275,8 +1490,8 @@ public abstract class TvIAppService extends Service { } return; } - ITvIAppSession stub = new ITvIAppSessionWrapper( - TvIAppService.this, sessionImpl, channel); + ITvInteractiveAppSession stub = new ITvInteractiveAppSessionWrapper( + android.media.tv.interactive.TvIAppService.this, sessionImpl, channel); SomeArgs someArgs = SomeArgs.obtain(); someArgs.arg1 = sessionImpl; @@ -1289,8 +1504,9 @@ public abstract class TvIAppService extends Service { case DO_NOTIFY_SESSION_CREATED: { SomeArgs args = (SomeArgs) msg.obj; Session sessionImpl = (Session) args.arg1; - ITvIAppSession stub = (ITvIAppSession) args.arg2; - ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg3; + ITvInteractiveAppSession stub = (ITvInteractiveAppSession) args.arg2; + ITvInteractiveAppSessionCallback cb = + (ITvInteractiveAppSessionCallback) args.arg3; try { cb.onSessionCreated(stub); } catch (RemoteException e) { diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.aidl index 604146009254..5e1501677b3b 100644 --- a/media/java/android/media/tv/interactive/TvIAppInfo.aidl +++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.aidl @@ -16,4 +16,4 @@ package android.media.tv.interactive; -parcelable TvIAppInfo;
\ No newline at end of file +parcelable TvInteractiveAppInfo;
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java new file mode 100644 index 000000000000..2f96552f6f32 --- /dev/null +++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.interactive; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is used to specify meta information of a TV interactive app. + * @hide + */ +public final class TvInteractiveAppInfo implements Parcelable { + private static final boolean DEBUG = false; + private static final String TAG = "TvInteractiveAppInfo"; + + private static final String XML_START_TAG_NAME = "tv-interactive-app"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = { + INTERACTIVE_APP_TYPE_HBBTV, + INTERACTIVE_APP_TYPE_ATSC, + INTERACTIVE_APP_TYPE_GINGA, + }) + @interface InteractiveAppType {} + + /** HbbTV interactive app type */ + public static final int INTERACTIVE_APP_TYPE_HBBTV = 0x1; + /** ATSC interactive app type */ + public static final int INTERACTIVE_APP_TYPE_ATSC = 0x2; + /** Ginga interactive app type */ + public static final int INTERACTIVE_APP_TYPE_GINGA = 0x4; + + private final ResolveInfo mService; + private final String mId; + private int mTypes; + + public TvInteractiveAppInfo(@NonNull Context context, @NonNull ComponentName component) { + if (context == null) { + throw new IllegalArgumentException("context cannot be null."); + } + Intent intent = + new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component); + ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); + if (resolveInfo == null) { + throw new IllegalArgumentException("Invalid component. Can't find the service."); + } + + ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name); + String id; + id = generateInteractiveAppServiceId(componentName); + List<String> types = new ArrayList<>(); + parseServiceMetadata(resolveInfo, context, types); + + mService = resolveInfo; + mId = id; + mTypes = toTypesFlag(types); + } + private TvInteractiveAppInfo(ResolveInfo service, String id, int types) { + mService = service; + mId = id; + mTypes = types; + } + + private TvInteractiveAppInfo(@NonNull Parcel in) { + mService = ResolveInfo.CREATOR.createFromParcel(in); + mId = in.readString(); + mTypes = in.readInt(); + } + + public static final @NonNull Creator<TvInteractiveAppInfo> CREATOR = + new Creator<TvInteractiveAppInfo>() { + @Override + public TvInteractiveAppInfo createFromParcel(Parcel in) { + return new TvInteractiveAppInfo(in); + } + + @Override + public TvInteractiveAppInfo[] newArray(int size) { + return new TvInteractiveAppInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + mService.writeToParcel(dest, flags); + dest.writeString(mId); + dest.writeInt(mTypes); + } + + @NonNull + public String getId() { + return mId; + } + + /** + * Returns the component of the TV Interactive App service. + * @hide + */ + public ComponentName getComponent() { + return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); + } + + /** + * Returns the information of the service that implements this TV Interactive App service. + */ + @Nullable + public ServiceInfo getServiceInfo() { + return mService.serviceInfo; + } + + /** + * Gets supported interactive app types + */ + @InteractiveAppType + @NonNull + public int getSupportedTypes() { + return mTypes; + } + + private static String generateInteractiveAppServiceId(ComponentName name) { + return name.flattenToShortString(); + } + + private static void parseServiceMetadata( + ResolveInfo resolveInfo, Context context, List<String> types) { + ServiceInfo si = resolveInfo.serviceInfo; + PackageManager pm = context.getPackageManager(); + try (XmlResourceParser parser = + si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) { + if (parser == null) { + throw new IllegalStateException( + "No " + TvIAppService.SERVICE_META_DATA + + " meta-data found for " + si.name); + } + + Resources res = pm.getResourcesForApplication(si.applicationInfo); + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + // move to the START_TAG + } + + String nodeName = parser.getName(); + if (!XML_START_TAG_NAME.equals(nodeName)) { + throw new IllegalStateException("Meta-data does not start with " + + XML_START_TAG_NAME + " tag for " + si.name); + } + + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.TvIAppService); + CharSequence[] textArr = sa.getTextArray( + com.android.internal.R.styleable.TvIAppService_supportedTypes); + for (CharSequence cs : textArr) { + types.add(cs.toString().toLowerCase()); + } + + sa.recycle(); + } catch (IOException | XmlPullParserException e) { + throw new IllegalStateException( + "Failed reading meta-data for " + si.packageName, e); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("No resources found for " + si.packageName, e); + } + } + + private static int toTypesFlag(List<String> types) { + int flag = 0; + for (String type : types) { + switch (type) { + case "hbbtv": + flag |= INTERACTIVE_APP_TYPE_HBBTV; + break; + case "atsc": + flag |= INTERACTIVE_APP_TYPE_ATSC; + break; + case "ginga": + flag |= INTERACTIVE_APP_TYPE_GINGA; + break; + default: + break; + } + } + return flag; + } +} diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java index b29505578184..6f99d515f1e4 100644..100755 --- a/media/java/android/media/tv/interactive/TvIAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -16,6 +16,7 @@ package android.media.tv.interactive; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -45,13 +46,14 @@ import android.view.ViewGroup; import android.view.ViewRootImpl; import java.util.List; +import java.util.concurrent.Executor; /** * Displays contents of interactive TV applications. * @hide */ -public class TvIAppView extends ViewGroup { - private static final String TAG = "TvIAppView"; +public class TvInteractiveAppView extends ViewGroup { + private static final String TAG = "TvInteractiveAppView"; private static final boolean DEBUG = false; private static final int SET_TVVIEW_SUCCESS = 1; @@ -59,11 +61,13 @@ public class TvIAppView extends ViewGroup { private static final int UNSET_TVVIEW_SUCCESS = 3; private static final int UNSET_TVVIEW_FAIL = 4; - private final TvIAppManager mTvIAppManager; + private final TvIAppManager mTvInteractiveAppManager; private final Handler mHandler = new Handler(); + private final Object mCallbackLock = new Object(); private Session mSession; private MySessionCallback mSessionCallback; - private TvIAppCallback mCallback; + private TvInteractiveAppCallback mCallback; + private Executor mCallbackExecutor; private SurfaceView mSurfaceView; private Surface mSurface; @@ -114,15 +118,16 @@ public class TvIAppView extends ViewGroup { } }; - public TvIAppView(@NonNull Context context) { + public TvInteractiveAppView(@NonNull Context context) { this(context, null, 0); } - public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs) { + public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { super(context, attrs, defStyleAttr); int sourceResId = Resources.getAttributeSetSourceResId(attrs); if (sourceResId != Resources.ID_NULL) { @@ -136,31 +141,50 @@ public class TvIAppView extends ViewGroup { } mDefStyleAttr = defStyleAttr; resetSurfaceView(); - mTvIAppManager = (TvIAppManager) getContext().getSystemService(Context.TV_IAPP_SERVICE); + mTvInteractiveAppManager = (TvIAppManager) getContext().getSystemService( + Context.TV_IAPP_SERVICE); } /** - * Sets the callback to be invoked when an event is dispatched to this TvIAppView. + * Sets the callback to be invoked when an event is dispatched to this TvInteractiveAppView. * * @param callback The callback to receive events. A value of {@code null} removes the existing - * callback. + * callback. */ - public void setCallback(@Nullable TvIAppCallback callback) { - mCallback = callback; + public void setCallback( + @NonNull TvInteractiveAppCallback callback, + @NonNull @CallbackExecutor Executor executor) { + synchronized (mCallbackLock) { + mCallbackExecutor = executor; + mCallback = callback; + } + } + + /** + * Clears the callback. + */ + public void clearCallback() { + synchronized (mCallbackLock) { + mCallback = null; + mCallbackExecutor = null; + } } + /** @hide */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); createSessionMediaView(); } + /** @hide */ @Override protected void onDetachedFromWindow() { removeSessionMediaView(); super.onDetachedFromWindow(); } + /** @hide */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (DEBUG) { @@ -175,6 +199,7 @@ public class TvIAppView extends ViewGroup { } } + /** @hide */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec); @@ -186,6 +211,7 @@ public class TvIAppView extends ViewGroup { childState << MEASURED_HEIGHT_STATE_SHIFT)); } + /** @hide */ @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); @@ -216,7 +242,8 @@ public class TvIAppView extends ViewGroup { } /** - * Resets this TvIAppView. + * Resets this TvInteractiveAppView. + * @hide */ public void reset() { if (DEBUG) Log.d(TAG, "reset()"); @@ -302,6 +329,7 @@ public class TvIAppView extends ViewGroup { /** * Dispatches an unhandled input event to the next receiver. + * @hide */ public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) { if (mOnUnhandledInputEventListener != null) { @@ -314,11 +342,13 @@ public class TvIAppView extends ViewGroup { /** * Called when an unhandled input event also has not been handled by the user provided - * callback. This is the last chance to handle the unhandled input event in the TvIAppView. + * callback. This is the last chance to handle the unhandled input event in the + * TvInteractiveAppView. * * @param event The input event. * @return If you handled the event, return {@code true}. If you want to allow the event to be * handled by the next receiver, return {@code false}. + * @hide */ public boolean onUnhandledInputEvent(@NonNull InputEvent event) { return false; @@ -329,6 +359,7 @@ public class TvIAppView extends ViewGroup { * by the TV Interactive App. * * @param listener The callback to be invoked when the unhandled input event is received. + * @hide */ public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) { mOnUnhandledInputEventListener = listener; @@ -350,44 +381,60 @@ public class TvIAppView extends ViewGroup { /** * Prepares the interactive application. + * @hide */ - public void prepareIApp(@NonNull String iAppServiceId, int type) { + public void prepareInteractiveApp(@NonNull String iAppServiceId, int type) { // TODO: document and handle the cases that this method is called multiple times. if (DEBUG) { - Log.d(TAG, "prepareIApp"); + Log.d(TAG, "prepareInteractiveApp"); } mSessionCallback = new MySessionCallback(iAppServiceId, type); - if (mTvIAppManager != null) { - mTvIAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler); + if (mTvInteractiveAppManager != null) { + mTvInteractiveAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler); } } /** * Starts the interactive application. */ - public void startIApp() { + public void startInteractiveApp() { if (DEBUG) { - Log.d(TAG, "startIApp"); + Log.d(TAG, "startInteractiveApp"); } if (mSession != null) { - mSession.startIApp(); + mSession.startInteractiveApp(); } } /** * Stops the interactive application. + * @hide */ - public void stopIApp() { + public void stopInteractiveApp() { if (DEBUG) { - Log.d(TAG, "stopIApp"); + Log.d(TAG, "stopInteractiveApp"); } if (mSession != null) { - mSession.stopIApp(); + mSession.stopInteractiveApp(); + } + } + + /** + * Resets the interactive application. + * @hide + */ + public void resetInteractiveApp() { + if (DEBUG) { + Log.d(TAG, "resetInteractiveApp"); + } + if (mSession != null) { + mSession.resetInteractiveApp(); } } /** * Sends current channel URI to related TV interactive app. + * @hide */ public void sendCurrentChannelUri(Uri channelUri) { if (DEBUG) { @@ -400,6 +447,7 @@ public class TvIAppView extends ViewGroup { /** * Sends current channel logical channel number (LCN) to related TV interactive app. + * @hide */ public void sendCurrentChannelLcn(int lcn) { if (DEBUG) { @@ -412,6 +460,7 @@ public class TvIAppView extends ViewGroup { /** * Sends stream volume to related TV interactive app. + * @hide */ public void sendStreamVolume(float volume) { if (DEBUG) { @@ -424,6 +473,7 @@ public class TvIAppView extends ViewGroup { /** * Sends track info list to related TV interactive app. + * @hide */ public void sendTrackInfoList(List<TvTrackInfo> tracks) { if (DEBUG) { @@ -434,6 +484,23 @@ public class TvIAppView extends ViewGroup { } } + /** + * Sends current TV input ID to related TV interactive app. + * + * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is + * tuned. + * @see android.media.tv.TvInputInfo + * @hide + */ + public void sendCurrentTvInputId(@Nullable String inputId) { + if (DEBUG) { + Log.d(TAG, "sendCurrentTvInputId"); + } + if (mSession != null) { + mSession.sendCurrentTvInputId(inputId); + } + } + private void resetInternal() { mSessionCallback = null; if (mSession != null) { @@ -478,16 +545,18 @@ public class TvIAppView extends ViewGroup { } } - public Session getIAppSession() { + /** @hide */ + public Session getInteractiveAppSession() { return mSession; } /** - * Sets the TvIAppView to receive events from TIS. This method links the session of + * Sets the TvInteractiveAppView to receive events from TIS. This method links the session of * TvIAppManager to TvInputManager session, so the TIAS can get the TIS events. * - * @param tvView the TvView to be linked to this TvIAppView via linking of Sessions. + * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions. * @return to be added + * @hide */ public int setTvView(@Nullable TvView tvView) { if (tvView == null) { @@ -498,7 +567,7 @@ public class TvIAppView extends ViewGroup { return SET_TVVIEW_FAIL; } mSession.setInputSession(inputSession); - inputSession.setIAppSession(mSession); + inputSession.setInteractiveAppSession(mSession); return SET_TVVIEW_SUCCESS; } @@ -506,15 +575,29 @@ public class TvIAppView extends ViewGroup { if (mSession == null || mSession.getInputSession() == null) { return UNSET_TVVIEW_FAIL; } - mSession.getInputSession().setIAppSession(null); + mSession.getInputSession().setInteractiveAppSession(null); mSession.setInputSession(null); return UNSET_TVVIEW_SUCCESS; } /** - * Callback used to receive various status updates on the {@link TvIAppView}. + * To toggle Digital Teletext Application if there is one in AIT app list. + * @param enable */ - public abstract static class TvIAppCallback { + public void setTeletextAppEnabled(boolean enable) { + if (DEBUG) { + Log.d(TAG, "setTeletextAppEnabled enable=" + enable); + } + if (mSession != null) { + mSession.setTeletextAppEnabled(enable); + } + } + + /** + * Callback used to receive various status updates on the {@link TvInteractiveAppView}. + */ + public abstract static class TvInteractiveAppCallback { + // TODO: unhide the following public APIs /** * This is called when a command is requested to be processed by the related TV input. @@ -522,10 +605,11 @@ public class TvIAppView extends ViewGroup { * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param cmdType type of the command * @param parameters parameters of the command + * @hide */ public void onCommandRequest( @NonNull String iAppServiceId, - @NonNull @TvIAppService.IAppServiceCommandType String cmdType, + @NonNull @TvIAppService.InteractiveAppServiceCommandType String cmdType, @Nullable Bundle parameters) { } @@ -534,6 +618,7 @@ public class TvIAppView extends ViewGroup { * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param state current session state. + * @hide */ public void onSessionStateChanged(@NonNull String iAppServiceId, int state) { } @@ -546,55 +631,84 @@ public class TvIAppView extends ViewGroup { * {@link Session#createBiInteractiveApp(Uri, Bundle)} * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive * app. + * @hide */ public void onBiInteractiveAppCreated(@NonNull String iAppServiceId, @NonNull Uri biIAppUri, @Nullable String biIAppId) { } /** + * This is called when the digital teletext app state is changed. + * + * @param iAppServiceId The ID of the TV interactive app service bound to this view. + * @param state digital teletext app current state. + */ + public void onTeletextAppStateChanged( + @NonNull String iAppServiceId, @TvIAppManager.TeletextAppState int state) { + } + + /** * This is called when {@link TvIAppService.Session#SetVideoBounds} is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. + * @hide */ public void onSetVideoBounds(@NonNull String iAppServiceId, @NonNull Rect rect) { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is called. + * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is + * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. + * @hide */ public void onRequestCurrentChannelUri(@NonNull String iAppServiceId) { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is called. + * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is + * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. + * @hide */ public void onRequestCurrentChannelLcn(@NonNull String iAppServiceId) { } /** - * This is called when {@link TvIAppService.Session#RequestStreamVolume} is called. + * This is called when {@link TvIAppService.Session#RequestStreamVolume} is + * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. + * @hide */ public void onRequestStreamVolume(@NonNull String iAppServiceId) { } /** - * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is called. + * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is + * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. + * @hide */ public void onRequestTrackInfoList(@NonNull String iAppServiceId) { } + /** + * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called. + * + * @param iAppServiceId The ID of the TV interactive app service bound to this view. + */ + public void onRequestCurrentTvInputId(@NonNull String iAppServiceId) { + } + } /** * Interface definition for a callback to be invoked when the unhandled input event is received. + * @hide */ public interface OnUnhandledInputEventListener { /** @@ -685,8 +799,10 @@ public class TvIAppView extends ViewGroup { } @Override - public void onCommandRequest(Session session, - @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) { + public void onCommandRequest( + Session session, + @TvIAppService.InteractiveAppServiceCommandType String cmdType, + Bundle parameters) { if (DEBUG) { Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters=" + parameters.toString() + ")"); @@ -695,8 +811,16 @@ public class TvIAppView extends ViewGroup { Log.w(TAG, "onCommandRequest - session not created"); return; } - if (mCallback != null) { - mCallback.onCommandRequest(mIAppServiceId, cmdType, parameters); + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onCommandRequest(mIAppServiceId, cmdType, parameters); + } + } + }); + } } } @@ -709,8 +833,16 @@ public class TvIAppView extends ViewGroup { Log.w(TAG, "onSessionStateChanged - session not created"); return; } - if (mCallback != null) { - mCallback.onSessionStateChanged(mIAppServiceId, state); + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onSessionStateChanged(mIAppServiceId, state); + } + } + }); + } } } @@ -724,8 +856,31 @@ public class TvIAppView extends ViewGroup { Log.w(TAG, "onBiInteractiveAppCreated - session not created"); return; } + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onBiInteractiveAppCreated( + mIAppServiceId, biIAppUri, biIAppId); + } + } + }); + } + } + } + + @Override + public void onTeletextAppStateChanged(Session session, int state) { + if (DEBUG) { + Log.d(TAG, "onTeletextAppStateChanged (state=" + state + ")"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onTeletextAppStateChanged - session not created"); + return; + } if (mCallback != null) { - mCallback.onBiInteractiveAppCreated(mIAppServiceId, biIAppUri, biIAppId); + mCallback.onTeletextAppStateChanged(mIAppServiceId, state); } } @@ -738,8 +893,16 @@ public class TvIAppView extends ViewGroup { Log.w(TAG, "onSetVideoBounds - session not created"); return; } - if (mCallback != null) { - mCallback.onSetVideoBounds(mIAppServiceId, rect); + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onSetVideoBounds(mIAppServiceId, rect); + } + } + }); + } } } @@ -752,8 +915,16 @@ public class TvIAppView extends ViewGroup { Log.w(TAG, "onRequestCurrentChannelUri - session not created"); return; } - if (mCallback != null) { - mCallback.onRequestCurrentChannelUri(mIAppServiceId); + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestCurrentChannelUri(mIAppServiceId); + } + } + }); + } } } @@ -766,8 +937,16 @@ public class TvIAppView extends ViewGroup { Log.w(TAG, "onRequestCurrentChannelLcn - session not created"); return; } - if (mCallback != null) { - mCallback.onRequestCurrentChannelLcn(mIAppServiceId); + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestCurrentChannelLcn(mIAppServiceId); + } + } + }); + } } } @@ -780,8 +959,16 @@ public class TvIAppView extends ViewGroup { Log.w(TAG, "onRequestStreamVolume - session not created"); return; } - if (mCallback != null) { - mCallback.onRequestStreamVolume(mIAppServiceId); + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestStreamVolume(mIAppServiceId); + } + } + }); + } } } @@ -794,8 +981,30 @@ public class TvIAppView extends ViewGroup { Log.w(TAG, "onRequestTrackInfoList - session not created"); return; } + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestTrackInfoList(mIAppServiceId); + } + } + }); + } + } + } + + @Override + public void onRequestCurrentTvInputId(Session session) { + if (DEBUG) { + Log.d(TAG, "onRequestCurrentTvInputId"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRequestCurrentTvInputId - session not created"); + return; + } if (mCallback != null) { - mCallback.onRequestTrackInfoList(mIAppServiceId); + mCallback.onRequestCurrentTvInputId(mIAppServiceId); } } } diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java index 6a6a22c1fc0e..50a208344c3a 100644 --- a/media/java/android/media/tv/tuner/Lnb.java +++ b/media/java/android/media/tv/tuner/Lnb.java @@ -28,6 +28,9 @@ import android.media.tv.tuner.Tuner.Result; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -145,9 +148,9 @@ public class Lnb implements AutoCloseable { private static final String TAG = "Lnb"; - LnbCallback mCallback; - Executor mExecutor; - Tuner mTuner; + Map<LnbCallback, Executor> mCallbackMap = + new HashMap<LnbCallback, Executor>(); + Tuner mOwner; private final Object mCallbackLock = new Object(); @@ -164,38 +167,82 @@ public class Lnb implements AutoCloseable { private Lnb() {} - void setCallback(Executor executor, @Nullable LnbCallback callback, Tuner tuner) { + void setCallbackAndOwner(Executor executor, @Nullable LnbCallback callback, Tuner tuner) { synchronized (mCallbackLock) { - mCallback = callback; - mExecutor = executor; - mTuner = tuner; + if (callback != null && executor != null) { + addCallback(callback, executor); + } + } + setOwner(tuner); + } + + /** + * Adds LnbCallback + * + * @param callback the callback to receive notifications from LNB. + * @param executor the executor on which callback will be invoked. Cannot be null. + */ + public void addCallback(@NonNull LnbCallback callback, @NonNull Executor executor) { + Objects.requireNonNull(callback, "callback must not be null"); + Objects.requireNonNull(executor, "executor must not be null"); + synchronized (mCallbackLock) { + mCallbackMap.put(callback, executor); + } + } + + /** + * Removes LnbCallback + * + * @param callback the callback be removed for callback + * + * @return {@code true} when successful. {@code false} otherwise. + */ + public boolean removeCallback(@NonNull LnbCallback callback) { + Objects.requireNonNull(callback, "callback must not be null"); + synchronized (mCallbackLock) { + boolean result = (mCallbackMap.remove(callback) != null); + return result; + } + } + + // allow owner transfer independent of whether callback is registered or not + /* package */ void setOwner(@NonNull Tuner newOwner) { + Objects.requireNonNull(newOwner, "newOwner must not be null"); + synchronized (mLock) { + mOwner = newOwner; } } private void onEvent(int eventType) { synchronized (mCallbackLock) { - if (mExecutor != null && mCallback != null) { - mExecutor.execute(() -> { - synchronized (mCallbackLock) { - if (mCallback != null) { - mCallback.onEvent(eventType); + for (LnbCallback callback : mCallbackMap.keySet()) { + Executor executor = mCallbackMap.get(callback); + if (callback != null && executor != null) { + executor.execute(() -> { + synchronized (mCallbackLock) { + if (callback != null) { + callback.onEvent(eventType); + } } - } - }); + }); + } } } } private void onDiseqcMessage(byte[] diseqcMessage) { synchronized (mCallbackLock) { - if (mExecutor != null && mCallback != null) { - mExecutor.execute(() -> { - synchronized (mCallbackLock) { - if (mCallback != null) { - mCallback.onDiseqcMessage(diseqcMessage); + for (LnbCallback callback : mCallbackMap.keySet()) { + Executor executor = mCallbackMap.get(callback); + if (callback != null && executor != null) { + executor.execute(() -> { + synchronized (mCallbackLock) { + if (callback != null) { + callback.onDiseqcMessage(diseqcMessage); + } } - } - }); + }); + } } } } @@ -279,7 +326,11 @@ public class Lnb implements AutoCloseable { TunerUtils.throwExceptionForResult(res, "Failed to close LNB"); } else { mIsClosed = true; - mTuner.releaseLnb(); + if (mOwner != null) { + mOwner.releaseLnb(); + mOwner = null; + } + mCallbackMap.clear(); } } } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 4128abf03e07..9c4a83a235c0 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -241,7 +241,7 @@ public class Tuner implements AutoCloseable { private static final String TAG = "MediaTvTuner"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int MSG_RESOURCE_LOST = 1; private static final int MSG_ON_FILTER_EVENT = 2; @@ -250,7 +250,6 @@ public class Tuner implements AutoCloseable { private static final int FILTER_CLEANUP_THRESHOLD = 256; - /** @hide */ @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK}) @Retention(RetentionPolicy.SOURCE) @@ -455,6 +454,260 @@ public class Tuner implements AutoCloseable { } /** + * Transfers the ownership of shared frontend and its associated resources. + * + * @param newOwner the Tuner instance to be the new owner. + * + * @return result status of tune operation. + */ + public int transferOwner(@NonNull Tuner newOwner) { + acquireTRMSLock("transferOwner()"); + mFrontendLock.lock(); + mFrontendCiCamLock.lock(); + mLnbLock.lock(); + try { + + if (!isFrontendOwner() || !isNewOwnerQualifiedForTransfer(newOwner)) { + return RESULT_INVALID_STATE; + } + + int res = transferFeOwner(newOwner); + if (res != RESULT_SUCCESS) { + return res; + } + + res = transferCiCamOwner(newOwner); + if (res != RESULT_SUCCESS) { + return res; + } + + res = transferLnbOwner(newOwner); + if (res != RESULT_SUCCESS) { + return res; + } + } finally { + mFrontendLock.unlock(); + mFrontendCiCamLock.unlock(); + mLnbLock.unlock(); + releaseTRMSLock(); + } + return RESULT_SUCCESS; + } + + /** + * Resets or copies Frontend related settings. + */ + private void replicateFrontendSettings(@Nullable Tuner src) { + mFrontendLock.lock(); + try { + if (src == null) { + if (DEBUG) { + Log.d(TAG, "resetting Frontend params for " + mClientId); + } + mFrontend = null; + mFrontendHandle = null; + mFrontendInfo = null; + mFrontendType = FrontendSettings.TYPE_UNDEFINED; + } else { + if (DEBUG) { + Log.d(TAG, "copying Frontend params from " + src.mClientId + + " to " + mClientId); + } + mFrontend = src.mFrontend; + mFrontendHandle = src.mFrontendHandle; + mFrontendInfo = src.mFrontendInfo; + mFrontendType = src.mFrontendType; + } + } finally { + mFrontendLock.unlock(); + } + } + + /** + * Sets the frontend owner. mFeOwnerTuner should be null for the owner Tuner instance. + */ + private void setFrontendOwner(Tuner owner) { + mFrontendLock.lock(); + try { + mFeOwnerTuner = owner; + } finally { + mFrontendLock.unlock(); + } + } + + /** + * Resets or copies the CiCam related settings. + */ + private void replicateCiCamSettings(@Nullable Tuner src) { + mFrontendCiCamLock.lock(); + try { + if (src == null) { + if (DEBUG) { + Log.d(TAG, "resetting CiCamParams: " + mClientId); + } + mFrontendCiCamHandle = null; + mFrontendCiCamId = null; + } else { + if (DEBUG) { + Log.d(TAG, "copying CiCamParams from " + src.mClientId + " to " + mClientId); + Log.d(TAG, "mFrontendCiCamHandle:" + src.mFrontendCiCamHandle + ", " + + "mFrontendCiCamId:" + src.mFrontendCiCamId); + } + mFrontendCiCamHandle = src.mFrontendCiCamHandle; + mFrontendCiCamId = src.mFrontendCiCamId; + } + } finally { + mFrontendCiCamLock.unlock(); + } + } + + /** + * Resets or copies Lnb related settings. + */ + private void replicateLnbSettings(@Nullable Tuner src) { + mLnbLock.lock(); + try { + if (src == null) { + if (DEBUG) { + Log.d(TAG, "resetting Lnb params"); + } + mLnb = null; + mLnbHandle = null; + } else { + if (DEBUG) { + Log.d(TAG, "copying Lnb params from " + src.mClientId + " to " + mClientId); + } + mLnb = src.mLnb; + mLnbHandle = src.mLnbHandle; + } + } finally { + mLnbLock.unlock(); + } + } + + /** + * Checks if it is a frontend resource owner. + * Proper mutex must be held prior to calling this. + */ + private boolean isFrontendOwner() { + boolean notAnOwner = (mFeOwnerTuner != null); + if (notAnOwner) { + Log.e(TAG, "transferOwner() - cannot be called on the non-owner"); + return false; + } + return true; + } + + /** + * Checks if the newOwner is qualified. + * Proper mutex must be held prior to calling this. + */ + private boolean isNewOwnerQualifiedForTransfer(@NonNull Tuner newOwner) { + // new owner must be the current sharee + boolean newOwnerIsTheCurrentSharee = (newOwner.mFeOwnerTuner == this) + && (newOwner.mFrontendHandle.equals(mFrontendHandle)); + if (!newOwnerIsTheCurrentSharee) { + Log.e(TAG, "transferOwner() - new owner must be the current sharee"); + return false; + } + + // new owner must not be holding any of the to-be-shared resources + boolean newOwnerAlreadyHoldsToBeSharedResource = + (newOwner.mFrontendCiCamHandle != null || newOwner.mLnb != null); + if (newOwnerAlreadyHoldsToBeSharedResource) { + Log.e(TAG, "transferOwner() - new owner cannot be holding CiCam" + + " nor Lnb resource"); + return false; + } + + return true; + } + + /** + * Transfers the ownership of the already held frontend resource. + * Proper mutex must be held prior to calling this. + */ + private int transferFeOwner(@NonNull Tuner newOwner) { + // handle native resource first + newOwner.nativeUpdateFrontend(getNativeContext()); + nativeUpdateFrontend(0); + + // transfer frontend related settings + newOwner.replicateFrontendSettings(this); + + // transfer the frontend owner info + setFrontendOwner(newOwner); + newOwner.setFrontendOwner(null); + + // handle TRM + if (mTunerResourceManager.transferOwner( + TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, + mClientId, newOwner.mClientId)) { + return RESULT_SUCCESS; + } else { + return RESULT_UNKNOWN_ERROR; + } + } + + /** + * Transfers the ownership of CiCam resource. + * This is a no-op if the CiCam resource is not held. + * Proper mutex must be held prior to calling this. + */ + private int transferCiCamOwner(Tuner newOwner) { + boolean notAnOwner = (mFrontendCiCamHandle == null); + if (notAnOwner) { + // There is nothing to do here if there is no CiCam + return RESULT_SUCCESS; + } + + // no need to handle at native level + + // transfer the CiCam info at Tuner level + newOwner.replicateCiCamSettings(this); + replicateCiCamSettings(null); + + // handle TRM + if (mTunerResourceManager.transferOwner( + TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, + mClientId, newOwner.mClientId)) { + return RESULT_SUCCESS; + } else { + return RESULT_UNKNOWN_ERROR; + } + } + + /** + * Transfers the ownership of Lnb resource. + * This is a no-op if the Lnb resource is not held. + * Proper mutex must be held prior to calling this. + */ + private int transferLnbOwner(Tuner newOwner) { + boolean notAnOwner = (mLnb == null); + if (notAnOwner) { + // There is nothing to do here if there is no Lnb + return RESULT_SUCCESS; + } + + // no need to handle at native level + + // set the new owner + mLnb.setOwner(newOwner); + + newOwner.replicateLnbSettings(this); + replicateLnbSettings(null); + + // handle TRM + if (mTunerResourceManager.transferOwner( + TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, + mClientId, newOwner.mClientId)) { + return RESULT_SUCCESS; + } else { + return RESULT_UNKNOWN_ERROR; + } + } + + /** * Updates client priority with an arbitrary value along with a nice value. * * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able @@ -547,59 +800,114 @@ public class Tuner implements AutoCloseable { } } + /** + * Either unshares the frontend resource (for sharee) or release Frontend (for owner) + */ + public void closeFrontend() { + acquireTRMSLock("closeFrontend()"); + try { + releaseFrontend(); + } finally { + releaseTRMSLock(); + } + } + + /** + * Releases frontend resource for the owner. Unshares frontend resource for the sharee. + */ private void releaseFrontend() { + if (DEBUG) { + Log.d(TAG, "Tuner#releaseFrontend"); + } mFrontendLock.lock(); try { if (mFrontendHandle != null) { + if (DEBUG) { + Log.d(TAG, "mFrontendHandle not null"); + } if (mFeOwnerTuner != null) { + if (DEBUG) { + Log.d(TAG, "mFeOwnerTuner not null - sharee"); + } // unregister self from the Frontend callback mFeOwnerTuner.unregisterFrontendCallbackListener(this); mFeOwnerTuner = null; + nativeUnshareFrontend(); } else { + if (DEBUG) { + Log.d(TAG, "mFeOwnerTuner null - owner"); + } // close resource as owner int res = nativeCloseFrontend(mFrontendHandle); if (res != Tuner.RESULT_SUCCESS) { TunerUtils.throwExceptionForResult(res, "failed to close frontend"); } - mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); } + if (DEBUG) { + Log.d(TAG, "call TRM#releaseFrontend :" + mFrontendHandle + ", " + mClientId); + } + mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); FrameworkStatsLog .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); - mFrontendHandle = null; - mFrontend = null; + replicateFrontendSettings(null); } } finally { mFrontendLock.unlock(); } } + /** + * Releases CiCam resource if held. No-op otherwise. + */ + private void releaseCiCam() { + mFrontendCiCamLock.lock(); + try { + if (mFrontendCiCamHandle != null) { + if (DEBUG) { + Log.d(TAG, "unlinking CiCam : " + mFrontendCiCamHandle + " for " + mClientId); + } + int result = nativeUnlinkCiCam(mFrontendCiCamId); + if (result == RESULT_SUCCESS) { + mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); + replicateCiCamSettings(null); + } else { + Log.e(TAG, "nativeUnlinkCiCam(" + mFrontendCiCamHandle + ") for mClientId:" + + mClientId + "failed with result:" + result); + } + } else { + if (DEBUG) { + Log.d(TAG, "NOT unlinking CiCam : " + mClientId); + } + } + } finally { + mFrontendCiCamLock.unlock(); + } + } + private void releaseAll() { + // release CiCam before frontend because frontend handle is needed to unlink CiCam + releaseCiCam(); + releaseFrontend(); mLnbLock.lock(); try { // mLnb will be non-null only for owner tuner if (mLnb != null) { + if (DEBUG) { + Log.d(TAG, "calling mLnb.close() : " + mClientId); + } mLnb.close(); + } else { + if (DEBUG) { + Log.d(TAG, "NOT calling mLnb.close() : " + mClientId); + } } } finally { mLnbLock.unlock(); } - mFrontendCiCamLock.lock(); - try { - if (mFrontendCiCamHandle != null) { - int result = nativeUnlinkCiCam(mFrontendCiCamId); - if (result == RESULT_SUCCESS) { - mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); - mFrontendCiCamId = null; - mFrontendCiCamHandle = null; - } - } - } finally { - mFrontendCiCamLock.unlock(); - } synchronized (mDescramblers) { if (!mDescramblers.isEmpty()) { @@ -669,8 +977,11 @@ public class Tuner implements AutoCloseable { */ private native Frontend nativeOpenFrontendByHandle(int handle); private native int nativeShareFrontend(int id); + private native int nativeUnshareFrontend(); private native void nativeRegisterFeCbListener(long nativeContext); private native void nativeUnregisterFeCbListener(long nativeContext); + // nativeUpdateFrontend must be called on the new owner first + private native void nativeUpdateFrontend(long nativeContext); @Result private native int nativeTune(int type, FrontendSettings settings); private native int nativeStopTune(); @@ -997,6 +1308,21 @@ public class Tuner implements AutoCloseable { mFrontendHandle = feHandle[0]; mFrontend = nativeOpenFrontendByHandle(mFrontendHandle); } + + // For satellite type, set Lnb if valid handle exists. + // This is necessary as now that we support closeFrontend(). + if (mFrontendType == FrontendSettings.TYPE_DVBS + || mFrontendType == FrontendSettings.TYPE_ISDBS + || mFrontendType == FrontendSettings.TYPE_ISDBS3) { + mLnbLock.lock(); + try { + if (mLnbHandle != null && mLnb != null) { + nativeSetLnb(mLnb); + } + } finally { + mLnbLock.unlock(); + } + } return granted; } @@ -1756,12 +2082,12 @@ public class Tuner implements AutoCloseable { Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(cb, "LnbCallback must not be null"); if (mLnb != null) { - mLnb.setCallback(executor, cb, this); + mLnb.setCallbackAndOwner(executor, cb, this); return mLnb; } if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock) && mLnb != null) { - mLnb.setCallback(executor, cb, this); + mLnb.setCallbackAndOwner(executor, cb, this); setLnb(mLnb); return mLnb; } @@ -1795,7 +2121,7 @@ public class Tuner implements AutoCloseable { mLnbHandle = null; } mLnb = newLnb; - mLnb.setCallback(executor, cb, this); + mLnb.setCallbackAndOwner(executor, cb, this); setLnb(mLnb); } return mLnb; @@ -2081,8 +2407,15 @@ public class Tuner implements AutoCloseable { try { if (mLnbHandle != null) { // LNB handle can be null if it's opened by name. + if (DEBUG) { + Log.d(TAG, "releasing Lnb"); + } mTunerResourceManager.releaseLnb(mLnbHandle, mClientId); mLnbHandle = null; + } else { + if (DEBUG) { + Log.d(TAG, "NOT releasing Lnb because mLnbHandle is null"); + } } mLnb = null; } finally { diff --git a/media/java/android/media/tv/tunerresourcemanager/OWNER b/media/java/android/media/tv/tunerresourcemanager/OWNER index 76b84d98046a..0eb1c311695c 100644 --- a/media/java/android/media/tv/tunerresourcemanager/OWNER +++ b/media/java/android/media/tv/tunerresourcemanager/OWNER @@ -1,4 +1,3 @@ -amyjojo@google.com -nchalko@google.com quxiangfang@google.com -shubang@google.com
\ No newline at end of file +shubang@google.com +kemiyagi@google.com
\ No newline at end of file diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index fe611c711273..5ada89e9dea7 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -21,9 +21,12 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; +import android.annotation.SuppressLint; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; +import android.media.tv.TvInputService; import android.os.Binder; import android.os.RemoteException; import android.util.Log; @@ -415,6 +418,25 @@ public class TunerResourceManager { } /** + * Transfers the ownership of shared resource. + * + * <p><strong>Note:</strong> Only the existing frontend sharee can be the new owner. + * + * @param resourceType the type of the resource to transfer the ownership for. + * @param currentOwnerId the id of the current owner client. + * @param newOwnerId the id of the new owner client. + * + * @return true if successful and false otherwise. + */ + public boolean transferOwner(int resourceType, int currentOwnerId, int newOwnerId) { + try { + return mService.transferOwner(resourceType, currentOwnerId, newOwnerId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Requests a Tuner Demux resource. * * <p>There are three possible scenarios: @@ -702,6 +724,49 @@ public class TunerResourceManager { } /** + * Returns a priority for the given use case type and the client's foreground or background + * status. + * + * @param useCase the use case type of the client. When the given use case type is invalid, + * the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}. + * @param pid the pid of the client. When the pid is invalid, background status will be used as + * a client's status. Otherwise, client's app corresponding to the given session id will + * be used as a client. {@see TvInputService#onCreateSession(String, String)}. + * + * @return the client priority.. + */ + public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase, int pid) { + try { + return mService.getClientPriority(useCase, pid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a config priority for the given use case type and the foreground or background + * status. + * + * @param useCase the use case type of the client. When the given use case type is invalid, + * the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}. + * @param isForeground {@code true} if foreground, {@code false} otherwise. + * + * @return the config priority. + * + * @hide + */ + @TestApi + @SuppressLint("ShowingMemberInHiddenClass") + public int getConfigPriority(@TvInputService.PriorityHintUseCaseType int useCase, + boolean isForeground) { + try { + return mService.getConfigPriority(useCase, isForeground); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Interface used to receive events from TunerResourceManager. */ public abstract static class ResourcesReclaimListener { diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl index 5f3582046d15..d16fc6ca1dc7 100644 --- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl @@ -177,6 +177,19 @@ interface ITunerResourceManager { void shareFrontend(in int selfClientId, in int targetClientId); /* + * Transfers the ownership of the shared resource. + * + * <p><strong>Note:</strong> Only the existing frontend sharee can be the new owner. + * + * @param resourceType the type of resource to transfer the ownership for. + * @param currentOwnerId the id of the current owner client. + * @param newOwnerId the id of the new owner client. + * + * @return true if successful. false otherwise. + */ + boolean transferOwner(in int resourceType, in int currentOwnerId, in int newOwnerId); + + /* * This API is used by the Tuner framework to request an available demux from the TunerHAL. * * <p>There are three possible scenarios: @@ -442,4 +455,30 @@ interface ITunerResourceManager { * guaranteed to work and may be unrecoverrable. (This should not happen.) */ boolean releaseLock(in int clientId); + + /** + * Returns a priority for the given use case type and the client's foreground or background + * status. + * + * @param useCase the use case type of the client. When the given use case type is invalid, + * the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}. + * @param pid the pid of the client. When the pid is invalid, background status will be used as + * a client's status. Otherwise, client's app corresponding to the given session id will + * be used as a client. {@see TvInputService#onCreateSession(String, String)}. + * + * @return the client priority.. + */ + int getClientPriority(int useCase, int pid); + + /** + * Returns a config priority for the given use case type and the foreground or background + * status. + * + * @param useCase the use case type of the client. When the given use case type is invalid, + * the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}. + * @param isForeground {@code true} if foreground, {@code false} otherwise. + * + * @return the config priority. + */ + int getConfigPriority(int useCase, boolean isForeground); } diff --git a/media/jni/Android.bp b/media/jni/Android.bp index e817f2dc9e1d..feae6065c680 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -39,6 +39,7 @@ cc_library_shared { "android_media_MediaProfiles.cpp", "android_media_MediaRecorder.cpp", "android_media_MediaSync.cpp", + "android_media_PublicFormatUtils.cpp", "android_media_ResampleInputStream.cpp", "android_media_Streams.cpp", "android_media_SyncParams.cpp", diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index 021507c8631c..6002e2884db8 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -375,18 +375,13 @@ static void ImageReader_classInit(JNIEnv* env, jclass clazz) } static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint width, jint height, - jint format, jint maxImages, jlong ndkUsage) -{ + jint maxImages, jlong ndkUsage, jint nativeFormat, jlong dataSpace) { status_t res; - int nativeFormat; - android_dataspace nativeDataspace; - ALOGV("%s: width:%d, height: %d, format: 0x%x, maxImages:%d", - __FUNCTION__, width, height, format, maxImages); + ALOGV("%s: width:%d, height: %d, nativeFormat: %d, maxImages:%d", + __FUNCTION__, width, height, nativeFormat, maxImages); - PublicFormat publicFormat = static_cast<PublicFormat>(format); - nativeFormat = mapPublicFormatToHalFormat(publicFormat); - nativeDataspace = mapPublicFormatToHalDataspace(publicFormat); + android_dataspace nativeDataspace = static_cast<android_dataspace>(dataSpace); jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { @@ -400,7 +395,7 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint w BufferQueue::createBufferQueue(&gbProducer, &gbConsumer); sp<BufferItemConsumer> bufferConsumer; String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d", - width, height, format, maxImages, getpid(), + width, height, nativeFormat, maxImages, getpid(), createProcessUniqueId()); uint64_t consumerUsage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage); @@ -527,7 +522,8 @@ static void ImageReader_imageRelease(JNIEnv* env, jobject thiz, jobject image) ALOGV("%s: Image (format: 0x%x) has been released", __FUNCTION__, ctx->getBufferFormat()); } -static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image) { +static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image, + jboolean legacyValidateImageFormat) { ALOGV("%s:", __FUNCTION__); JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz); if (ctx == NULL) { @@ -590,7 +586,7 @@ static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image) { ALOGV("%s: Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d", __FUNCTION__, outputWidth, outputHeight, imageReaderWidth, imageReaderHeight); } - if (imgReaderFmt != bufferFormat) { + if (legacyValidateImageFormat && imgReaderFmt != bufferFormat) { if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 && isPossiblyYUV(bufferFormat)) { // Treat formats that are compatible with flexible YUV @@ -958,10 +954,10 @@ static jobject Image_getHardwareBuffer(JNIEnv* env, jobject thiz) { static const JNINativeMethod gImageReaderMethods[] = { {"nativeClassInit", "()V", (void*)ImageReader_classInit }, - {"nativeInit", "(Ljava/lang/Object;IIIIJ)V", (void*)ImageReader_init }, + {"nativeInit", "(Ljava/lang/Object;IIIJIJ)V", (void*)ImageReader_init }, {"nativeClose", "()V", (void*)ImageReader_close }, {"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease }, - {"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup }, + {"nativeImageSetup", "(Landroid/media/Image;Z)I", (void*)ImageReader_imageSetup }, {"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface }, {"nativeDetachImage", "(Landroid/media/Image;)I", (void*)ImageReader_detachImage }, {"nativeCreateImagePlanes", diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 8dcdc989ec8f..a548a472fc3a 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1454,6 +1454,7 @@ extern int register_android_media_MediaMetadataRetriever(JNIEnv *env); extern int register_android_media_MediaMuxer(JNIEnv *env); extern int register_android_media_MediaRecorder(JNIEnv *env); extern int register_android_media_MediaSync(JNIEnv *env); +extern int register_android_media_PublicFormatUtils(JNIEnv *env); extern int register_android_media_ResampleInputStream(JNIEnv *env); extern int register_android_media_MediaProfiles(JNIEnv *env); extern int register_android_mtp_MtpDatabase(JNIEnv *env); @@ -1501,6 +1502,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) goto bail; } + if (register_android_media_PublicFormatUtils(env) < 0) { + ALOGE("ERROR: PublicFormatUtils native registration failed\n"); + goto bail; + } + if (register_android_media_ResampleInputStream(env) < 0) { ALOGE("ERROR: ResampleInputStream native registration failed\n"); goto bail; diff --git a/media/jni/android_media_PublicFormatUtils.cpp b/media/jni/android_media_PublicFormatUtils.cpp new file mode 100644 index 000000000000..09ebdeeff06f --- /dev/null +++ b/media/jni/android_media_PublicFormatUtils.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "PublicFormatUtils_JNI" + +#include <utils/misc.h> +#include <ui/PublicFormat.h> +#include <android_runtime/AndroidRuntime.h> +#include <jni.h> + +using namespace android; + +static jint android_media_PublicFormatUtils_getHalFormat(JNIEnv* /*env*/, jobject /*thiz*/, + jint imageFormat) { + PublicFormat publicFormat = static_cast<PublicFormat>(imageFormat); + int nativeFormat = mapPublicFormatToHalFormat(publicFormat); + return static_cast<jint>(nativeFormat); +} + +static jlong android_media_PublicFormatUtils_getHalDataspace(JNIEnv* /*env*/, jobject /*thiz*/, + jint imageFormat) { + PublicFormat publicFormat = static_cast<PublicFormat>(imageFormat); + android_dataspace + nativeDataspace = mapPublicFormatToHalDataspace(publicFormat); + return static_cast<jlong>(nativeDataspace); +} + +static jint android_media_PublicFormatUtils_getPublicFormat(JNIEnv* /*env*/, jobject /*thiz*/, + jint hardwareBufferFormat, + jlong dataspace) { + PublicFormat nativeFormat = mapHalFormatDataspaceToPublicFormat( + hardwareBufferFormat, static_cast<android_dataspace>(dataspace)); + return static_cast<jint>(nativeFormat); +} + +static const JNINativeMethod gMethods[] = { + {"nativeGetHalFormat", "(I)I", (void*)android_media_PublicFormatUtils_getHalFormat}, + {"nativeGetHalDataspace", "(I)J", (void*)android_media_PublicFormatUtils_getHalDataspace}, + {"nativeGetPublicFormat", "(IJ)I",(void*)android_media_PublicFormatUtils_getPublicFormat} +}; + +int register_android_media_PublicFormatUtils(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/PublicFormatUtils", gMethods, NELEM(gMethods)); +} + diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index d9feaf4013a7..1b41494814b7 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -978,7 +978,8 @@ FrontendClientCallbackImpl::FrontendClientCallbackImpl(JTuner* jtuner, jweak lis void FrontendClientCallbackImpl::addCallbackListener(JTuner* jtuner, jweak listener) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jweak listenerRef = env->NewWeakGlobalRef(listener); - ALOGV("addCallbackListener() with listener:%p and ref:%p @%p", listener, listenerRef, this); + ALOGV("addCallbackListener() with listener:%p and ref:%p @%p", + listener, listenerRef, this); std::scoped_lock<std::mutex> lock(mMutex); mListenersMap[jtuner] = listenerRef; } @@ -1345,18 +1346,43 @@ int JTuner::shareFrontend(int feId) { return (int)Result::SUCCESS; } +int JTuner::unshareFrontend() { + if (mFeClient != nullptr) { + ALOGE("Cannot unshare frontend because this session is already holding %d" + " as an owner instead of as a sharee", mFeClient->getId()); + return (int)Result::INVALID_STATE; + } + + mSharedFeId = (int)Constant::INVALID_FRONTEND_ID; + return (int)Result::SUCCESS; +} + void JTuner::registerFeCbListener(JTuner* jtuner) { + ALOGV("registerFeCbListener: %p", jtuner); if (mFeClientCb != nullptr && jtuner != nullptr) { mFeClientCb->addCallbackListener(jtuner, jtuner->getObject()); } } void JTuner::unregisterFeCbListener(JTuner* jtuner) { + ALOGV("unregisterFeCbListener: %p", jtuner); if (mFeClientCb != nullptr && jtuner != nullptr) { mFeClientCb->removeCallbackListener(jtuner); } } +void JTuner::updateFrontend(JTuner* jtuner) { + if (jtuner == nullptr) { + ALOGV("JTuner::updateFrontend(null) called for previous owner: %p", this); + mFeClient = nullptr; + mFeClientCb = nullptr; + } else { + ALOGV("JTuner::updateFrontend(%p) called for new owner: %p", jtuner, this); + mFeClient = jtuner->mFeClient; + mFeClientCb = jtuner->mFeClientCb; + } +} + jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendCapabilities &caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); @@ -3319,6 +3345,12 @@ static int android_media_tv_Tuner_share_frontend( return tuner->shareFrontend(id); } +static int android_media_tv_Tuner_unshare_frontend( + JNIEnv *env, jobject thiz) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->unshareFrontend(); +} + static void android_media_tv_Tuner_register_fe_cb_listener( JNIEnv *env, jobject thiz, jlong shareeJTuner) { sp<JTuner> tuner = getTuner(env, thiz); @@ -3333,6 +3365,17 @@ static void android_media_tv_Tuner_unregister_fe_cb_listener( tuner->unregisterFeCbListener(jtuner); } +static void android_media_tv_Tuner_update_frontend(JNIEnv *env, jobject thiz, jlong jtunerPtr) { + sp<JTuner> tuner = getTuner(env, thiz); + JTuner *jtuner; + if (jtunerPtr == 0) { + jtuner = nullptr; + } else { + jtuner = (JTuner *) jtunerPtr; + } + tuner->updateFrontend(jtuner); +} + static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) { sp<JTuner> tuner = getTuner(env, thiz); FrontendSettings setting = getFrontendSettings(env, type, settings); @@ -4574,10 +4617,14 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_open_frontend_by_handle }, { "nativeShareFrontend", "(I)I", (void *)android_media_tv_Tuner_share_frontend }, + { "nativeUnshareFrontend", "()I", + (void *)android_media_tv_Tuner_unshare_frontend }, { "nativeRegisterFeCbListener", "(J)V", (void*)android_media_tv_Tuner_register_fe_cb_listener }, { "nativeUnregisterFeCbListener", "(J)V", (void*)android_media_tv_Tuner_unregister_fe_cb_listener }, + { "nativeUpdateFrontend", "(J)V", + (void*)android_media_tv_Tuner_update_frontend }, { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I", (void *)android_media_tv_Tuner_tune }, { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune }, diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index f1b31e3520b1..502bd6b18413 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -178,8 +178,10 @@ struct JTuner : public RefBase { jobject getFrontendIds(); jobject openFrontendByHandle(int feHandle); int shareFrontend(int feId); + int unshareFrontend(); void registerFeCbListener(JTuner* jtuner); void unregisterFeCbListener(JTuner* jtuner); + void updateFrontend(JTuner* jtuner); jint closeFrontendById(int id); jobject getFrontendInfo(int id); int tune(const FrontendSettings& settings); diff --git a/media/native/midi/MidiDeviceInfo.cpp b/media/native/midi/MidiDeviceInfo.cpp index 8a573fba322b..14524883470f 100644 --- a/media/native/midi/MidiDeviceInfo.cpp +++ b/media/native/midi/MidiDeviceInfo.cpp @@ -64,6 +64,7 @@ status_t MidiDeviceInfo::writeToParcel(Parcel* parcel) const { RETURN_IF_FAILED(writeStringVector(parcel, mInputPortNames)); RETURN_IF_FAILED(writeStringVector(parcel, mOutputPortNames)); RETURN_IF_FAILED(parcel->writeInt32(mIsPrivate ? 1 : 0)); + RETURN_IF_FAILED(parcel->writeInt32(mDefaultProtocol)); RETURN_IF_FAILED(mProperties.writeToParcel(parcel)); // This corresponds to "extra" properties written by Java code RETURN_IF_FAILED(mProperties.writeToParcel(parcel)); @@ -83,6 +84,7 @@ status_t MidiDeviceInfo::readFromParcel(const Parcel* parcel) { int32_t isPrivate; RETURN_IF_FAILED(parcel->readInt32(&isPrivate)); mIsPrivate = isPrivate == 1; + RETURN_IF_FAILED(parcel->readInt32(&mDefaultProtocol)); RETURN_IF_FAILED(mProperties.readFromParcel(parcel)); // Ignore "extra" properties as they may contain Java Parcelables return OK; @@ -130,7 +132,8 @@ bool operator==(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs) { areVectorsEqual(lhs.mInputPortNames, rhs.mInputPortNames) && areVectorsEqual(lhs.mOutputPortNames, rhs.mOutputPortNames) && lhs.mProperties == rhs.mProperties && - lhs.mIsPrivate == rhs.mIsPrivate); + lhs.mIsPrivate == rhs.mIsPrivate && + lhs.mDefaultProtocol == rhs.mDefaultProtocol); } } // namespace midi diff --git a/media/native/midi/MidiDeviceInfo.h b/media/native/midi/MidiDeviceInfo.h index 5b4a241323d7..23e1cb474168 100644 --- a/media/native/midi/MidiDeviceInfo.h +++ b/media/native/midi/MidiDeviceInfo.h @@ -38,6 +38,7 @@ public: int getType() const { return mType; } int getUid() const { return mId; } bool isPrivate() const { return mIsPrivate; } + int getDefaultProtocol() const { return mDefaultProtocol; } const Vector<String16>& getInputPortNames() const { return mInputPortNames; } const Vector<String16>& getOutputPortNames() const { return mOutputPortNames; } String16 getProperty(const char* propertyName); @@ -48,6 +49,18 @@ public: TYPE_VIRTUAL = 2, TYPE_BLUETOOTH = 3, }; + + enum { + PROTOCOL_UMP_USE_MIDI_CI = 0, + PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1, + PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2, + PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3, + PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4, + PROTOCOL_UMP_MIDI_2_0 = 17, + PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18, + PROTOCOL_UNKNOWN = -1, + }; + static const char* const PROPERTY_NAME; static const char* const PROPERTY_MANUFACTURER; static const char* const PROPERTY_PRODUCT; @@ -72,6 +85,7 @@ private: Vector<String16> mOutputPortNames; os::PersistableBundle mProperties; bool mIsPrivate; + int32_t mDefaultProtocol; }; } // namespace midi diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp index f90796e415c0..aa076e85e30d 100644 --- a/media/native/midi/amidi.cpp +++ b/media/native/midi/amidi.cpp @@ -138,6 +138,7 @@ static media_status_t AMIDI_getDeviceInfo(const AMidiDevice *device, outDeviceInfoPtr->type = deviceInfo.getType(); outDeviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size(); outDeviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size(); + outDeviceInfoPtr->defaultProtocol = deviceInfo.getDefaultProtocol(); return AMEDIA_OK; } @@ -238,6 +239,13 @@ ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) { return device->deviceInfo.outputPortCount; } +AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device) { + if (device == nullptr) { + return AMIDI_DEVICE_PROTOCOL_UNKNOWN; + } + return static_cast<AMidiDevice_Protocol>(device->deviceInfo.defaultProtocol); +} + /* * Port Helpers */ diff --git a/media/native/midi/amidi_internal.h b/media/native/midi/amidi_internal.h index fce85963d217..023a6f5ec900 100644 --- a/media/native/midi/amidi_internal.h +++ b/media/native/midi/amidi_internal.h @@ -25,6 +25,7 @@ typedef struct { int32_t type; /* one of AMIDI_DEVICE_TYPE_* constants */ int32_t inputPortCount; /* number of input (send) ports associated with the device */ int32_t outputPortCount; /* number of output (received) ports associated with the device */ + int32_t defaultProtocol; /* one of the AMIDI_DEVICE_PROTOCOL_* constants */ } AMidiDeviceInfo; struct AMidiDevice { diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h index 742db34b74a7..fbb7fb329659 100644 --- a/media/native/midi/include/amidi/AMidi.h +++ b/media/native/midi/include/amidi/AMidi.h @@ -62,6 +62,78 @@ enum { }; /* + * Protocol IDs for various MIDI devices. + * + * Introduced in API 33. + */ +enum AMidiDevice_Protocol : int32_t { + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use UMP to negotiate with the device with MIDI-CI. + * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec. + */ + AMIDI_DEVICE_PROTOCOL_UMP_USE_MIDI_CI = 0, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 64 bits. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 64 bits and jitter reduction timestamps. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 128 bits. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 128 bits and jitter reduction timestamps. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 2.0 through UMP. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0 = 17, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 2.0 through UMP and jitter reduction timestamps. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18, + + /** + * Constant representing a device with an unknown default protocol. + * If Universal MIDI Packets (UMP) are needed, use MIDI-CI through MIDI 1.0. + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec. + */ + AMIDI_DEVICE_PROTOCOL_UNKNOWN = -1 +}; + +/* * Device API */ /** @@ -134,6 +206,30 @@ ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) __INTR */ ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) __INTRODUCED_IN(29); +/** + * Gets the MIDI device default protocol. + * + * @param device Specifies the MIDI device. + * + * @return The identifier of the MIDI device default protocol: + * AMIDI_DEVICE_PROTOCOL_UMP_USE_MIDI_CI + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0 + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0_AND_JRTS + * AMIDI_DEVICE_PROTOCOL_UNKNOWN + * + * Most devices should return PROTOCOL_UNKNOWN (-1). This is intentional as devices + * with default UMP support are not backwards compatible. When the device is null, + * return AMIDI_DEVICE_PROTOCOL_UNKNOWN. + * + * Available since API 33. + */ +AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device) + __INTRODUCED_IN(33); + /* * API for receiving data from the Output port of a device. */ diff --git a/media/native/midi/libamidi.map.txt b/media/native/midi/libamidi.map.txt index 62627f8c5ef7..f25f97770b50 100644 --- a/media/native/midi/libamidi.map.txt +++ b/media/native/midi/libamidi.map.txt @@ -2,6 +2,7 @@ LIBAMIDI { global: AMidiDevice_fromJava; AMidiDevice_release; + AMidiDevice_getDefaultProtocol; # introduced=Tiramisu AMidiDevice_getType; AMidiDevice_getNumInputPorts; AMidiDevice_getNumOutputPorts; diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java index 62c313ace306..2dd9525867b6 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java @@ -260,7 +260,8 @@ public final class BluetoothMidiDevice { inputPortReceivers[0] = mEventScheduler.getReceiver(); mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1, - null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, mDeviceServerCallback); + null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, + MidiDeviceInfo.PROTOCOL_UNKNOWN, mDeviceServerCallback); mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0]; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java new file mode 100644 index 000000000000..fd66d3b9904e --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java @@ -0,0 +1,69 @@ +/* + * 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.mediaframeworktest.unit; + +import static org.junit.Assert.assertEquals; + +import android.bluetooth.BluetoothProfile; +import android.media.BtProfileConnectionInfo; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class BtProfileConnectionInfoTest { + + @Test + public void testCoverageA2dp() { + final boolean supprNoisy = false; + final int volume = 42; + final BtProfileConnectionInfo info = BtProfileConnectionInfo.a2dpInfo(supprNoisy, volume); + assertEquals(info.getProfile(), BluetoothProfile.A2DP); + assertEquals(info.getSuppressNoisyIntent(), supprNoisy); + assertEquals(info.getVolume(), volume); + } + + @Test + public void testCoverageA2dpSink() { + final int volume = 42; + final BtProfileConnectionInfo info = BtProfileConnectionInfo.a2dpSinkInfo(volume); + assertEquals(info.getProfile(), BluetoothProfile.A2DP_SINK); + assertEquals(info.getVolume(), volume); + } + + @Test + public void testCoveragehearingAid() { + final boolean supprNoisy = true; + final BtProfileConnectionInfo info = BtProfileConnectionInfo.hearingAidInfo(supprNoisy); + assertEquals(info.getProfile(), BluetoothProfile.HEARING_AID); + assertEquals(info.getSuppressNoisyIntent(), supprNoisy); + } + + @Test + public void testCoverageLeAudio() { + final boolean supprNoisy = false; + final boolean isLeOutput = true; + final BtProfileConnectionInfo info = BtProfileConnectionInfo.leAudio(supprNoisy, + isLeOutput); + assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO); + assertEquals(info.getSuppressNoisyIntent(), supprNoisy); + assertEquals(info.getIsLeOutput(), isLeOutput); + } +} + diff --git a/media/tests/TunerTest/OWNERS b/media/tests/TunerTest/OWNERS index 73ea663aa37e..75548894c9f0 100644 --- a/media/tests/TunerTest/OWNERS +++ b/media/tests/TunerTest/OWNERS @@ -1,4 +1,4 @@ -amyjojo@google.com -nchalko@google.com quxiangfang@google.com shubang@google.com +hgchen@google.com +kemiyagi@google.com
\ No newline at end of file diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp index deee5b173c21..fbd4b2ec8736 100644 --- a/native/android/choreographer.cpp +++ b/native/android/choreographer.cpp @@ -71,11 +71,12 @@ int64_t AChoreographerFrameCallbackData_getFrameTimelineVsyncId( const AChoreographerFrameCallbackData* data, size_t index) { return AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(data, index); } -int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime( +int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos( const AChoreographerFrameCallbackData* data, size_t index) { - return AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTime(data, index); + return AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTimeNanos(data, + index); } -int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadline( +int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos( const AChoreographerFrameCallbackData* data, size_t index) { - return AChoreographerFrameCallbackData_routeGetFrameTimelineDeadline(data, index); + return AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos(data, index); } diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 3c1aa4409254..35c794e82695 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -34,8 +34,8 @@ LIBANDROID { AChoreographerFrameCallbackData_getFrameTimelinesLength; # introduced=33 AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex; # introduced=33 AChoreographerFrameCallbackData_getFrameTimelineVsyncId; # introduced=33 - AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime; # introduced=33 - AChoreographerFrameCallbackData_getFrameTimelineDeadline; # introduced=33 + AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos; # introduced=33 + AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos; # introduced=33 AConfiguration_copy; AConfiguration_delete; AConfiguration_diff; diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt index a6c1b5098066..32fd734d61a0 100644 --- a/native/android/libandroid_net.map.txt +++ b/native/android/libandroid_net.map.txt @@ -18,6 +18,10 @@ LIBANDROID_NET { android_getprocnetwork; # llndk android_setprocdns; # llndk android_getprocdns; # llndk + # These functions have been part of the NDK since API 33. + android_tag_socket_with_uid; # llndk + android_tag_socket; # llndk + android_untag_socket; # llndk local: *; }; diff --git a/native/android/net.c b/native/android/net.c index e2f36a77b7c6..d7c22e1a5741 100644 --- a/native/android/net.c +++ b/native/android/net.c @@ -161,3 +161,15 @@ int android_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen, void android_res_cancel(int nsend_fd) { resNetworkCancel(nsend_fd); } + +int android_tag_socket_with_uid(int sockfd, int tag, uid_t uid) { + return tagSocket(sockfd, tag, uid); +} + +int android_tag_socket(int sockfd, int tag) { + return tagSocket(sockfd, tag, -1); +} + +int android_untag_socket(int sockfd) { + return untagSocket(sockfd); +} diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java index f684a4d9d89a..2b6570a6ecb0 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java @@ -545,9 +545,18 @@ public final class NetworkStats implements AutoCloseable { } /** + * Collects tagged summary results and sets summary enumeration mode. + * @throws RemoteException + */ + void startTaggedSummaryEnumeration() throws RemoteException { + mSummary = mSession.getTaggedSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp); + mEnumerationIndex = 0; + } + + /** * Collects history results for uid and resets history enumeration index. */ - void startHistoryEnumeration(int uid, int tag, int state) { + void startHistoryUidEnumeration(int uid, int tag, int state) { mHistory = null; try { mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid, @@ -562,6 +571,20 @@ public final class NetworkStats implements AutoCloseable { } /** + * Collects history results for network and resets history enumeration index. + */ + void startHistoryDeviceEnumeration() { + try { + mHistory = mSession.getHistoryIntervalForNetwork( + mTemplate, NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); + } catch (RemoteException e) { + Log.w(TAG, e); + mHistory = null; + } + mEnumerationIndex = 0; + } + + /** * Starts uid enumeration for current user. * @throws RemoteException */ diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java index 583f7ba8cde5..4b906c908959 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -18,6 +18,7 @@ package android.app.usage; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -142,7 +143,21 @@ public class NetworkStatsManager { setAugmentWithSubscriptionPlan(true); } - /** @hide */ + /** + * Set poll on open flag to indicate the poll is needed before service gets statistics + * result. This is default enabled. However, for any non-privileged caller, the poll might + * be omitted in case of rate limiting. + * + * @param pollOnOpen true if poll is needed. + * @hide + */ + // The system will ignore any non-default values for non-privileged + // processes, so processes that don't hold the appropriate permissions + // can make no use of this API. + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean pollOnOpen) { if (pollOnOpen) { mFlags |= FLAG_POLL_ON_OPEN; @@ -369,7 +384,7 @@ public class NetworkStatsManager { * @return Statistics which is described above. * @hide */ - @Nullable + @NonNull // @SystemApi(client = MODULE_LIBRARIES) @WorkerThread public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime, @@ -386,6 +401,75 @@ public class NetworkStatsManager { } /** + * Query tagged network usage statistics summaries. + * + * The results will only include tagged traffic made by UIDs belonging to the calling user + * profile. The results are aggregated over time, so that all buckets will have the same + * start and end timestamps as the passed arguments. Not aggregated over state, uid, + * default network, metered, or roaming. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param template Template used to match networks. See {@link NetworkTemplate}. + * @param startTime Start of period, in milliseconds since the Unix epoch, see + * {@link System#currentTimeMillis}. + * @param endTime End of period, in milliseconds since the Unix epoch, see + * {@link System#currentTimeMillis}. + * @return Statistics which is described above. + * @hide + */ + @NonNull + // @SystemApi(client = MODULE_LIBRARIES) + @WorkerThread + public NetworkStats queryTaggedSummary(@NonNull NetworkTemplate template, long startTime, + long endTime) throws SecurityException { + try { + NetworkStats result = + new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + result.startTaggedSummaryEnumeration(); + return result; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return null; // To make the compiler happy. + } + + /** + * Query usage statistics details for networks matching a given {@link NetworkTemplate}. + * + * Result is not aggregated over time. This means buckets' start and + * end timestamps will be between 'startTime' and 'endTime' parameters. + * <p>Only includes buckets whose entire time period is included between + * startTime and endTime. Doesn't interpolate or return partial buckets. + * Since bucket length is in the order of hours, this + * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param template Template used to match networks. See {@link NetworkTemplate}. + * @param startTime Start of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @return Statistics which is described above. + * @hide + */ + @NonNull + // @SystemApi(client = MODULE_LIBRARIES) + @WorkerThread + public NetworkStats queryDetailsForDevice(@NonNull NetworkTemplate template, + long startTime, long endTime) { + try { + final NetworkStats result = + new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + result.startHistoryDeviceEnumeration(); + return result; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + return null; // To make the compiler happy. + } + + /** * Query network usage statistics details for a given uid. * This may take a long time, and apps should avoid calling this on their main thread. * @@ -451,7 +535,8 @@ public class NetworkStatsManager { * @param endTime End of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param uid UID of app - * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for no tags. + * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data + * across all the tags. * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate * traffic from all states. * @return Statistics object or null if an error happened during statistics collection. @@ -466,21 +551,51 @@ public class NetworkStatsManager { return queryDetailsForUidTagState(template, startTime, endTime, uid, tag, state); } - /** @hide */ - public NetworkStats queryDetailsForUidTagState(NetworkTemplate template, + /** + * Query network usage statistics details for a given template, uid, tag, and state. + * + * Only usable for uids belonging to calling user. Result is not aggregated over time. + * This means buckets' start and end timestamps are going to be between 'startTime' and + * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag + * the same as the 'tag' parameter, and the state the same as the 'state' parameter. + * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and + * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}. + * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't + * interpolate across partial buckets. Since bucket length is in the order of hours, this + * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param template Template used to match networks. See {@link NetworkTemplate}. + * @param startTime Start of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param uid UID of app + * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data + * across all the tags. + * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate + * traffic from all states. + * @return Statistics which is described above. + * @hide + */ + @NonNull + // @SystemApi(client = MODULE_LIBRARIES) + @WorkerThread + public NetworkStats queryDetailsForUidTagState(@NonNull NetworkTemplate template, long startTime, long endTime, int uid, int tag, int state) throws SecurityException { - - NetworkStats result; try { - result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); - result.startHistoryEnumeration(uid, tag, state); + final NetworkStats result = new NetworkStats( + mContext, template, mFlags, startTime, endTime, mService); + result.startHistoryUidEnumeration(uid, tag, state); + return result; } catch (RemoteException e) { Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag + " state=" + state, e); - return null; + e.rethrowFromSystemServer(); } - return result; + return null; // To make the compiler happy. } /** @@ -830,4 +945,83 @@ public class NetworkStatsManager { return msg.getData().getParcelable(key); } } + + /** + * Mark given UID as being in foreground for stats purposes. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void setUidForeground(int uid, boolean uidForeground) { + try { + mService.setUidForeground(uid, uidForeground); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set default value of global alert bytes, the value will be clamped to [128kB, 2MB]. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + Manifest.permission.NETWORK_STACK}) + public void setDefaultGlobalAlert(long alertBytes) { + try { + // TODO: Sync internal naming with the API surface. + mService.advisePersistThreshold(alertBytes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Force update of statistics. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void forceUpdate() { + try { + mService.forceUpdate(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set the warning and limit to all registered custom network stats providers. + * Note that invocation of any interface will be sent to all providers. + * + * Asynchronicity notes : because traffic may be happening on the device at the same time, it + * doesn't make sense to wait for the warning and limit to be set – a caller still wouldn't + * know when exactly it was effective. All that can matter is that it's done quickly. Also, + * this method can't fail, so there is no status to return. All providers will see the new + * values soon. + * As such, this method returns immediately and sends the warning and limit to all providers + * as soon as possible through a one-way binder call. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning, + long limit) { + try { + mService.setStatsProviderWarningAndLimitAsync(iface, warning, limit); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java index 62c576144221..925d12b574a6 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java @@ -23,8 +23,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import com.android.internal.util.Preconditions; - import java.util.Objects; /** @@ -47,7 +45,9 @@ public final class EthernetNetworkSpecifier extends NetworkSpecifier implements * @param interfaceName Name of the ethernet interface the specifier refers to. */ public EthernetNetworkSpecifier(@NonNull String interfaceName) { - Preconditions.checkStringNotEmpty(interfaceName); + if (TextUtils.isEmpty(interfaceName)) { + throw new IllegalArgumentException(); + } mInterfaceName = interfaceName; } diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl index 12937b5cb2c7..a4babb543dbd 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl @@ -94,4 +94,16 @@ interface INetworkStatsService { /** Registers a network stats provider */ INetworkStatsProviderCallback registerNetworkStatsProvider(String tag, in INetworkStatsProvider provider); + + /** Mark given UID as being in foreground for stats purposes. */ + void setUidForeground(int uid, boolean uidForeground); + + /** Advise persistence threshold; may be overridden internally. */ + void advisePersistThreshold(long thresholdBytes); + + /** + * Set the warning and limit to all registered custom network stats providers. + * Note that invocation of any interface will be sent to all providers. + */ + void setStatsProviderWarningAndLimitAsync(String iface, long warning, long limit); } diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl index dfedf6633dcd..ab70be826f8e 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl @@ -32,6 +32,11 @@ interface INetworkStatsSession { /** Return historical network layer stats for traffic that matches template. */ @UnsupportedAppUsage NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields); + /** + * Return historical network layer stats for traffic that matches template, start and end + * timestamp. + */ + NetworkStatsHistory getHistoryIntervalForNetwork(in NetworkTemplate template, int fields, long start, long end); /** * Return network layer usage summary per UID for traffic that matches template. @@ -46,6 +51,10 @@ interface INetworkStatsSession { */ @UnsupportedAppUsage NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags); + + /** Return network layer usage summary per UID for tagged traffic that matches template. */ + NetworkStats getTaggedSummaryForAllUid(in NetworkTemplate template, long start, long end); + /** Return historical network layer stats for specific UID traffic that matches template. */ @UnsupportedAppUsage NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields); diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java index 0d15dffae92d..a423783bc1ca 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java @@ -17,6 +17,7 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -25,8 +26,8 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; -import android.net.annotations.PolicyDirection; import android.os.Binder; +import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -41,6 +42,8 @@ import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; @@ -88,6 +91,11 @@ public final class IpSecManager { @SystemApi(client = MODULE_LIBRARIES) public static final int DIRECTION_FWD = 2; + /** @hide */ + @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) + @Retention(RetentionPolicy.SOURCE) + public @interface PolicyDirection {} + /** * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * @@ -981,6 +989,29 @@ public final class IpSecManager { } /** + * @hide + */ + public IpSecTransformResponse createTransform(IpSecConfig config, IBinder binder, + String callingPackage) { + try { + return mService.createTransform(config, binder, callingPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public void deleteTransform(int resourceId) { + try { + mService.deleteTransform(resourceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Construct an instance of IpSecManager within an application context. * * @param context the application context for this manager diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java index 36199a046cfc..68ae5de4ee70 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java @@ -26,9 +26,6 @@ import android.annotation.SystemApi; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.util.Log; @@ -93,16 +90,9 @@ public final class IpSecTransform implements AutoCloseable { mResourceId = INVALID_RESOURCE_ID; } - private IIpSecService getIpSecService() { - IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE); - if (b == null) { - throw new RemoteException("Failed to connect to IpSecService") - .rethrowAsRuntimeException(); - } - - return IIpSecService.Stub.asInterface(b); + private IpSecManager getIpSecManager(Context context) { + return context.getSystemService(IpSecManager.class); } - /** * Checks the result status and throws an appropriate exception if the status is not Status.OK. */ @@ -130,8 +120,7 @@ public final class IpSecTransform implements AutoCloseable { IpSecManager.SpiUnavailableException { synchronized (this) { try { - IIpSecService svc = getIpSecService(); - IpSecTransformResponse result = svc.createTransform( + IpSecTransformResponse result = getIpSecManager(mContext).createTransform( mConfig, new Binder(), mContext.getOpPackageName()); int status = result.status; checkResultStatus(status); @@ -140,8 +129,6 @@ public final class IpSecTransform implements AutoCloseable { mCloseGuard.open("build"); } catch (ServiceSpecificException e) { throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); } } @@ -177,10 +164,7 @@ public final class IpSecTransform implements AutoCloseable { return; } try { - IIpSecService svc = getIpSecService(); - svc.deleteTransform(mResourceId); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); + getIpSecManager(mContext).deleteTransform(mResourceId); } catch (Exception e) { // On close we swallow all random exceptions since failure to close is not // actionable by the user. diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java index 8f1115e065dd..04d1d6885186 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java @@ -160,11 +160,6 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { // Not dumping mSubType, subtypes are no longer supported. - if (mSubscriberId != null) { - proto.write(NetworkIdentityProto.SUBSCRIBER_ID, - NetworkIdentityUtils.scrubSubscriberId(mSubscriberId)); - } - proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId); proto.write(NetworkIdentityProto.ROAMING, mRoaming); proto.write(NetworkIdentityProto.METERED, mMetered); proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork); diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java index b00fea4de269..9175809d9c7c 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java @@ -41,6 +41,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -57,7 +58,7 @@ import java.util.function.Predicate; */ // @NotThreadSafe @SystemApi -public final class NetworkStats implements Parcelable { +public final class NetworkStats implements Parcelable, Iterable<NetworkStats.Entry> { private static final String TAG = "NetworkStats"; /** @@ -383,6 +384,95 @@ public final class NetworkStats implements Parcelable { this.operations += another.operations; } + /** + * @return interface name of this entry. + * @hide + */ + @Nullable public String getIface() { + return iface; + } + + /** + * @return the uid of this entry. + */ + public int getUid() { + return uid; + } + + /** + * @return the set state of this entry. Should be one of the following + * values: {@link #SET_DEFAULT}, {@link #SET_FOREGROUND}. + */ + @State public int getSet() { + return set; + } + + /** + * @return the tag value of this entry. + */ + public int getTag() { + return tag; + } + + /** + * @return the metered state. Should be one of the following + * values: {link #METERED_YES}, {link #METERED_NO}. + */ + @Meteredness public int getMetered() { + return metered; + } + + /** + * @return the roaming state. Should be one of the following + * values: {link #ROAMING_YES}, {link #ROAMING_NO}. + */ + @Roaming public int getRoaming() { + return roaming; + } + + /** + * @return the default network state. Should be one of the following + * values: {link #DEFAULT_NETWORK_YES}, {link #DEFAULT_NETWORK_NO}. + */ + @DefaultNetwork public int getDefaultNetwork() { + return defaultNetwork; + } + + /** + * @return the number of received bytes. + */ + public long getRxBytes() { + return rxBytes; + } + + /** + * @return the number of received packets. + */ + public long getRxPackets() { + return rxPackets; + } + + /** + * @return the number of transmitted bytes. + */ + public long getTxBytes() { + return txBytes; + } + + /** + * @return the number of transmitted packets. + */ + public long getTxPackets() { + return txPackets; + } + + /** + * @return the count of network operations performed for this entry. + */ + public long getOperations() { + return operations; + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); @@ -589,11 +679,40 @@ public final class NetworkStats implements Parcelable { } /** + * Iterate over Entry objects. + * + * Return an iterator of this object that will iterate through all contained Entry objects. + * + * This iterator does not support concurrent modification and makes no guarantee of fail-fast + * behavior. If any method that can mutate the contents of this object is called while + * iteration is in progress, either inside the loop or in another thread, then behavior is + * undefined. + * The remove() method is not implemented and will throw UnsupportedOperationException. + * @hide + */ + @SystemApi + @NonNull public Iterator<Entry> iterator() { + return new Iterator<Entry>() { + int mIndex = 0; + + @Override + public boolean hasNext() { + return mIndex < size; + } + + @Override + public Entry next() { + return getValues(mIndex++, null); + } + }; + } + + /** * Return specific stats entry. * @hide */ @UnsupportedAppUsage - public Entry getValues(int i, Entry recycle) { + public Entry getValues(int i, @Nullable Entry recycle) { final Entry entry = recycle != null ? recycle : new Entry(); entry.iface = iface[i]; entry.uid = uid[i]; diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java index 7935d28f305d..9f9d73f88851 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java @@ -46,8 +46,6 @@ import android.util.Range; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.FastDataInput; -import com.android.internal.util.FastDataOutput; import com.android.internal.util.FileRotator; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkStatsUtils; @@ -58,6 +56,7 @@ import java.io.BufferedInputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; +import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -83,9 +82,6 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** File header magic number: "ANET" */ private static final int FILE_MAGIC = 0x414E4554; - /** Default buffer size from BufferedInputStream */ - private static final int BUFFER_SIZE = 8192; - private static final int VERSION_NETWORK_INIT = 1; private static final int VERSION_UID_INIT = 1; @@ -439,8 +435,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W @Override public void read(InputStream in) throws IOException { - final FastDataInput dataIn = new FastDataInput(in, BUFFER_SIZE); - read(dataIn); + read((DataInput) new DataInputStream(in)); } private void read(DataInput in) throws IOException { @@ -479,9 +474,8 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W @Override public void write(OutputStream out) throws IOException { - final FastDataOutput dataOut = new FastDataOutput(out, BUFFER_SIZE); - write(dataOut); - dataOut.flush(); + write((DataOutput) new DataOutputStream(out)); + out.flush(); } private void write(DataOutput out) throws IOException { diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java index d8feb88f0fe4..1af32bf5524c 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -27,8 +28,8 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.media.MediaPlayer; import android.os.Build; +import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceManager; import com.android.server.NetworkManagementSocketTagger; @@ -36,6 +37,8 @@ import dalvik.system.SocketTagger; import java.io.FileDescriptor; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.DatagramSocket; import java.net.Socket; import java.net.SocketException; @@ -53,6 +56,7 @@ import java.net.SocketException; * use {@link NetworkStatsManager} instead. */ public class TrafficStats { + private static final String TAG = TrafficStats.class.getSimpleName(); /** * The return value to indicate that the device does not support the statistic. */ @@ -173,12 +177,25 @@ public class TrafficStats { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private synchronized static INetworkStatsService getStatsService() { if (sStatsService == null) { - sStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + sStatsService = getStatsBinder(); } return sStatsService; } + @Nullable + private static INetworkStatsService getStatsBinder() { + try { + final Method getServiceMethod = Class.forName("android.os.ServiceManager") + .getDeclaredMethod("getService", new Class[]{String.class}); + final IBinder binder = (IBinder) getServiceMethod.invoke( + null, Context.NETWORK_STATS_SERVICE); + return INetworkStatsService.Stub.asInterface(binder); + } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException + | InvocationTargetException e) { + throw new NullPointerException("Cannot get INetworkStatsService: " + e); + } + } + /** * Snapshot of {@link NetworkStats} when the currently active profiling * session started, or {@code null} if no session active. @@ -265,6 +282,18 @@ public class TrafficStats { } /** + * Set active tag to use when accounting {@link Socket} traffic originating + * from the current thread. The tag used internally is well-defined to + * distinguish all download provider traffic. + * + * @hide + */ + @SystemApi + public static void setThreadStatsTagDownload() { + setThreadStatsTag(TAG_SYSTEM_DOWNLOAD); + } + + /** * Get the active tag used when accounting {@link Socket} traffic originating * from the current thread. Only one active tag per thread is supported. * {@link #tagSocket(Socket)}. diff --git a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java index e35f6a648b77..1eb52fb44629 100644 --- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java +++ b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java @@ -26,6 +26,7 @@ import java.net.SocketException; /** * Assigns tags to sockets for traffic stats. + * @hide */ public final class NetworkManagementSocketTagger extends SocketTagger { private static final String TAG = "NetworkManagementSocketTagger"; diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java deleted file mode 100644 index 0e9a9da6804b..000000000000 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java +++ /dev/null @@ -1,45 +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. - */ - -package com.android.server.net; - -import android.annotation.NonNull; -import android.net.NetworkStats; -import android.net.NetworkTemplate; - -public abstract class NetworkStatsManagerInternal { - /** Return network layer usage total for traffic that matches template. */ - public abstract long getNetworkTotalBytes(NetworkTemplate template, long start, long end); - - /** Return network layer usage per-UID for traffic that matches template. */ - public abstract NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end); - - /** Mark given UID as being in foreground for stats purposes. */ - public abstract void setUidForeground(int uid, boolean uidForeground); - - /** Advise persistance threshold; may be overridden internally. */ - public abstract void advisePersistThreshold(long thresholdBytes); - - /** Force update of statistics. */ - public abstract void forceUpdate(); - - /** - * Set the warning and limit to all registered custom network stats providers. - * Note that invocation of any interface will be sent to all providers. - */ - public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning, - long limit); -} diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index 4db90c1b1a5a..a0710f77b4e0 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -44,6 +44,7 @@ import static android.net.NetworkTemplate.buildTemplateMobileWildcard; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; +import static android.net.TrafficStats.UID_TETHERING; import static android.net.TrafficStats.UNSUPPORTED; import static android.os.Trace.TRACE_TAG_NETWORK; import static android.provider.Settings.Global.NETSTATS_AUGMENT_ENABLED; @@ -70,7 +71,7 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport; -import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; +import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT; import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats; import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet; @@ -89,13 +90,13 @@ import android.content.pm.PackageManager; import android.database.ContentObserver; import android.net.DataUsageRequest; import android.net.INetd; -import android.net.INetworkManagementEventObserver; import android.net.INetworkStatsService; import android.net.INetworkStatsSession; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkIdentity; import android.net.NetworkIdentitySet; +import android.net.NetworkPolicyManager; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStateSnapshot; @@ -106,6 +107,7 @@ import android.net.NetworkStatsCollection; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TelephonyNetworkSpecifier; +import android.net.TetherStatsParcel; import android.net.TetheringManager; import android.net.TrafficStats; import android.net.UnderlyingNetworkInfo; @@ -120,12 +122,12 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; -import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; @@ -148,13 +150,13 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FileRotator; +import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.BestClock; import com.android.net.module.util.BinderUtils; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.LocationPermissionChecker; import com.android.net.module.util.NetworkStatsUtils; import com.android.net.module.util.PermissionUtils; -import com.android.server.EventLogTags; -import com.android.server.LocalServices; import java.io.File; import java.io.FileDescriptor; @@ -207,8 +209,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final String TAG_NETSTATS_ERROR = "netstats_error"; + /** + * EventLog tags used when logging into the event log. Note the values must be sync with + * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct + * name translation. + */ + private static final int LOG_TAG_NETSTATS_MOBILE_SAMPLE = 51100; + private static final int LOG_TAG_NETSTATS_WIFI_SAMPLE = 51101; + private final Context mContext; - private final INetworkManagementService mNetworkManager; private final NetworkStatsFactory mStatsFactory; private final AlarmManager mAlarmManager; private final Clock mClock; @@ -223,6 +232,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final ContentObserver mContentObserver; private final ContentResolver mContentResolver; + protected INetd mNetd; + private final AlertObserver mAlertObserver = new AlertObserver(); + @VisibleForTesting public static final String ACTION_NETWORK_STATS_POLL = "com.android.server.action.NETWORK_STATS_POLL"; @@ -354,6 +366,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @NonNull private final NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor; + @NonNull + private final LocationPermissionChecker mLocationPermissionChecker; + private static @NonNull File getDefaultSystemDir() { return new File(Environment.getDataDirectory(), "system"); } @@ -405,20 +420,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - public static NetworkStatsService create(Context context, - INetworkManagementService networkManager) { + /** Creates a new NetworkStatsService */ + public static NetworkStatsService create(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); final INetd netd = INetd.Stub.asInterface( (IBinder) context.getSystemService(Context.NETD_SERVICE)); - final NetworkStatsService service = new NetworkStatsService(context, networkManager, + final NetworkStatsService service = new NetworkStatsService(context, + INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)), alarmManager, wakeLock, getDefaultClock(), new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd), new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(), new Dependencies()); - service.registerLocalService(); return service; } @@ -426,14 +441,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // This must not be called outside of tests, even within the same package, as this constructor // does not register the local service. Use the create() helper above. @VisibleForTesting - NetworkStatsService(Context context, INetworkManagementService networkManager, - AlarmManager alarmManager, PowerManager.WakeLock wakeLock, Clock clock, - NetworkStatsSettings settings, NetworkStatsFactory factory, - NetworkStatsObservers statsObservers, File systemDir, File baseDir, - @NonNull Dependencies deps) { + NetworkStatsService(Context context, INetd netd, AlarmManager alarmManager, + PowerManager.WakeLock wakeLock, Clock clock, NetworkStatsSettings settings, + NetworkStatsFactory factory, NetworkStatsObservers statsObservers, File systemDir, + File baseDir, @NonNull Dependencies deps) { mContext = Objects.requireNonNull(context, "missing Context"); - mNetworkManager = Objects.requireNonNull(networkManager, - "missing INetworkManagementService"); + mNetd = Objects.requireNonNull(netd, "missing Netd"); mAlarmManager = Objects.requireNonNull(alarmManager, "missing AlarmManager"); mClock = Objects.requireNonNull(clock, "missing Clock"); mSettings = Objects.requireNonNull(settings, "missing NetworkStatsSettings"); @@ -452,6 +465,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContentResolver = mContext.getContentResolver(); mContentObserver = mDeps.makeContentObserver(mHandler, mSettings, mNetworkStatsSubscriptionsMonitor); + mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext); } /** @@ -499,11 +513,33 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } }; } + + /** + * @see LocationPermissionChecker + */ + public LocationPermissionChecker makeLocationPermissionChecker(final Context context) { + return new LocationPermissionChecker(context); + } } - private void registerLocalService() { - LocalServices.addService(NetworkStatsManagerInternal.class, - new NetworkStatsManagerInternalImpl()); + /** + * Observer that watches for {@link INetdUnsolicitedEventListener} alerts. + */ + @VisibleForTesting + public class AlertObserver extends BaseNetdUnsolicitedEventListener { + @Override + public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) { + PermissionUtils.enforceNetworkStackPermission(mContext); + + if (LIMIT_GLOBAL_ALERT.equals(alertName)) { + // kick off background poll to collect network stats unless there is already + // such a call pending; UID stats are handled during normal polling interval. + if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) { + mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT, + mSettings.getPollDelay()); + } + } + } } public void systemReady() { @@ -551,9 +587,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContext.registerReceiver(mShutdownReceiver, shutdownFilter); try { - mNetworkManager.registerObserver(mAlertObserver); - } catch (RemoteException e) { - // ignored; service lives in system_server + mNetd.registerUnsolicitedEventListener(mAlertObserver); + } catch (RemoteException | ServiceSpecificException e) { + Log.wtf(TAG, "Error registering event listener :", e); } // schedule periodic pall alarm based on {@link NetworkStatsSettings#getPollInterval()}. @@ -641,13 +677,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Register for a global alert that is delivered through {@link INetworkManagementEventObserver} + * Register for a global alert that is delivered through {@link AlertObserver} * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has * been transferred. */ private void registerGlobalAlert() { try { - mNetworkManager.setGlobalAlert(mGlobalAlertBytes); + mNetd.bandwidthSetGlobalAlert(mGlobalAlertBytes); } catch (IllegalStateException e) { Log.w(TAG, "problem registering for global alert: " + e); } catch (RemoteException e) { @@ -683,12 +719,25 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return now - lastCallTime < POLL_RATE_LIMIT_MS; } - private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) { + private int restrictFlagsForCaller(int flags) { + // All non-privileged callers are not allowed to turn off POLL_ON_OPEN. + final boolean isPrivileged = PermissionUtils.checkAnyPermissionOf(mContext, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK); + if (!isPrivileged) { + flags |= NetworkStatsManager.FLAG_POLL_ON_OPEN; + } + // Non-system uids are rate limited for POLL_ON_OPEN. final int callingUid = Binder.getCallingUid(); - final int usedFlags = isRateLimitedForPoll(callingUid) + flags = isRateLimitedForPoll(callingUid) ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN) : flags; - if ((usedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN + return flags; + } + + private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) { + final int restrictedFlags = restrictFlagsForCaller(flags); + if ((restrictedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN | NetworkStatsManager.FLAG_POLL_FORCE)) != 0) { final long ident = Binder.clearCallingIdentity(); try { @@ -702,7 +751,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // for its lifetime; when caller closes only weak references remain. return new INetworkStatsSession.Stub() { - private final int mCallingUid = callingUid; + private final int mCallingUid = Binder.getCallingUid(); private final String mCallingPackage = callingPackage; private final @NetworkStatsAccess.Level int mAccessLevel = checkAccessLevel( callingPackage); @@ -736,26 +785,41 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getDeviceSummaryForNetwork( NetworkTemplate template, long start, long end) { - return internalGetSummaryForNetwork(template, usedFlags, start, end, mAccessLevel, - mCallingUid); + enforceTemplatePermissions(template, callingPackage); + return internalGetSummaryForNetwork(template, restrictedFlags, start, end, + mAccessLevel, mCallingUid); } @Override public NetworkStats getSummaryForNetwork( NetworkTemplate template, long start, long end) { - return internalGetSummaryForNetwork(template, usedFlags, start, end, mAccessLevel, - mCallingUid); + enforceTemplatePermissions(template, callingPackage); + return internalGetSummaryForNetwork(template, restrictedFlags, start, end, + mAccessLevel, mCallingUid); } + // TODO: Remove this after all callers are removed. @Override public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { - return internalGetHistoryForNetwork(template, usedFlags, fields, mAccessLevel, - mCallingUid); + enforceTemplatePermissions(template, callingPackage); + return internalGetHistoryForNetwork(template, restrictedFlags, fields, + mAccessLevel, mCallingUid, Long.MIN_VALUE, Long.MAX_VALUE); + } + + @Override + public NetworkStatsHistory getHistoryIntervalForNetwork(NetworkTemplate template, + int fields, long start, long end) { + enforceTemplatePermissions(template, callingPackage); + // TODO(b/200768422): Redact returned history if the template is location + // sensitive but the caller is not privileged. + return internalGetHistoryForNetwork(template, restrictedFlags, fields, + mAccessLevel, mCallingUid, start, end); } @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { + enforceTemplatePermissions(template, callingPackage); try { final NetworkStats stats = getUidComplete() .getSummary(template, start, end, mAccessLevel, mCallingUid); @@ -766,8 +830,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } return stats; } catch (NullPointerException e) { - // TODO: Track down and fix the cause of this crash and remove this catch block. - Log.wtf(TAG, "NullPointerException in getSummaryForAllUid", e); + throw e; + } + } + + @Override + public NetworkStats getTaggedSummaryForAllUid( + NetworkTemplate template, long start, long end) { + enforceTemplatePermissions(template, callingPackage); + try { + final NetworkStats tagStats = getUidTagComplete() + .getSummary(template, start, end, mAccessLevel, mCallingUid); + return tagStats; + } catch (NullPointerException e) { throw e; } } @@ -775,6 +850,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForUid( NetworkTemplate template, int uid, int set, int tag, int fields) { + enforceTemplatePermissions(template, callingPackage); // NOTE: We don't augment UID-level statistics if (tag == TAG_NONE) { return getUidComplete().getHistory(template, null, uid, set, tag, fields, @@ -789,6 +865,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public NetworkStatsHistory getHistoryIntervalForUid( NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) { + enforceTemplatePermissions(template, callingPackage); + // TODO(b/200768422): Redact returned history if the template is location + // sensitive but the caller is not privileged. // NOTE: We don't augment UID-level statistics if (tag == TAG_NONE) { return getUidComplete().getHistory(template, null, uid, set, tag, fields, @@ -810,6 +889,26 @@ public class NetworkStatsService extends INetworkStatsService.Stub { }; } + private void enforceTemplatePermissions(@NonNull NetworkTemplate template, + @NonNull String callingPackage) { + // For a template with wifi network keys, it is possible for a malicious + // client to track the user locations via querying data usage. Thus, enforce + // fine location permission check. + if (!template.getWifiNetworkKeys().isEmpty()) { + final boolean canAccessFineLocation = mLocationPermissionChecker + .checkCallersLocationPermission(callingPackage, + null /* featureId */, + Binder.getCallingUid(), + false /* coarseForTargetSdkLessThanQ */, + null /* message */); + if (!canAccessFineLocation) { + throw new SecurityException("Access fine location is required when querying" + + " with wifi network keys, make sure the app has the necessary" + + "permissions and the location toggle is on."); + } + } + } + private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) { return NetworkStatsAccess.checkAccessLevel( mContext, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage); @@ -827,7 +926,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (LOGD) Log.d(TAG, "Resolving plan for " + template); final long token = Binder.clearCallingIdentity(); try { - plan = LocalServices.getService(NetworkPolicyManagerInternal.class) + plan = mContext.getSystemService(NetworkPolicyManager.class) .getSubscriptionPlan(template); } finally { Binder.restoreCallingIdentity(token); @@ -846,7 +945,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // We've been using pure XT stats long enough that we no longer need to // splice DEV and XT together. final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL, - accessLevel, callingUid); + accessLevel, callingUid, start, end); final long now = System.currentTimeMillis(); final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null); @@ -863,14 +962,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * appropriate. */ private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, - int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid) { + int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid, + long start, long end) { // We've been using pure XT stats long enough that we no longer need to // splice DEV and XT together. final SubscriptionPlan augmentPlan = resolveSubscriptionPlan(template, flags); synchronized (mStatsLock) { return mXtStatsCached.getHistory(template, augmentPlan, - UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, Long.MAX_VALUE, - accessLevel, callingUid); + UID_ALL, SET_ALL, TAG_NONE, fields, start, end, accessLevel, callingUid); } } @@ -966,7 +1065,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @VisibleForTesting - void setUidForeground(int uid, boolean uidForeground) { + public void setUidForeground(int uid, boolean uidForeground) { + PermissionUtils.enforceNetworkStackPermission(mContext); synchronized (mStatsLock) { final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT; final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT); @@ -1002,7 +1102,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void forceUpdate() { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + PermissionUtils.enforceNetworkStackPermission(mContext); final long token = Binder.clearCallingIdentity(); try { @@ -1012,7 +1112,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - private void advisePersistThreshold(long thresholdBytes) { + /** Advise persistence threshold; may be overridden internally. */ + public void advisePersistThreshold(long thresholdBytes) { + PermissionUtils.enforceNetworkStackPermission(mContext); // clamp threshold into safe range mPersistThreshold = NetworkStatsUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES); @@ -1227,26 +1329,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { }; /** - * Observer that watches for {@link INetworkManagementService} alerts. - */ - private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() { - @Override - public void limitReached(String limitName, String iface) { - // only someone like NMS should be calling us - PermissionUtils.enforceNetworkStackPermission(mContext); - - if (LIMIT_GLOBAL_ALERT.equals(limitName)) { - // kick off background poll to collect network stats unless there is already - // such a call pending; UID stats are handled during normal polling interval. - if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) { - mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT, - mSettings.getPollDelay()); - } - } - } - }; - - /** * Handle collapsed RAT type changed event. */ @VisibleForTesting @@ -1611,7 +1693,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { xtTotal = mXtRecorder.getTotalSinceBootLocked(template); uidTotal = mUidRecorder.getTotalSinceBootLocked(template); - EventLogTags.writeNetstatsMobileSample( + EventLog.writeEvent(LOG_TAG_NETSTATS_MOBILE_SAMPLE, devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets, xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets, @@ -1623,7 +1705,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { xtTotal = mXtRecorder.getTotalSinceBootLocked(template); uidTotal = mUidRecorder.getTotalSinceBootLocked(template); - EventLogTags.writeNetstatsWifiSample( + EventLog.writeEvent(LOG_TAG_NETSTATS_WIFI_SAMPLE, devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets, xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets, @@ -1669,52 +1751,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { removeUidsLocked(CollectionUtils.toIntArray(uids)); } - private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal { - @Override - public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { - Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes"); - try { - return NetworkStatsService.this.getNetworkTotalBytes(template, start, end); - } finally { - Trace.traceEnd(TRACE_TAG_NETWORK); - } - } - - @Override - public NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) { - Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes"); - try { - return NetworkStatsService.this.getNetworkUidBytes(template, start, end); - } finally { - Trace.traceEnd(TRACE_TAG_NETWORK); - } - } - - @Override - public void setUidForeground(int uid, boolean uidForeground) { - NetworkStatsService.this.setUidForeground(uid, uidForeground); - } - - @Override - public void advisePersistThreshold(long thresholdBytes) { - NetworkStatsService.this.advisePersistThreshold(thresholdBytes); - } - - @Override - public void forceUpdate() { - NetworkStatsService.this.forceUpdate(); - } - - @Override - public void setStatsProviderWarningAndLimitAsync( - @NonNull String iface, long warning, long limit) { - if (LOGV) { - Log.v(TAG, "setStatsProviderWarningAndLimitAsync(" - + iface + "," + warning + "," + limit + ")"); - } - invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, - warning, limit)); + /** + * Set the warning and limit to all registered custom network stats providers. + * Note that invocation of any interface will be sent to all providers. + */ + public void setStatsProviderWarningAndLimitAsync( + @NonNull String iface, long warning, long limit) { + PermissionUtils.enforceNetworkStackPermission(mContext); + if (LOGV) { + Log.v(TAG, "setStatsProviderWarningAndLimitAsync(" + + iface + "," + warning + "," + limit + ")"); } + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, + warning, limit)); } @Override @@ -1957,13 +2006,29 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ // TODO: Remove this by implementing {@link NetworkStatsProvider} for non-offloaded // tethering stats. - private NetworkStats getNetworkStatsTethering(int how) throws RemoteException { + private @NonNull NetworkStats getNetworkStatsTethering(int how) throws RemoteException { + // We only need to return per-UID stats. Per-device stats are already counted by + // interface counters. + if (how != STATS_PER_UID) { + return new NetworkStats(SystemClock.elapsedRealtime(), 0); + } + + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); try { - return mNetworkManager.getNetworkStatsTethering(how); + final TetherStatsParcel[] tetherStatsParcels = mNetd.tetherGetStats(); + for (TetherStatsParcel tetherStats : tetherStatsParcels) { + try { + stats.combineValues(new NetworkStats.Entry(tetherStats.iface, UID_TETHERING, + SET_DEFAULT, TAG_NONE, tetherStats.rxBytes, tetherStats.rxPackets, + tetherStats.txBytes, tetherStats.txPackets, 0L)); + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalStateException("invalid tethering stats " + e); + } + } } catch (IllegalStateException e) { Log.wtf(TAG, "problem reading network stats", e); - return new NetworkStats(0L, 10); } + return stats; } // TODO: It is copied from ConnectivityService, consider refactor these check permission @@ -2002,10 +2067,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); Objects.requireNonNull(provider, "provider is null"); Objects.requireNonNull(tag, "tag is null"); + final NetworkPolicyManager netPolicyManager = mContext + .getSystemService(NetworkPolicyManager.class); try { NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl( tag, provider, mStatsProviderSem, mAlertObserver, - mStatsProviderCbList); + mStatsProviderCbList, netPolicyManager); mStatsProviderCbList.add(callback); Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid=" + getCallingUid() + "/" + getCallingPid()); @@ -2045,8 +2112,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @NonNull final INetworkStatsProvider mProvider; @NonNull private final Semaphore mSemaphore; - @NonNull final INetworkManagementEventObserver mAlertObserver; + @NonNull final AlertObserver mAlertObserver; @NonNull final CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList; + @NonNull final NetworkPolicyManager mNetworkPolicyManager; @NonNull private final Object mProviderStatsLock = new Object(); @@ -2059,8 +2127,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { NetworkStatsProviderCallbackImpl( @NonNull String tag, @NonNull INetworkStatsProvider provider, @NonNull Semaphore semaphore, - @NonNull INetworkManagementEventObserver alertObserver, - @NonNull CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> cbList) + @NonNull AlertObserver alertObserver, + @NonNull CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> cbList, + @NonNull NetworkPolicyManager networkPolicyManager) throws RemoteException { mTag = tag; mProvider = provider; @@ -2068,6 +2137,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mSemaphore = semaphore; mAlertObserver = alertObserver; mStatsProviderCbList = cbList; + mNetworkPolicyManager = networkPolicyManager; } @NonNull @@ -2107,15 +2177,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // This binder object can only have been obtained by a process that holds // NETWORK_STATS_PROVIDER. Thus, no additional permission check is required. BinderUtils.withCleanCallingIdentity(() -> - mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */)); + mAlertObserver.onQuotaLimitReached(LIMIT_GLOBAL_ALERT, null /* unused */)); } @Override public void notifyWarningOrLimitReached() { Log.d(TAG, mTag + ": notifyWarningOrLimitReached"); BinderUtils.withCleanCallingIdentity(() -> - LocalServices.getService(NetworkPolicyManagerInternal.class) - .onStatsProviderWarningOrLimitReached(mTag)); + mNetworkPolicyManager.notifyStatsProviderWarningOrLimitReached()); } @Override diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java index 7a239af98755..068074ae1b89 100644 --- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java @@ -44,6 +44,7 @@ import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** Basic fused location provider implementation. */ public class FusedLocationProvider extends LocationProviderBase { @@ -69,6 +70,12 @@ public class FusedLocationProvider extends LocationProviderBase { private final BroadcastReceiver mUserChangeReceiver; @GuardedBy("mLock") + boolean mGpsPresent; + + @GuardedBy("mLock") + boolean mNlpPresent; + + @GuardedBy("mLock") private ProviderRequest mRequest; @GuardedBy("mLock") @@ -119,19 +126,28 @@ public class FusedLocationProvider extends LocationProviderBase { @Override public void onFlush(OnFlushCompleteCallback callback) { - OnFlushCompleteCallback wrapper = new OnFlushCompleteCallback() { - private int mFlushCount = 2; + synchronized (mLock) { + AtomicInteger flushCount = new AtomicInteger(0); + if (mGpsPresent) { + flushCount.incrementAndGet(); + } + if (mNlpPresent) { + flushCount.incrementAndGet(); + } - @Override - public void onFlushComplete() { - if (--mFlushCount == 0) { + OnFlushCompleteCallback wrapper = () -> { + if (flushCount.decrementAndGet() == 0) { callback.onFlushComplete(); } - } - }; + }; - mGpsListener.flush(wrapper); - mNetworkListener.flush(wrapper); + if (mGpsPresent) { + mGpsListener.flush(wrapper); + } + if (mNlpPresent) { + mNetworkListener.flush(wrapper); + } + } } @Override @@ -139,9 +155,19 @@ public class FusedLocationProvider extends LocationProviderBase { @GuardedBy("mLock") private void updateRequirementsLocked() { - long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getIntervalMillis() - : INTERVAL_DISABLED; - long networkInterval = mRequest.getIntervalMillis(); + // it's possible there might be race conditions on device start where a provider doesn't + // appear to be present yet, but once a provider is present it shouldn't go away. + if (!mGpsPresent) { + mGpsPresent = mLocationManager.hasProvider(GPS_PROVIDER); + } + if (!mNlpPresent) { + mNlpPresent = mLocationManager.hasProvider(NETWORK_PROVIDER); + } + + long gpsInterval = + mGpsPresent && (!mNlpPresent || mRequest.getQuality() < QUALITY_LOW_POWER) + ? mRequest.getIntervalMillis() : INTERVAL_DISABLED; + long networkInterval = mNlpPresent ? mRequest.getIntervalMillis() : INTERVAL_DISABLED; mGpsListener.resetProviderRequest(gpsInterval); mNetworkListener.resetProviderRequest(networkInterval); diff --git a/packages/PrintSpooler/Android.bp b/packages/PrintSpooler/Android.bp index 772c69fe079b..6af3c6624f62 100644 --- a/packages/PrintSpooler/Android.bp +++ b/packages/PrintSpooler/Android.bp @@ -34,18 +34,23 @@ license { android_app { name: "PrintSpooler", defaults: ["platform_app_defaults"], + resource_dirs: [], + platform_apis: true, + jni_libs: ["libprintspooler_jni"], + static_libs: [ + "PrintSpoolerLib", + ], +} +android_library { + name: "PrintSpoolerLib", resource_dirs: ["res"], - srcs: [ "src/**/*.java", "src/com/android/printspooler/renderer/IPdfRenderer.aidl", "src/com/android/printspooler/renderer/IPdfEditor.aidl", ], - platform_apis: true, - - jni_libs: ["libprintspooler_jni"], static_libs: [ "android-support-v7-recyclerview", "android-support-compat", @@ -55,4 +60,5 @@ android_app { "android-support-fragment", "android-support-annotations", ], + manifest: "AndroidManifest.xml", } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java index cf73aacb9b55..0c4cb8e4e8f6 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java @@ -314,16 +314,15 @@ public final class SelectPrinterActivity extends Activity implements @Override public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.string.print_select_printer: { - PrinterInfo printer = item.getIntent().getParcelableExtra(EXTRA_PRINTER); - onPrinterSelected(printer); - } return true; - - case R.string.print_forget_printer: { - PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); - mPrinterRegistry.forgetFavoritePrinter(printerId); - } return true; + final int itemId = item.getItemId(); + if (itemId == R.string.print_select_printer) { + PrinterInfo printer = item.getIntent().getParcelableExtra(EXTRA_PRINTER); + onPrinterSelected(printer); + return true; + } else if (itemId == R.string.print_forget_printer) { + PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); + mPrinterRegistry.forgetFavoritePrinter(printerId); + return true; } return false; } diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index b266df53b18e..fcf2282160a7 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -49,6 +49,7 @@ android_library { "SettingsLibTwoTargetPreference", "SettingsLibSettingsTransition", "SettingsLibActivityEmbedding", + "SettingsLibButtonPreference", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/core/tests/bluetoothtests/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp index 68416dd65466..39f804fa9ae5 100644 --- a/core/tests/bluetoothtests/Android.bp +++ b/packages/SettingsLib/ButtonPreference/Android.bp @@ -7,18 +7,17 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "BluetoothTests", - // Include all test java files. +android_library { + name: "SettingsLibButtonPreference", + srcs: ["src/**/*.java"], - libs: [ - "android.test.runner", - "android.test.base", - ], + resource_dirs: ["res"], + static_libs: [ - "junit", - "modules-utils-bytesmatcher", + "androidx.preference_preference", + "SettingsLibSettingsTheme", ], - platform_apis: true, - certificate: "platform", + + sdk_version: "system_current", + min_sdk_version: "21", } diff --git a/packages/SettingsLib/ButtonPreference/AndroidManifest.xml b/packages/SettingsLib/ButtonPreference/AndroidManifest.xml new file mode 100644 index 000000000000..2d35c331cd82 --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml new file mode 100644 index 000000000000..51ca4ac04189 --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<!-- Used for the text of a bordered colored button. --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?android:attr/disabledAlpha" + android:color="?android:attr/colorButtonNormal" /> + <item android:color="?android:attr/colorAccent" /> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml new file mode 100644 index 000000000000..8dca4dbcd111 --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<!-- Used for the text of a bordered colored button. --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?android:attr/disabledAlpha" + android:color="?android:attr/textColorPrimary" /> + <item android:color="?android:attr/textColorPrimaryInverse" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml index 70f553b89657..1e930eacffba 100644 --- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml +++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project +<!-- + 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. @@ -13,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" - android:propertyName="alpha" - android:valueTo="0" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="100" /> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:alpha="@dimen/settingslib_highlight_alpha_material_dark" + android:color="@android:color/white" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml index 70f553b89657..378fc166af7f 100644 --- a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml +++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project +<!-- + 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. @@ -13,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" - android:propertyName="alpha" - android:valueTo="0" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="100" /> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:alpha="@dimen/settingslib_highlight_alpha_material_light" + android:color="@android:color/black" /> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml new file mode 100644 index 000000000000..bb0597d1f74f --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetLeft="4dp" + android:insetTop="6dp" + android:insetRight="4dp" + android:insetBottom="6dp"> + <ripple android:color="?attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle" + android:tint="@color/settingslib_btn_colored_background_material"> + <corners android:radius="2dp" /> + <solid android:color="@android:color/white" /> + <padding android:left="8dp" + android:top="4dp" + android:right="8dp" + android:bottom="4dp" /> + </shape> + </item> + </ripple> +</inset>
\ No newline at end of file diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml new file mode 100644 index 000000000000..1ff09901ffaf --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + + <Button + android:id="@+id/settingslib_button" + android:drawablePadding="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:layout_marginStart="-4dp" + style="@style/SettingsLibButtonStyle" /> + +</LinearLayout> diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml index 29d9b257cc59..6be7b93a847e 100644 --- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml +++ b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project +<!-- + 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. @@ -13,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" - android:propertyName="alpha" - android:valueTo="1" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="100" /> + +<resources> + <!-- Material inverse ripple color, useful for inverted backgrounds. --> + <color name="settingslib_button_ripple">@color/settingslib_ripple_material_light</color> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml new file mode 100644 index 000000000000..202645f0584d --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<resources> + + <style name="SettingsLibButtonStyle" parent="android:Widget.Material.Button.Colored"> + <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item> + <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> + <item name="android:textSize">16sp</item> + <item name="android:textColor">@color/settingslib_btn_colored_text_material</item> + </style> +</resources> diff --git a/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml new file mode 100644 index 000000000000..d8c6ac3fa471 --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<resources> + + <style name="SettingsLibButtonStyle" parent="android:Widget.DeviceDefault.Button.Colored"> + <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item> + <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> + <item name="android:textSize">16sp</item> + </style> +</resources> diff --git a/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml new file mode 100644 index 000000000000..12dcbbf1a08a --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<resources> + + <style name="SettingsLibRoundedCornerThemeOverlay"> + <item name="android:buttonCornerRadius">24dp</item> + <item name="android:paddingStart">16dp</item> + <item name="android:paddingEnd">16dp</item> + <item name="android:colorControlHighlight">@color/settingslib_button_ripple</item> + </style> +</resources> diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml index 29d9b257cc59..9a4312aa8b36 100644 --- a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml +++ b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project +<!-- + 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. @@ -13,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. --> -<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" - android:propertyName="alpha" - android:valueTo="1" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="100" /> + +<resources> + <declare-styleable name="ButtonPreference"> + <attr name="android:gravity" /> + </declare-styleable> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/ButtonPreference/res/values/colors.xml b/packages/SettingsLib/ButtonPreference/res/values/colors.xml new file mode 100644 index 000000000000..45baeebe6feb --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/values/colors.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<resources> + <!-- Material inverse ripple color, useful for inverted backgrounds. --> + <color name="settingslib_button_ripple">@color/settingslib_ripple_material_dark</color> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/ButtonPreference/res/values/dimens.xml b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml new file mode 100644 index 000000000000..3d7831e80cee --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<resources> + <item name="settingslib_highlight_alpha_material_light" format="float" type="dimen">0.10</item> + <item name="settingslib_highlight_alpha_material_dark" format="float" type="dimen">0.10</item> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/ButtonPreference/res/values/styles.xml b/packages/SettingsLib/ButtonPreference/res/values/styles.xml new file mode 100644 index 000000000000..3963732f7ae4 --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/res/values/styles.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<resources> + + <style name="SettingsLibRoundedCornerThemeOverlay"> + <item name="android:paddingStart">16dp</item> + <item name="android:paddingEnd">16dp</item> + <item name="android:colorControlHighlight">@color/settingslib_button_ripple</item> + </style> + + <style name="SettingsLibButtonStyle" parent="android:Widget.Material.Button"> + <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item> + <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> + <item name="android:textSize">16sp</item> + <item name="android:textColor">@color/settingslib_btn_colored_text_material</item> + <item name="android:background">@drawable/settingslib_btn_colored_material</item> + </style> +</resources> diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java new file mode 100644 index 000000000000..56d296709914 --- /dev/null +++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java @@ -0,0 +1,197 @@ +/* + * 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.settingslib.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import androidx.annotation.GravityInt; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +/** + * A preference handled a button + */ +public class ButtonPreference extends Preference { + + private static final int ICON_SIZE = 24; + + private View.OnClickListener mClickListener; + private Button mButton; + private CharSequence mTitle; + private Drawable mIcon; + @GravityInt + private int mGravity; + + /** + * Constructs a new LayoutPreference with the given context's theme, the supplied + * attribute set, and default style attribute. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default + * values for the view. Can be 0 to not look for + * defaults. + */ + public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + /** + * Constructs a new LayoutPreference with the given context's theme and the supplied + * attribute set. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public ButtonPreference(Context context, AttributeSet attrs) { + this(context, attrs, 0 /* defStyleAttr */); + } + + /** + * Constructs a new LayoutPreference with the given context's theme and a customized view. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + */ + public ButtonPreference(Context context) { + this(context, null); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + setLayoutResource(R.layout.settingslib_button_layout); + + if (attrs != null) { + TypedArray a = context.obtainStyledAttributes(attrs, + androidx.preference.R.styleable.Preference, defStyleAttr, + 0 /*defStyleRes*/); + mTitle = a.getText( + androidx.preference.R.styleable.Preference_android_title); + mIcon = a.getDrawable( + androidx.preference.R.styleable.Preference_android_icon); + a.recycle(); + + a = context.obtainStyledAttributes(attrs, + R.styleable.ButtonPreference, defStyleAttr, + 0 /*defStyleRes*/); + mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START); + a.recycle(); + } + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + mButton = (Button) holder.findViewById(R.id.settingslib_button); + setTitle(mTitle); + setIcon(mIcon); + setGravity(mGravity); + setOnClickListener(mClickListener); + + if (mButton != null) { + final boolean selectable = isSelectable(); + mButton.setFocusable(selectable); + mButton.setClickable(selectable); + + mButton.setEnabled(isEnabled()); + } + + holder.setDividerAllowedAbove(false); + holder.setDividerAllowedBelow(false); + } + + @Override + public void setTitle(CharSequence title) { + mTitle = title; + if (mButton != null) { + mButton.setText(title); + } + } + + @Override + public void setIcon(Drawable icon) { + mIcon = icon; + if (mButton == null || icon == null) { + return; + } + //get pixel from dp + int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE, + getContext().getResources().getDisplayMetrics()); + icon.setBounds(0, 0, size, size); + + //set drawableStart + mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */, null/* end */, + null/* bottom */); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (mButton != null) { + mButton.setEnabled(enabled); + } + } + + /** + * Return Button + */ + public Button getButton() { + return mButton; + } + + + /** + * Set a listener for button click + */ + public void setOnClickListener(View.OnClickListener listener) { + mClickListener = listener; + if (mButton != null) { + mButton.setOnClickListener(listener); + } + } + + /** + * Set the gravity of button + * + * @param gravity The {@link Gravity} supported CENTER_HORIZONTAL + * and the default value is START + */ + public void setGravity(@GravityInt int gravity) { + if (gravity == Gravity.CENTER_HORIZONTAL || gravity == Gravity.CENTER_VERTICAL + || gravity == Gravity.CENTER) { + mGravity = Gravity.CENTER_HORIZONTAL; + } else { + mGravity = Gravity.START; + } + + if (mButton != null) { + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mButton.getLayoutParams(); + lp.gravity = mGravity; + mButton.setLayoutParams(lp); + } + } +} diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml index c20690342c19..3f2b8ac7609d 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml @@ -43,4 +43,8 @@ <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color> <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color> + + <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_200</color> + + <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml index 04010985fe74..ec3c336eba46 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml @@ -65,4 +65,8 @@ <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color> <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color> + + <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color> + + <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml index 1c33f1a57ea5..11546c8ed3d9 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml @@ -20,4 +20,9 @@ <dimen name="app_icon_min_width">52dp</dimen> <dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen> <dimen name="settingslib_dialogCornerRadius">28dp</dimen> + + <!-- Left padding of the preference --> + <dimen name="settingslib_listPreferredItemPaddingStart">24dp</dimen> + <!-- Right padding of the preference --> + <dimen name="settingslib_listPreferredItemPaddingEnd">24dp</dimen> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml index 58006369988e..8e7226b0eb53 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml @@ -17,13 +17,19 @@ <resources> <style name="TextAppearance.PreferenceTitle.SettingsLib" parent="@android:style/TextAppearance.Material.Subhead"> + <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item> <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> <item name="android:textSize">20sp</item> </style> + <style name="TextAppearance.PreferenceSummary.SettingsLib" + parent="@android:style/TextAppearance.DeviceDefault.Small"> + <item name="android:textColor">@color/settingslib_text_color_secondary_device_default</item> + </style> + <style name="TextAppearance.CategoryTitle.SettingsLib" parent="@android:style/TextAppearance.DeviceDefault.Medium"> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">@color/settingslib_text_color_preference_category_title</item> <item name="android:textSize">14sp</item> </style> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml index 6bf288b74d5a..7bf75bcc8a53 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml @@ -19,8 +19,8 @@ <!-- Only using in Settings application --> <style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings" > <item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle.SettingsLib</item> - <item name="android:listPreferredItemPaddingStart">24dp</item> - <item name="android:listPreferredItemPaddingLeft">24dp</item> + <item name="android:listPreferredItemPaddingStart">@dimen/settingslib_listPreferredItemPaddingStart</item> + <item name="android:listPreferredItemPaddingLeft">@dimen/settingslib_listPreferredItemPaddingStart</item> <item name="android:listPreferredItemPaddingEnd">16dp</item> <item name="android:listPreferredItemPaddingRight">16dp</item> <item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item> diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml new file mode 100644 index 000000000000..e73dcc0cc559 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <bool name="settingslib_config_icon_space_reserved">true</bool> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml index 18af1f9c15d0..f7e01444f4d4 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml @@ -21,4 +21,11 @@ <dimen name="app_icon_min_width">56dp</dimen> <dimen name="two_target_min_width">72dp</dimen> <dimen name="settingslib_dialogCornerRadius">8dp</dimen> + + <!-- Left padding of the preference --> + <dimen name="settingslib_listPreferredItemPaddingStart">16dp</dimen> + <!-- Right padding of the preference --> + <dimen name="settingslib_listPreferredItemPaddingEnd">16dp</dimen> + <!-- Icon size of the preference --> + <dimen name="settingslib_preferenceIconSize">24dp</dimen> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml new file mode 100644 index 000000000000..aaab0f041fe3 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> +<resources> + <style name="TextAppearance.PreferenceTitle.SettingsLib" + parent="@android:style/TextAppearance.Material.Subhead"> + </style> + + <style name="TextAppearance.PreferenceSummary.SettingsLib" + parent="@style/PreferenceSummaryTextStyle"> + </style> + + <style name="TextAppearance.CategoryTitle.SettingsLib" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + </style> +</resources> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 4cdcca7dd389..5222d8aab538 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -250,7 +250,7 @@ <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Konektatuta daudenak"</string> <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Gailuaren xehetasunak"</string> <string name="adb_device_forget" msgid="193072400783068417">"Ahaztu"</string> - <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Gailuaren erreferentzia-gako digitala: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string> + <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Gailuaren aztarna digitala: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string> <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Ezin izan da konektatu"</string> <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Ziurtatu <xliff:g id="DEVICE_NAME">%1$s</xliff:g> sare berera konektatuta dagoela"</string> <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Parekatu gailuarekin"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 9490ede09ff8..4103a9fd1d5e 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -209,7 +209,7 @@ <string name="tts_status_checking" msgid="8026559918948285013">"Tarkistetaan…"</string> <string name="tts_engine_settings_title" msgid="7849477533103566291">"Asetukset: <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string> <string name="tts_engine_settings_button" msgid="477155276199968948">"Käynnistä moottorin asetukset"</string> - <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Ensisijainen kone"</string> + <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Ensisijainen moottori"</string> <string name="tts_general_section_title" msgid="8919671529502364567">"Yleiset"</string> <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Palauta äänenkorkeus"</string> <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Palauta tekstin lukemisen oletusäänenkorkeus"</string> @@ -220,8 +220,8 @@ <item msgid="1158955023692670059">"Nopea"</item> <item msgid="5664310435707146591">"Nopeampi"</item> <item msgid="5491266922147715962">"Hyvin nopea"</item> - <item msgid="7659240015901486196">"Nopea"</item> - <item msgid="7147051179282410945">"Erittäin nopea"</item> + <item msgid="7659240015901486196">"Vauhdikas"</item> + <item msgid="7147051179282410945">"Erittäin vauhdikas"</item> <item msgid="581904787661470707">"Nopein"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 47b0744497ff..f9ac01db5454 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1271,10 +1271,13 @@ <string name="wifi_status_mac_randomized">MAC is randomized</string> <!-- Summary to show how many devices are connected in wifi hotspot [CHAR LIMIT=NONE] --> - <plurals name="wifi_tether_connected_summary"> - <item quantity="one">%1$d device connected</item> - <item quantity="other">%1$d devices connected</item> - </plurals> + <string name="wifi_tether_connected_summary"> + {count, plural, + =0 {0 device connected} + =1 {1 device connected} + other {# devices connected} + } + </string> <!-- Content description of zen mode time condition plus button (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_manual_zen_more_time">More time.</string> diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java index de45ea536e27..d3fe4a7fcb9f 100644 --- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java +++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java @@ -47,7 +47,7 @@ import javax.tools.Diagnostic.Kind; * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources} * subclasses. */ -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_9) @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"}) public class IndexableProcessor extends AbstractProcessor { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 8ac4e38677fa..2c862e685035 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -176,6 +176,12 @@ public class BluetoothEventManager { } @VisibleForTesting + void registerProfileIntentReceiverForTest() { + mContext.registerReceiverAsUser(mProfileBroadcastReceiver, mUserHandle, + mProfileIntentFilter, null, mReceiverHandler); + } + + @VisibleForTesting void addProfileHandler(String action, Handler handler) { mHandlerMap.put(action, handler); mProfileIntentFilter.addAction(action); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 9dd329ed7cd7..b11bbdec191f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -138,13 +138,6 @@ public class HeadsetProfile implements LocalBluetoothProfile { return mService.getActiveDevice(); } - public boolean isAudioOn() { - if (mService == null) { - return false; - } - return mService.isAudioOn(); - } - public int getAudioState(BluetoothDevice device) { if (mService == null) { return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index 6b9b75011f5b..aed2ec10e924 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -52,6 +52,7 @@ import java.util.List; public class DreamBackend { private static final String TAG = "DreamBackend"; private static final boolean DEBUG = false; + private final Drawable mDreamPreviewDefault; public static class DreamInfo { public CharSequence caption; @@ -111,6 +112,8 @@ public class DreamBackend { .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); mDreamsActivatedOnDockByDefault = mContext.getResources() .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); + mDreamPreviewDefault = mContext.getResources().getDrawable( + com.android.internal.R.drawable.default_dream_preview); } public List<DreamInfo> getDreamInfos() { @@ -133,11 +136,11 @@ public class DreamBackend { dreamInfo.isActive = dreamInfo.componentName.equals(activeDream); final DreamMetadata dreamMetadata = getDreamMetadata(pm, resolveInfo); - if (dreamMetadata != null) { - dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity; - dreamInfo.previewImage = dreamMetadata.mPreviewImage; + dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity; + dreamInfo.previewImage = dreamMetadata.mPreviewImage; + if (dreamInfo.previewImage == null) { + dreamInfo.previewImage = mDreamPreviewDefault; } - dreamInfos.add(dreamInfo); } Collections.sort(dreamInfos, mComparator); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index 56454e975370..4ab6542d567a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -22,6 +22,7 @@ import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNe import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.icu.text.MessageFormat; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; @@ -33,6 +34,8 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; +import java.util.HashMap; +import java.util.Locale; import java.util.Map; public class WifiUtils { @@ -333,4 +336,20 @@ public class WifiUtils { intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle); return intent; } + + /** + * Returns the string of Wi-Fi tethering summary for connected devices. + * + * @param context The application context + * @param connectedDevices The count of connected devices + */ + public static String getWifiTetherSummaryForConnectedDevices(Context context, + int connectedDevices) { + MessageFormat msgFormat = new MessageFormat( + context.getResources().getString(R.string.wifi_tether_connected_summary), + Locale.getDefault()); + Map<String, Object> arguments = new HashMap<>(); + arguments.put("count", connectedDevices); + return msgFormat.format(arguments); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 63153f89ad99..1edb7d18bc88 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -64,6 +64,7 @@ import com.android.settingslib.testutils.shadow.ShadowUserManager; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -435,6 +436,7 @@ public class ApplicationsStateRoboTest { } @Test + @Ignore public void noAppRemoved_noWorkprofile_doResumeIfNeededLocked_shouldNotClearEntries() throws RemoteException { // scenario: only owner user @@ -628,6 +630,7 @@ public class ApplicationsStateRoboTest { } @Test + @Ignore public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries() throws RemoteException { if (!MU_ENABLED) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java index 30182c476855..f5ce6647e531 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java @@ -55,15 +55,6 @@ public class HeadsetProfileTest { } @Test - public void bluetoothProfile_shouldReturnTheAudioStatusFromBlueToothHeadsetService() { - when(mService.isAudioOn()).thenReturn(true); - assertThat(mProfile.isAudioOn()).isTrue(); - - when(mService.isAudioOn()).thenReturn(false); - assertThat(mProfile.isAudioOn()).isFalse(); - } - - @Test public void testHeadsetProfile_shouldReturnAudioState() { when(mService.getAudioState(mBluetoothDevice)). thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java index 4f8fa2fdb96e..09540d1373ee 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java @@ -40,6 +40,7 @@ import android.os.ParcelUuid; import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -84,6 +85,7 @@ public class LocalBluetoothProfileManagerTest { when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice); mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter, mDeviceManager, mEventManager); + mEventManager.registerProfileIntentReceiverForTest(); } /** @@ -150,6 +152,7 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test + @Ignore public void stateChangedHandler_receiveA2dpConnectionStateChanged_shouldDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.A2DP})); @@ -171,6 +174,7 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test + @Ignore public void stateChangedHandler_receiveHeadsetConnectionStateChanged_shouldDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.HEADSET})); @@ -192,6 +196,7 @@ public class LocalBluetoothProfileManagerTest { * CachedBluetoothDeviceManager method */ @Test + @Ignore public void stateChangedHandler_receiveHAPConnectionStateChanged_shouldDispatchDeviceManager() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.HEARING_AID})); @@ -214,6 +219,7 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test + @Ignore public void stateChangedHandler_receivePanConnectionStateChanged_shouldNotDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.PAN})); @@ -255,6 +261,7 @@ public class LocalBluetoothProfileManagerTest { * handler and refresh CachedBluetoothDevice */ @Test + @Ignore public void stateChangedHandler_receivePanConnectionStateChangedWithProfile_shouldRefresh() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.PAN})); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java index 4444e6369b67..c1cc3ae9778a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java @@ -33,6 +33,7 @@ import android.os.Handler; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -54,6 +55,7 @@ public class ConnectivityPreferenceControllerTest { } @Test + @Ignore public void testBroadcastReceiver() { final AbstractConnectivityPreferenceController preferenceController = spy(new ConcreteConnectivityPreferenceController(mContext, mLifecycle)); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java index 8ec577e8a764..aa11952397b4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java @@ -37,6 +37,7 @@ import android.provider.Settings; import com.android.settingslib.RestrictedLockUtils; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -66,6 +67,7 @@ public class BiometricActionDisabledByAdminControllerTest { } @Test + @Ignore public void buttonClicked() { ComponentName componentName = mock(ComponentName.class); RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils.EnforcedAdmin( diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 2d53831a30e7..aa0ef91be46b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -46,6 +46,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.testutils.shadow.ShadowRouter2Manager; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -731,6 +732,7 @@ public class InfoMediaManagerTest { } @Test + @Ignore public void shouldDisableMediaOutput_infosSizeEqual1_returnsTrue() { final MediaRoute2Info info = mock(MediaRoute2Info.class); final List<MediaRoute2Info> infos = new ArrayList<>(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java new file mode 100644 index 000000000000..625b214544f1 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java @@ -0,0 +1,166 @@ +/* + * 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.settingslib.widget; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.Shadows.shadowOf; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.Gravity; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import androidx.preference.PreferenceViewHolder; +import androidx.preference.R; +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowDrawable; + +@RunWith(RobolectricTestRunner.class) +public class ButtonPreferenceTest { + + private final Context mContext = ApplicationProvider.getApplicationContext(); + private ButtonPreference mPreference; + private PreferenceViewHolder mHolder; + + private boolean mClickListenerCalled; + private final View.OnClickListener mClickListener = v -> mClickListenerCalled = true; + + @Before + public void setUp() { + mClickListenerCalled = false; + mPreference = new ButtonPreference(mContext); + setUpViewHolder(); + } + + @Test + public void onBindViewHolder_whenTitleSet_shouldSetButtonText() { + final String testTitle = "Test title"; + mPreference.setTitle(testTitle); + + mPreference.onBindViewHolder(mHolder); + + final Button button = mPreference.getButton(); + assertThat(button.getText().toString()).isEqualTo(testTitle); + } + + @Test + public void onBindViewHolder_whenIconSet_shouldSetIcon() { + mPreference.setIcon(R.drawable.settingslib_ic_cross); + + mPreference.onBindViewHolder(mHolder); + + final Button button = mPreference.getButton(); + final Drawable icon = button.getCompoundDrawablesRelative()[0]; + final ShadowDrawable shadowDrawable = shadowOf(icon); + assertThat(shadowDrawable.getCreatedFromResId()).isEqualTo(R.drawable.settingslib_ic_cross); + } + + @Test + public void onBindViewHolder_setEnable_shouldSetButtonEnabled() { + mPreference.setEnabled(true); + + mPreference.onBindViewHolder(mHolder); + + final Button button = mPreference.getButton(); + assertThat(button.isEnabled()).isTrue(); + } + + @Test + public void onBindViewHolder_setDisable_shouldSetButtonDisabled() { + mPreference.setEnabled(false); + + mPreference.onBindViewHolder(mHolder); + + final Button button = mPreference.getButton(); + assertThat(button.isEnabled()).isFalse(); + } + + @Test + public void onBindViewHolder_default_shouldReturnButtonGravityStart() { + mPreference.onBindViewHolder(mHolder); + + final Button button = mPreference.getButton(); + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams(); + assertThat(lp.gravity).isEqualTo(Gravity.START); + } + + @Test + public void onBindViewHolder_setGravityStart_shouldReturnButtonGravityStart() { + mPreference.setGravity(Gravity.START); + + mPreference.onBindViewHolder(mHolder); + + final Button button = mPreference.getButton(); + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams(); + assertThat(lp.gravity).isEqualTo(Gravity.START); + } + + @Test + public void onBindViewHolder_setGravityCenter_shouldReturnButtonGravityCenterHorizontal() { + mPreference.setGravity(Gravity.CENTER_HORIZONTAL); + + mPreference.onBindViewHolder(mHolder); + + final Button button = mPreference.getButton(); + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams(); + assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL); + + mPreference.setGravity(Gravity.CENTER_VERTICAL); + mPreference.onBindViewHolder(mHolder); + assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL); + + mPreference.setGravity(Gravity.CENTER); + mPreference.onBindViewHolder(mHolder); + assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL); + } + + @Test + public void onBindViewHolder_setUnsupportedGravity_shouldReturnButtonGravityStart() { + mPreference.setGravity(Gravity.END); + + mPreference.onBindViewHolder(mHolder); + + final Button button = mPreference.getButton(); + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams(); + assertThat(lp.gravity).isEqualTo(Gravity.START); + } + + @Test + public void setButtonOnClickListener_setsClickListener() { + mPreference.setOnClickListener(mClickListener); + + mPreference.onBindViewHolder(mHolder); + final Button button = mPreference.getButton(); + button.callOnClick(); + + assertThat(mClickListenerCalled).isTrue(); + } + + private void setUpViewHolder() { + final View rootView = + View.inflate(mContext, mPreference.getLayoutResource(), null /* parent */); + mHolder = PreferenceViewHolder.createInstanceForTests(rootView); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java index 2bd20a933c58..a31f24ae5f77 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java @@ -26,6 +26,7 @@ import android.content.Context; import android.graphics.drawable.ColorDrawable; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -58,6 +59,7 @@ public class AccessPointPreferenceTest { } @Test + @Ignore public void refresh_openNetwork_updateContentDescription() { final String ssid = "ssid"; final String summary = "connected"; @@ -88,6 +90,7 @@ public class AccessPointPreferenceTest { } @Test + @Ignore public void refresh_setTitle_shouldUseSsidString() { final String ssid = "ssid"; final String summary = "connected"; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java index 7c2b904fc576..5d7f8ba52d2a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java @@ -44,6 +44,7 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.R; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -77,6 +78,7 @@ public class WifiUtilsTest { } @Test + @Ignore public void testVerboseSummaryString_showsScanResultSpeedLabel() { WifiTracker.sVerboseLogging = true; diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 231252502937..f20057d9f800 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -296,6 +296,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.LOCATION_SHOW_SYSTEM_OPS, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DEVICE_STATE_ROTATION_LOCK, value -> { if (TextUtils.isEmpty(value)) { return true; diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 0c70821527dd..c16cae0412fe 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -341,6 +341,9 @@ <uses-permission android:name="android.permission.SET_WALLPAPER" /> <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" /> + <!-- Permission needed to test wallpaper dimming --> + <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" /> + <!-- Permission required to test ContentResolver caching. --> <uses-permission android:name="android.permission.CACHE_CONTENT" /> @@ -350,6 +353,9 @@ <!-- Permission required for CTS test - CrossProfileAppsHostSideTest --> <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/> + <!-- Permission required for CTS test - CrossProfileAppsHostSideTest --> + <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"/> + <!-- permissions required for CTS test - PhoneStateListenerTest --> <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" /> @@ -617,6 +623,9 @@ <!-- Permission required for CTS test - Notification test suite --> <uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" /> + <!-- Permission required for CTS test - CaptioningManagerTest --> + <uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index e907efbacb08..f35f5dd0c3ae 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -150,12 +150,12 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" /> <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" /> - <!-- Communal mode --> - <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" /> - <!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked --> <uses-permission android:name="android.permission.SET_WALLPAPER"/> + <!-- Needed for WallpaperManager.getWallpaperDimAmount in StatusBar.updateTheme --> + <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/> + <!-- Wifi Display --> <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" /> @@ -299,6 +299,9 @@ <uses-permission android:name="android.permission.BIND_APPWIDGET" /> + <!-- For clipboard overlay --> + <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> + <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> @@ -348,6 +351,7 @@ <!-- started from PhoneWindowManager TODO: Should have an android:permission attribute --> <service android:name=".screenshot.TakeScreenshotService" + android:permission="com.android.systemui.permission.SELF" android:process=":screenshot" android:exported="false" /> @@ -760,6 +764,12 @@ </intent-filter> </activity> + <activity android:name=".clipboardoverlay.EditTextActivity" + android:theme="@style/EditTextActivity" + android:exported="false" + android:excludeFromRecents="true" + /> + <activity android:name=".controls.management.ControlsProviderSelectorActivity" android:label="@string/controls_providers_title" android:theme="@style/Theme.ControlsManagement" @@ -845,6 +855,12 @@ android:singleUser="true" android:permission="android.permission.BIND_DREAM_SERVICE" /> + <!-- Service for external clients to do media transfer --> + <!-- TODO(b/203800643): Export and guard with a permission. --> + <service + android:name=".media.taptotransfer.sender.MediaTttSenderService" + /> + <receiver android:name=".tuner.TunerService$ClearReceiver" android:exported="false"> diff --git a/packages/SystemUI/res-keyguard/values/bools.xml b/packages/SystemUI/res-keyguard/values/bools.xml index 2b83787172d3..c5bf4ce48188 100644 --- a/packages/SystemUI/res-keyguard/values/bools.xml +++ b/packages/SystemUI/res-keyguard/values/bools.xml @@ -17,4 +17,5 @@ <resources> <bool name="kg_show_ime_at_screen_on">true</bool> <bool name="kg_use_all_caps">true</bool> + <bool name="flag_active_unlock">false</bool> </resources> diff --git a/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml b/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml new file mode 100644 index 000000000000..61c32b223a72 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml @@ -0,0 +1,27 @@ +<!-- + Copyright (C) 2020 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 + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M4,6h18L22,4L4,4c-1.1,0 -2,0.9 -2,2v11L0,17v3h14v-3L4,17L4,6zM23,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L24,9c0,-0.55 -0.45,-1 -1,-1zM22,17h-4v-7h4v7z"/> +</vector> + diff --git a/packages/SystemUI/res/layout/clipboard_content_preview.xml b/packages/SystemUI/res/layout/clipboard_content_preview.xml new file mode 100644 index 000000000000..7317a9480001 --- /dev/null +++ b/packages/SystemUI/res/layout/clipboard_content_preview.xml @@ -0,0 +1,60 @@ +<?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. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/preview_border" + android:elevation="9dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/screenshot_offset_x" + android:layout_marginBottom="@dimen/screenshot_offset_y" + android:layout_gravity="bottom|start" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:clipToPadding="false" + android:clipChildren="false" + android:padding="4dp" + android:background="@drawable/screenshot_border" + > + <FrameLayout + android:elevation="0dp" + android:background="@drawable/screenshot_preview_background" + android:clipChildren="true" + android:clipToOutline="true" + android:clipToPadding="true" + android:layout_width="@dimen/screenshot_x_scale" + android:layout_height="wrap_content"> + <TextView android:id="@+id/text_preview" + android:textFontWeight="500" + android:padding="8dp" + android:gravity="center|start" + android:ellipsize="end" + android:autoSizeTextType="uniform" + android:autoSizeMinTextSize="10sp" + android:autoSizeMaxTextSize="200sp" + android:textColor="?android:attr/textColorPrimary" + android:layout_width="@dimen/screenshot_x_scale" + android:layout_height="@dimen/screenshot_x_scale"/> + <ImageView + android:id="@+id/image_preview" + android:scaleType="fitCenter" + android:adjustViewBounds="true" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + </FrameLayout> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml new file mode 100644 index 000000000000..8f6753a095d0 --- /dev/null +++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:id="@+id/copy_button" + style="@android:style/Widget.DeviceDefault.Button.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:text="@string/clipboard_edit_text_copy" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/attribution" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + app:layout_constraintStart_toStartOf="@+id/copy_button" + app:layout_constraintTop_toBottomOf="@+id/copy_button" /> + + <ImageButton + android:id="@+id/share" + style="@android:style/Widget.Material.Button.Borderless" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginEnd="8dp" + android:padding="12dp" + android:scaleType="fitCenter" + android:contentDescription="@*android:string/share" + android:tooltipText="@*android:string/share" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/copy_button" + android:src="@drawable/ic_screenshot_share" /> + + <ScrollView + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginTop="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintStart_toStartOf="@+id/copy_button" + app:layout_constraintTop_toBottomOf="@+id/attribution"> + + <EditText + android:id="@+id/edit_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start|top" + android:textSize="24sp" /> + </ScrollView> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml new file mode 100644 index 000000000000..76280d816cd9 --- /dev/null +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -0,0 +1,61 @@ +<?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. + --> +<com.android.systemui.clipboardoverlay.DraggableConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_gravity="bottom" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <ImageView + android:id="@+id/actions_container_background" + android:visibility="gone" + android:layout_height="0dp" + android:layout_width="0dp" + android:elevation="1dp" + android:background="@drawable/action_chip_container_background" + android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal" + app:layout_constraintBottom_toBottomOf="@+id/actions_container" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/actions_container" + app:layout_constraintEnd_toEndOf="@+id/actions_container"/> + <HorizontalScrollView + android:id="@+id/actions_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal" + android:paddingEnd="@dimen/screenshot_action_container_padding_right" + android:paddingVertical="@dimen/screenshot_action_container_padding_vertical" + android:elevation="1dp" + android:scrollbars="none" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintWidth_percent="1.0" + app:layout_constraintWidth_max="wrap" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@+id/preview_border" + app:layout_constraintEnd_toEndOf="parent"> + <LinearLayout + android:id="@+id/actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <include layout="@layout/screenshot_action_chip" + android:id="@+id/remote_copy_chip"/> + <include layout="@layout/screenshot_action_chip" + android:id="@+id/edit_chip"/> + </LinearLayout> + </HorizontalScrollView> + <include layout="@layout/clipboard_content_preview" /> +</com.android.systemui.clipboardoverlay.DraggableConstraintLayout> diff --git a/packages/SystemUI/res/layout/contaminant_dialog.xml b/packages/SystemUI/res/layout/contaminant_dialog.xml index ea6d900e8fa7..5f8c30532255 100644 --- a/packages/SystemUI/res/layout/contaminant_dialog.xml +++ b/packages/SystemUI/res/layout/contaminant_dialog.xml @@ -63,7 +63,8 @@ <TextView android:id="@+id/learnMore" - style="@style/USBContaminant.UserAction" /> + style="@style/USBContaminant.UserAction" + android:visibility="gone" /> <View android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index e69582f52ebf..f72a8dc08c9c 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -378,7 +378,8 @@ android:clickable="true"/> </LinearLayout> </LinearLayout> - <FrameLayout + + <LinearLayout android:id="@+id/button_layout" android:orientation="horizontal" android:layout_width="match_parent" @@ -390,9 +391,10 @@ android:clickable="false" android:focusable="false"> - <FrameLayout + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_weight="1" android:layout_gravity="start|center_vertical" android:orientation="vertical"> <Button @@ -401,12 +403,13 @@ android:layout_height="wrap_content" android:text="@string/turn_off_airplane_mode" android:ellipsize="end" + android:maxLines="1" style="@style/Widget.Dialog.Button.BorderButton" android:clickable="true" android:focusable="true"/> - </FrameLayout> + </LinearLayout> - <FrameLayout + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" @@ -417,10 +420,13 @@ android:layout_height="wrap_content" android:text="@string/inline_done_button" style="@style/Widget.Dialog.Button" + android:maxLines="1" + android:ellipsize="end" android:clickable="true" android:focusable="true"/> - </FrameLayout> - </FrameLayout> + </LinearLayout> + </LinearLayout> + </LinearLayout> </androidx.core.widget.NestedScrollView> </LinearLayout> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 07f843bb2139..10b8ef414617 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi is nie gekoppel nie"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleuromkering"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurregstelling"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Meer instellings"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Gebruikerinstellings"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontsluit om te gebruik"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Kon nie jou kaarte kry nie; probeer later weer"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Sluitskerminstellings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skandeer QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik om \'n QR-kode te skandeer"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kode"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tik om te skandeer"</string> <string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Jy sal nie jou volgende wekker <xliff:g id="WHEN">%1$s</xliff:g> hoor nie"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ontdoen"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Beweeg nader om op <xliff:g id="DEVICENAME">%1$s</xliff:g> te speel"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Speel tans op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Onaktief, gaan program na"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nie gekry nie"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kies gebruiker"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Programme wat op die agtergrond werk"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-af/tiles_states_strings.xml b/packages/SystemUI/res/values-af/tiles_states_strings.xml index 4b1a5b85d1d2..08e60c51ece3 100644 --- a/packages/SystemUI/res/values-af/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-af/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Af"</item> <item msgid="2075645297847971154">"Aan"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Onbeskikbaar"</item> + <item msgid="1909756493418256167">"Af"</item> + <item msgid="4531508423703413340">"Aan"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Onbeskikbaar"</item> <item msgid="9103697205127645916">"Af"</item> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index c31179c643d2..6fe6f539df3c 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi አልተገናኘም"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ብሩህነት"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ተቃራኒ ቀለም"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"የቀለም ማስተካከያ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ተጨማሪ ቅንብሮች"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"የተጠቃሚ ቅንብሮች"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ተከናውኗል"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ለማየት ይክፈቱ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"የእርስዎን ካርዶች ማግኘት ላይ ችግር ነበር፣ እባክዎ ቆይተው እንደገና ይሞክሩ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"የገጽ መቆለፊያ ቅንብሮች"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR ቃኝ"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR ኮድን ለመቃኘት ጠቅ ያድርጉ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ለመቃኘት መታ ያድርጉ"</string> <string name="status_bar_work" msgid="5238641949837091056">"የስራ መገለጫ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"የአውሮፕላን ሁነታ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"የእርስዎን ቀጣይ ማንቂያ <xliff:g id="WHEN">%1$s</xliff:g> አይሰሙም"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> ያጫውቱ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ከ<xliff:g id="APP_LABEL">%2$s</xliff:g> ያጫውቱ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ቀልብስ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ ለማጫወት ጠጋ ያድርጉ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ በማጫወት ላይ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"አልተገኘም"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ተጠቃሚን ይምረጡ"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ከበስተጀርባ የሚሠሩ መተግበሪያዎች"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"መቆሚያ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-am/tiles_states_strings.xml b/packages/SystemUI/res/values-am/tiles_states_strings.xml index 0f7ee3ecd8d2..c464f9a98cf7 100644 --- a/packages/SystemUI/res/values-am/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-am/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ጠፍቷል"</item> <item msgid="2075645297847971154">"በርቷል"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"አይገኝም"</item> + <item msgid="1909756493418256167">"አጥፋ"</item> + <item msgid="4531508423703413340">"አብራ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"አይገኝም"</item> <item msgid="9103697205127645916">"ጠፍቷል"</item> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index ec4816fd448a..5a0c7a1969fc 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -237,8 +237,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"لم يتم الاتصال بشبكة Wi-Fi."</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"السطوع"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"قلب الألوان"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحيح الألوان"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"المزيد من الإعدادات"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"إعدادات المستخدم"</string> <string name="quick_settings_done" msgid="2163641301648855793">"تم"</string> @@ -462,8 +461,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"فتح القفل للاستخدام"</string> <string name="wallet_error_generic" msgid="257704570182963611">"حدثت مشكلة أثناء الحصول على البطاقات، يُرجى إعادة المحاولة لاحقًا."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"إعدادات شاشة القفل"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"مسح رمز الاستجابة السريعة ضوئيًا"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"انقر لمسح رمز الاستجابة السريعة ضوئيًا."</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"الملف الشخصي للعمل"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"وضع الطيران"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"لن تسمع المنبّه القادم في <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -816,7 +817,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"تراجع"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"عليك الاقتراب لتشغيل الموسيقى على <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"جارٍ تشغيل الموسيقى على <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string> <string name="controls_error_removed" msgid="6675638069846014366">"لم يتم العثور عليه."</string> @@ -904,4 +906,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"اختيار المستخدم"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"التطبيقات التي يتم تشغيلها في الخلفية"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"إيقاف"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ar/tiles_states_strings.xml b/packages/SystemUI/res/values-ar/tiles_states_strings.xml index 2da87c467177..2bfcf7c57b8b 100644 --- a/packages/SystemUI/res/values-ar/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ar/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"الميزة غير مفعّلة"</item> <item msgid="2075645297847971154">"الميزة مفعّلة"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"غير متوفّر"</item> + <item msgid="1909756493418256167">"غير مفعّل"</item> + <item msgid="4531508423703413340">"مفعّل"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"الميزة غير متاحة"</item> <item msgid="9103697205127645916">"الميزة غير مفعّلة"</item> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index f118633b199b..bf35ad202870 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ৱাই-ফাইৰ সৈতে সংযোগ হৈ থকা নাই"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ৰং বিপৰীতকৰণ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ৰং শুধৰণী"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"অধিক ছেটিং"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ব্যৱহাৰকাৰীৰ ছেটিং"</string> <string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন কৰা হ’ল"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যৱহাৰ কৰিবলৈ আনলক কৰক"</string> <string name="wallet_error_generic" msgid="257704570182963611">"আপোনাৰ কাৰ্ড লাভ কৰোঁতে এটা সমস্যা হৈছে, অনুগ্ৰহ কৰি পাছত পুনৰ চেষ্টা কৰক"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্ৰীনৰ ছেটিং"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"কিউআৰ স্কেন কৰক"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"এটা কিউআৰ ক’ড স্কেন কৰিবলৈ ক্লিক কৰক"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"এয়াৰপ্লেইন ম\'ড"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"আপুনি আপোনাৰ পিছৰটো এলাৰ্ম <xliff:g id="WHEN">%1$s</xliff:g> বজাত শুনা নাপাব"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ত <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"আনডু কৰক"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে\' কৰিবলৈ ওপৰলৈ যাওক"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে\' কৰি থকা হৈছে"</string> <string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্টো পৰীক্ষা কৰক"</string> <string name="controls_error_removed" msgid="6675638069846014366">"বিচাৰি পোৱা নগ’ল"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যৱহাৰকাৰী বাছনি কৰক"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"নেপথ্যত চলি থকা এপ্"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"বন্ধ কৰক"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-as/tiles_states_strings.xml b/packages/SystemUI/res/values-as/tiles_states_strings.xml index 2ede37473ea5..ba66f8c5f1cd 100644 --- a/packages/SystemUI/res/values-as/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-as/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"অফ আছে"</item> <item msgid="2075645297847971154">"অন কৰা আছে"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"উপলব্ধ নহয়"</item> + <item msgid="1909756493418256167">"অফ আছে"</item> + <item msgid="4531508423703413340">"অন কৰা আছে"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"উপলব্ধ নহয়"</item> <item msgid="9103697205127645916">"অফ আছে"</item> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 9abe0a7a04f7..d4917248de33 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi qoşulu deyil"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaqlıq"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rəng inversiyası"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Rəng korreksiyası"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Digər ayarlar"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"İstifadəçi ayarları"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Hazır"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"İstifadə etmək üçün kiliddən çıxarın"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Kartların əldə edilməsində problem oldu, sonra yenidən cəhd edin"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilid ekranı ayarları"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR kodunu skan edin"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR kodu skan etmək üçün tıklayın"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Skanlamaq üçün toxunun"</string> <string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Təyyarə rejimi"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> zaman növbəti xəbərdarlığınızı eşitməyəcəksiniz"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudun"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%2$s</xliff:g> tətbiqindən oxudun"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Geri qaytarın"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxutmaq üçün yaxınlaşın"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxudulur"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tapılmadı"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"İstifadəçi seçin"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Arxa fonda işləyən tətbiqlər"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Dayandırın"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-az/tiles_states_strings.xml b/packages/SystemUI/res/values-az/tiles_states_strings.xml index f52a9e1bf6fa..368966038b3d 100644 --- a/packages/SystemUI/res/values-az/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-az/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Deaktiv"</item> <item msgid="2075645297847971154">"Aktiv"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Əlçatmazdır"</item> + <item msgid="1909756493418256167">"Deaktiv"</item> + <item msgid="4531508423703413340">"Aktiv"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Əlçatan deyil"</item> <item msgid="9103697205127645916">"Deaktiv"</item> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 02c4d920bf83..955096e35628 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi nije povezan"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Još podešavanja"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisnička podešavanja"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> @@ -453,8 +452,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključaj radi korišćenja"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Došlo je do problema pri preuzimanju kartica. Probajte ponovo kasnije"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Podešavanja zaključanog ekrana"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skeniraj QR kôd"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknite da biste skenirali QR kôd"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kôd"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Dodirnite da biste skenirali"</string> <string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim rada u avionu"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sledeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +797,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Opozovi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Približite da biste puštali muziku na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Pušta se na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> @@ -886,4 +886,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izaberite korisnika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije pokrenute u pozadini"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml index d9e0bfca36ff..6e2b7d1bbe57 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Isključeno"</item> <item msgid="2075645297847971154">"Uključeno"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nedostupno"</item> + <item msgid="1909756493418256167">"Isključeno"</item> + <item msgid="4531508423703413340">"Uključeno"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nedostupno"</item> <item msgid="9103697205127645916">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 88f58ac105d3..4d11111832b1 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Няма падключэння да Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркасць"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія колераў"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Карэкцыя колераў"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Дадатковыя налады"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Налады карыстальніка"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Гатова"</string> @@ -354,7 +353,7 @@ <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Паказ апавяшчэнняў прыпынены ў рэжыме \"Не турбаваць\""</string> <string name="media_projection_action_text" msgid="3634906766918186440">"Пачаць зараз"</string> <string name="empty_shade_text" msgid="8935967157319717412">"Апавяшчэнняў няма"</string> - <string name="quick_settings_disclosure_parental_controls" msgid="2114102871438223600">"Гэта прылада знаходзіцца пад кантролем вашых бацькоў"</string> + <string name="quick_settings_disclosure_parental_controls" msgid="2114102871438223600">"Гэта прылада знаходзіцца пад кантролем бацькоў"</string> <string name="quick_settings_disclosure_management_monitoring" msgid="8231336875820702180">"Ваша арганізацыя валодае гэтай прыладай і можа кантраляваць сеткавы трафік"</string> <string name="quick_settings_disclosure_named_management_monitoring" msgid="2831423806103479812">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> валодае гэтай прыладай і можа кантраляваць сеткавы трафік"</string> <string name="quick_settings_financed_disclosure_named_management" msgid="2307703784594859524">"Гэта прылада належыць арганізацыі \"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>\""</string> @@ -393,7 +392,7 @@ <string name="monitoring_description_personal_profile_named_vpn" msgid="8179722332380953673">"Ваш асабісты профіль падключаны да праграмы <xliff:g id="VPN_APP">%1$s</xliff:g>, якая можа сачыць за вашай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты."</string> <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" ,"</string> <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Адкрыйце налады VPN"</string> - <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Гэта прылада знаходзіцца пад кантролем вашых бацькоў. Бацькі могуць праглядаць і кантраляваць вашу інфармацыю, напрыклад пра праграмы, якія вы выкарыстоўваеце, даныя пра ваша месцазнаходжанне і час карыстання прыладай."</string> + <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Гэта прылада знаходзіцца пад кантролем бацькоў. Бацькі могуць праглядаць і кантраляваць тваю інфармацыю, напрыклад пра праграмы, якія ты выкарыстоўваеш, даныя пра тваё месцазнаходжанне і час карыстання прыладай."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Разблакіравана з дапамогай TrustAgent"</string> <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string> @@ -456,8 +455,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблакіраваць для выкарыстання"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Узнікла праблема з загрузкай вашых карт. Паўтарыце спробу пазней"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Налады экрана блакіроўкі"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Адсканіраваць QR-код"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Націсніце, каб адсканіраваць QR-код"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Працоўны профіль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Рэжым палёту"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Вы не пачуеце наступны будзільнік <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -804,7 +805,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) з дапамогай праграмы \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" з дапамогай праграмы \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Адрабіць"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Наблізьцеся, каб прайграць музыку на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Прайграецца на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не знойдзена"</string> @@ -892,4 +894,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выбар карыстальніка"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Праграмы працуюць у фонавым рэжыме"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Спыніць"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-be/tiles_states_strings.xml b/packages/SystemUI/res/values-be/tiles_states_strings.xml index 5c7c40fed7a7..aef519fe9dcb 100644 --- a/packages/SystemUI/res/values-be/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-be/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Выключана"</item> <item msgid="2075645297847971154">"Уключана"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недаступна"</item> + <item msgid="1909756493418256167">"Выключана"</item> + <item msgid="4531508423703413340">"Уключана"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Недаступна"</item> <item msgid="9103697205127645916">"Выключана"</item> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 8b455daef832..aaa1f3abab11 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"не е установена връзка с Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркост"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инвертиране на цветовете"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекция на цветове"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Още настройки"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Потребителски настройки"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отключване с цел използване"</string> <string name="wallet_error_generic" msgid="257704570182963611">"При извличането на картите ви възникна проблем. Моля, опитайте отново по-късно"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки за заключения екран"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Сканиране на QR код"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Кликнете, за да сканирате QR код"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Потребителски профил в Work"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Самолетен режим"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Няма да чуете следващия си будилник в <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> от <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Отмяна"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Преместете се по-близо, за да се възпроизведе на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Възпроизвежда се на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не е намерено"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Избор на потребител"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Приложения, които се изпълняват на заден план"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Спиране"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-bg/tiles_states_strings.xml b/packages/SystemUI/res/values-bg/tiles_states_strings.xml index 6839b830283c..0900c521abf1 100644 --- a/packages/SystemUI/res/values-bg/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bg/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Изкл."</item> <item msgid="2075645297847971154">"Вкл."</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Не е налице"</item> + <item msgid="1909756493418256167">"Изкл."</item> + <item msgid="4531508423703413340">"Вкл."</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Не е налице"</item> <item msgid="9103697205127645916">"Изкл."</item> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 958853ec91e4..be290afede2f 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ওয়াই-ফাই কানেক্ট করা নেই"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"কালার ইনভার্সন"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"রঙ সংশোধন"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"আরও সেটিংস"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ব্যবহারকারী সেটিংস"</string> <string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন হয়েছে"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যবহার করতে আনলক করুন"</string> <string name="wallet_error_generic" msgid="257704570182963611">"আপনার কার্ড সংক্রান্ত তথ্য পেতে সমস্যা হয়েছে, পরে আবার চেষ্টা করুন"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্রিন সেটিংস"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR কোড স্ক্যান করুন"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR কোড স্ক্যান করতে ক্লিক করুন"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"স্ক্যান করতে ট্যাপ করুন"</string> <string name="status_bar_work" msgid="5238641949837091056">"কাজের প্রোফাইল"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"বিমান মোড"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"আপনি আপনার পরবর্তী <xliff:g id="WHEN">%1$s</xliff:g> অ্যালার্ম শুনতে পাবেন না"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চালান"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%2$s</xliff:g> অ্যাপে চালান"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"আগের অবস্থায় ফিরুন"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ চালাতে আরও কাছে আনুন"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ ভিডিও চালানো হচ্ছে"</string> <string name="controls_error_timeout" msgid="794197289772728958">"বন্ধ আছে, অ্যাপ চেক করুন"</string> <string name="controls_error_removed" msgid="6675638069846014366">"খুঁজে পাওয়া যায়নি"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যবহারকারী বেছে নিন"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ব্যাকগ্রাউন্ডে অ্যাপ চলছে"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"বন্ধ করুন"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-bn/tiles_states_strings.xml b/packages/SystemUI/res/values-bn/tiles_states_strings.xml index 7a6b3ac1c371..5358e5d2ec43 100644 --- a/packages/SystemUI/res/values-bn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bn/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"বন্ধ আছে"</item> <item msgid="2075645297847971154">"চালু আছে"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"অনুপলভ্য"</item> + <item msgid="1909756493418256167">"বন্ধ আছে"</item> + <item msgid="4531508423703413340">"চালু আছে"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"উপলভ্য নেই"</item> <item msgid="9103697205127645916">"বন্ধ আছে"</item> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 092d3df05c7a..eb45708f1caa 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi mreža nije povezana"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvjetljenje"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ispravka boje"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Više postavki"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisničke postavke"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> @@ -453,8 +452,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da koristite"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Došlo je do problema prilikom preuzimanja vaših kartica. Pokušajte ponovo kasnije"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključavanja ekrana"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skeniraj QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknite da skenirate QR kôd"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kôd"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Dodirnite da skenirate"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil za posao"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u avionu"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +797,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Približite se da reproducirate na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, vidite aplikaciju"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> @@ -886,4 +886,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odaberite korisnika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije su aktivne u pozadini"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-bs/tiles_states_strings.xml b/packages/SystemUI/res/values-bs/tiles_states_strings.xml index d9e0bfca36ff..6e2b7d1bbe57 100644 --- a/packages/SystemUI/res/values-bs/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bs/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Isključeno"</item> <item msgid="2075645297847971154">"Uključeno"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nedostupno"</item> + <item msgid="1909756493418256167">"Isključeno"</item> + <item msgid="4531508423703413340">"Uključeno"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nedostupno"</item> <item msgid="9103697205127645916">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 6ae7d1617df6..06799a21da4c 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"La Wi‑Fi no està connectada"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversió de colors"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correcció de color"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Més opcions"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuració d\'usuari"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Fet"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloqueja per utilitzar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Hi ha hagut un problema en obtenir les teves targetes; torna-ho a provar més tard"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuració de la pantalla de bloqueig"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Escaneja un codi QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Fes clic per escanejar un codi QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de treball"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode d\'avió"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> no sentiràs la pròxima alarma"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> des de l\'aplicació <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfés"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Mou més a prop per reproduir a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"S\'està reproduint a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No s\'ha trobat"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecciona un usuari"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicacions que s\'executen en segon pla"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Atura"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ca/tiles_states_strings.xml b/packages/SystemUI/res/values-ca/tiles_states_strings.xml index 28f3da4650eb..2738ecfcbd88 100644 --- a/packages/SystemUI/res/values-ca/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ca/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Desactivat"</item> <item msgid="2075645297847971154">"Activat"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"No disponible"</item> + <item msgid="1909756493418256167">"Desactivada"</item> + <item msgid="4531508423703413340">"Activada"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"No disponible"</item> <item msgid="9103697205127645916">"Desactivat"</item> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 7e716160939d..4b63cd807084 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Není připojena Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Převrácení barev"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekce barev"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Další nastavení"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Uživatelské nastavení"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Hotovo"</string> @@ -456,8 +455,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odemknout a použít"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Při načítání karet došlo k problému, zkuste to později"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavení obrazovky uzamčení"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Naskenovat QR kód"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknutím naskenujete QR kód"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kód"</string> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Pracovní profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim Letadlo"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Svůj další budík <xliff:g id="WHEN">%1$s</xliff:g> neuslyšíte"</string> @@ -804,7 +804,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Vrátit zpět"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Pokud chcete přehrávat na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>, přibližte se k němu"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Přehrává se na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nenalezeno"</string> @@ -892,4 +893,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zvolte uživatele"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikace běžící na pozadí"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Konec"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-cs/tiles_states_strings.xml b/packages/SystemUI/res/values-cs/tiles_states_strings.xml index ce64e273dc02..cd667cbdc2f0 100644 --- a/packages/SystemUI/res/values-cs/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-cs/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Vyp"</item> <item msgid="2075645297847971154">"Zap"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nedostupné"</item> + <item msgid="1909756493418256167">"Vyp"</item> + <item msgid="4531508423703413340">"Zap"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nedostupné"</item> <item msgid="9103697205127645916">"Vyp"</item> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 919f57c307ff..ad92cad640c9 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Manglende Wi-Fi-forbindelse"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ombytning af farver"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farvekorrigering"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Flere indstillinger"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Brugerindstillinger"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Udfør"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås op for at bruge"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Dine kort kunne ikke hentes. Prøv igen senere."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lås skærmindstillinger"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR-kode"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik for at scanne en QR-kode"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Arbejdsprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flytilstand"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Fortryd"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Flyt enheden tættere på for at afspille på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Afspilles på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ikke fundet"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vælg bruger"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps, der kører i baggrunden"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-da/tiles_states_strings.xml b/packages/SystemUI/res/values-da/tiles_states_strings.xml index 9e4c6f4b8459..5ec01fe39c4f 100644 --- a/packages/SystemUI/res/values-da/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-da/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Fra"</item> <item msgid="2075645297847971154">"Til"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Ikke tilgængelig"</item> + <item msgid="1909756493418256167">"Fra"</item> + <item msgid="4531508423703413340">"Til"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Ikke tilgængelig"</item> <item msgid="9103697205127645916">"Fra"</item> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index ce58c3d080c8..8b514829614f 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WLAN nicht verbunden"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helligkeit"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Farbumkehr"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farbkorrektur"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Weitere Einstellungen"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Nutzereinstellungen"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Fertig"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Zum Verwenden entsperren"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Beim Abrufen deiner Karten ist ein Fehler aufgetreten – bitte versuch es später noch einmal"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Einstellungen für den Sperrbildschirm"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-Code scannen"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klicken, um einen QR-Code zu scannen"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Zum Scannen tippen"</string> <string name="status_bar_work" msgid="5238641949837091056">"Arbeitsprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flugmodus"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Lautloser Weckruf <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> über <xliff:g id="APP_LABEL">%2$s</xliff:g> wiedergeben"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Rückgängig machen"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Gehe für die Wiedergabe näher an <xliff:g id="DEVICENAME">%1$s</xliff:g> heran"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Wird auf <xliff:g id="DEVICENAME">%1$s</xliff:g> abgespielt"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nicht gefunden"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Nutzer auswählen"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps, die im Hintergrund ausgeführt werden"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Beenden"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-de/tiles_states_strings.xml b/packages/SystemUI/res/values-de/tiles_states_strings.xml index e958670b502e..72476456b248 100644 --- a/packages/SystemUI/res/values-de/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-de/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Aus"</item> <item msgid="2075645297847971154">"An"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nicht verfügbar"</item> + <item msgid="1909756493418256167">"Aus"</item> + <item msgid="4531508423703413340">"An"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nicht verfügbar"</item> <item msgid="9103697205127645916">"Aus"</item> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 96aed4c7264c..9e61f1a29135 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Το Wi-Fi δεν είναι συνδεδεμένο"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Φωτεινότητα"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Αντιστροφή χρωμάτων"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Διόρθωση χρωμάτων"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Περισσότερες ρυθμίσεις"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ρυθμίσεις χρήστη"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Τέλος"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ξεκλείδωμα για χρήση"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Παρουσιάστηκε πρόβλημα με τη λήψη των καρτών σας. Δοκιμάστε ξανά αργότερα"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ρυθμίσεις κλειδώματος οθόνης"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Σάρωση κωδικού QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Κάντε κλικ για σάρωση κωδικού QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Πατήστε για σάρωση"</string> <string name="status_bar_work" msgid="5238641949837091056">"Προφίλ εργασίας"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Λειτουργία πτήσης"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Δεν θα ακούσετε το επόμενο ξυπνητήρι σας <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Αναίρεση"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Πλησιάστε για αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Ανενεργό, έλεγχος εφαρμογής"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Δεν βρέθηκε."</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Επιλογή χρήστη"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Οι εφαρμογές βρίσκονται σε εξέλιξη στο παρασκήνιο"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Διακοπή"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-el/tiles_states_strings.xml b/packages/SystemUI/res/values-el/tiles_states_strings.xml index 1c9583518595..4dca192a877c 100644 --- a/packages/SystemUI/res/values-el/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-el/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Ανενεργό"</item> <item msgid="2075645297847971154">"Ενεργό"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Μη διαθέσιμη"</item> + <item msgid="1909756493418256167">"Ανενεργή"</item> + <item msgid="4531508423703413340">"Ενεργή"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Μη διαθέσιμο"</item> <item msgid="9103697205127645916">"Ανενεργό"</item> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 8504cb77cbb3..79c9ca2de1db 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 68c5e649c56d..605811d571ee 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 8504cb77cbb3..79c9ca2de1db 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 8504cb77cbb3..79c9ca2de1db 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 60f725e6450f..389554855332 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards, please try again later"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,7 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +879,6 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copy"</string> + <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copied"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 66bbbbee56dc..f748e9dce65a 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Red Wi-Fi no conectada"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión de color"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de colores"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Más configuraciones"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuración del usuario"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Listo"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ocurrió un problema al obtener las tarjetas; vuelve a intentarlo más tarde"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración de pantalla de bloqueo"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Escanear QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Haz clic para escanear un código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Presiona para escanear"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avión"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma a la(s) <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducir <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Acércate para reproducir en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No se encontró"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps en ejecución en segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Detener"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml index 0d77977fd174..6e425eea6542 100644 --- a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Desactivado"</item> <item msgid="2075645297847971154">"Activado"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"No disponible"</item> + <item msgid="1909756493418256167">"Desactivada"</item> + <item msgid="4531508423703413340">"Activada"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"No disponible"</item> <item msgid="9103697205127645916">"Desactivado"</item> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 98b45d5162e0..04947803c4bc 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi no conectado"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de color"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Más ajustes"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ajustes de usuario"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Hecho"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Se ha producido un problema al obtener tus tarjetas. Inténtalo de nuevo más tarde."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ajustes de pantalla de bloqueo"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Escanear QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Haz clic para escanear un código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para que se reproduzca en ese dispositivo"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No se ha encontrado"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicaciones ejecutándose en segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Detener"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-es/tiles_states_strings.xml b/packages/SystemUI/res/values-es/tiles_states_strings.xml index 080731a2feb1..3ac10ec4a2cf 100644 --- a/packages/SystemUI/res/values-es/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-es/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Desactivado"</item> <item msgid="2075645297847971154">"Activado"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"No disponible"</item> + <item msgid="1909756493418256167">"Desactivada"</item> + <item msgid="4531508423703413340">"Activada"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"No disponible"</item> <item msgid="9103697205127645916">"Desactivado"</item> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 7526b0dea7f8..15c10be74efc 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi-ühendus puudub"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Heledus"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Värvide ümberpööramine"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värviparandus"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Rohkem seadeid"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Kasutaja seaded"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avage kasutamiseks"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Teie kaartide hankimisel ilmnes probleem, proovige hiljem uuesti"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukustuskuva seaded"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-koodi skannimine"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klõpsake QR-koodi skannimiseks"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Tööprofiil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lennukirežiim"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Te ei kuule järgmist äratust kell <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Võta tagasi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Minge lähemale, et seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g> esitada"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Esitatakse seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ei leitud"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kasutaja valimine"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Taustal töötavad rakendused"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Peata"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-et/tiles_states_strings.xml b/packages/SystemUI/res/values-et/tiles_states_strings.xml index 26d71fe07a57..27edd17c9b0d 100644 --- a/packages/SystemUI/res/values-et/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-et/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Väljas"</item> <item msgid="2075645297847971154">"Sees"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Pole saadaval"</item> + <item msgid="1909756493418256167">"Väljas"</item> + <item msgid="4531508423703413340">"Sees"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Pole saadaval"</item> <item msgid="9103697205127645916">"Väljas"</item> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 71c3feb8594c..454ad59807bb 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ez zaude konektatuta wifi-sarera"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kolore-alderantzikatzea"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Ezarpen gehiago"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Erabiltzaile-ezarpenak"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Eginda"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desblokeatu erabiltzeko"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Arazo bat izan da txartelak eskuratzean. Saiatu berriro geroago."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Pantaila blokeatuaren ezarpenak"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Eskaneatu QR kode bat"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR kode bat eskaneatzeko, sakatu hau"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Sakatu eskaneatzeko"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profila"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Hegaldi modua"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ez duzu entzungo hurrengo alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> bidez"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desegin"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Gerturatu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzeko"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> pantailan erreproduzitzen"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ez da aurkitu"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Hautatu erabiltzaile bat"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Atzeko planoan exekutatzen ari diren aplikazioak"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Gelditu"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-eu/tiles_states_strings.xml b/packages/SystemUI/res/values-eu/tiles_states_strings.xml index 11090458f1f5..eb13a1202cc4 100644 --- a/packages/SystemUI/res/values-eu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-eu/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Desaktibatuta"</item> <item msgid="2075645297847971154">"Aktibatuta"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Ez dago erabilgarri"</item> + <item msgid="1909756493418256167">"Desaktibatuta"</item> + <item msgid="4531508423703413340">"Aktibatuta"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Ez dago erabilgarri"</item> <item msgid="9103697205127645916">"Desaktibatuta"</item> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 4efdc8428097..c2c1ffb3ca48 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi وصل نیست"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"روشنایی"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"وارونگی رنگ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحیح رنگ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"تنظیمات بیشتر"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"تنظیمات کاربر"</string> <string name="quick_settings_done" msgid="2163641301648855793">"تمام"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"برای استفاده، قفل را باز کنید"</string> <string name="wallet_error_generic" msgid="257704570182963611">"هنگام دریافت کارتها مشکلی پیش آمد، لطفاً بعداً دوباره امتحان کنید"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"تنظیمات صفحه قفل"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"اسکن رمزینه پاسخسریع"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"برای اسکن رمزینه پاسخسریع، کلیک کنید"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"نمایه کاری"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"حالت هواپیما"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"در ساعت <xliff:g id="WHEN">%1$s</xliff:g>، دیگر صدای زنگ ساعت را نمیشنوید"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش کنید"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%2$s</xliff:g> پخش کنید"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"واگرد"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"برای پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g>، به دستگاه نزدیکتر شوید"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"درحال پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غیرفعال، برنامه را بررسی کنید"</string> <string name="controls_error_removed" msgid="6675638069846014366">"پیدا نشد"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"انتخاب کاربر"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"برنامههایی که در پسزمینه اجرا میشود"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"توقف"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fa/tiles_states_strings.xml b/packages/SystemUI/res/values-fa/tiles_states_strings.xml index ab755197d0a5..dcde4d3e18db 100644 --- a/packages/SystemUI/res/values-fa/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fa/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"خاموش"</item> <item msgid="2075645297847971154">"روشن"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"دردسترس نیست"</item> + <item msgid="1909756493418256167">"خاموش"</item> + <item msgid="4531508423703413340">"روشن"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"دردسترس نیست"</item> <item msgid="9103697205127645916">"خاموش"</item> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 1ab196f09aca..cd3844fad9da 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -100,7 +100,7 @@ <string name="accessibility_back" msgid="6530104400086152611">"Takaisin"</string> <string name="accessibility_home" msgid="5430449841237966217">"Aloitus"</string> <string name="accessibility_menu" msgid="2701163794470513040">"Valikko"</string> - <string name="accessibility_accessibility_button" msgid="4089042473497107709">"Esteettömyys"</string> + <string name="accessibility_accessibility_button" msgid="4089042473497107709">"Saavutettavuus"</string> <string name="accessibility_rotate_button" msgid="1238584767612362586">"Näytön kääntäminen"</string> <string name="accessibility_recent" msgid="901641734769533575">"Viimeisimmät"</string> <string name="accessibility_camera_button" msgid="2938898391716647247">"Kamera"</string> @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fiä ei ole yhdistetty"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kirkkaus"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Käänteiset värit"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värinkorjaus"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Lisäasetukset"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Käyttäjäasetukset"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avaa lukitus ja käytä"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Korttien noutamisessa oli ongelma, yritä myöhemmin uudelleen"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukitusnäytön asetukset"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skannaa QR-koodi"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Skannaa QR-koodi klikkaamalla"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Työprofiili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lentokonetila"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Et kuule seuraavaa hälytystäsi (<xliff:g id="WHEN">%1$s</xliff:g>)."</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) sovelluksessa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="APP_LABEL">%2$s</xliff:g>)"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Kumoa"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Siirry lähemmäs, jotta <xliff:g id="DEVICENAME">%1$s</xliff:g> voi toistaa tämän"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> toistaa tämän"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ei löydy"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Valitse käyttäjä"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Sovellukset jotka ovat käynnissä taustalla"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Lopeta"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fi/tiles_states_strings.xml b/packages/SystemUI/res/values-fi/tiles_states_strings.xml index 6fa9466e1313..d838cf84d409 100644 --- a/packages/SystemUI/res/values-fi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fi/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Poissa päältä"</item> <item msgid="2075645297847971154">"Päällä"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Ei saatavilla"</item> + <item msgid="1909756493418256167">"Pois päältä"</item> + <item msgid="4531508423703413340">"Päällä"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Ei saatavilla"</item> <item msgid="9103697205127645916">"Poissa päältä"</item> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 4eadcc232a73..440369d54ea1 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Non connecté au Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Plus de paramètres"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Paramètres utilisateur"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Terminé"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Un problème est survenu lors de la récupération de vos cartes, veuillez réessayer plus tard"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Numériser le code QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Cliquez pour numériser un code QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme à <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Rapprochez-vous pour faire jouer le contenu sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Sélect. utilisateur"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Applications exécutées en arrière-plan"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Arrêter"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml index aec8ab87dcfb..0b087ad821c8 100644 --- a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Désactivé"</item> <item msgid="2075645297847971154">"Activé"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Non disponible"</item> + <item msgid="1909756493418256167">"Désactivée"</item> + <item msgid="4531508423703413340">"Activée"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Non disponible"</item> <item msgid="9103697205127645916">"Désactivée"</item> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 33fc2945a045..891e85c19724 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi non connecté"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Plus de paramètres"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Paramètres utilisateur"</string> <string name="quick_settings_done" msgid="2163641301648855793">"OK"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Problème de récupération de vos cartes. Réessayez plus tard"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scanner un code QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Cliquer pour scanner un code QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Appuyer pour scanner"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme <xliff:g id="WHEN">%1$s</xliff:g>."</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> depuis <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Rapprochez-vous pour lire sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>…"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Choisir utilisateur"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Applis exécutées en arrière-plan"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Arrêter"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr/tiles_states_strings.xml b/packages/SystemUI/res/values-fr/tiles_states_strings.xml index 01d9c1d74dd0..fbae02afb9b5 100644 --- a/packages/SystemUI/res/values-fr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Désactivé"</item> <item msgid="2075645297847971154">"Activé"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Indisponible"</item> + <item msgid="1909756493418256167">"Désactivée"</item> + <item msgid="4531508423703413340">"Activée"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Indisponible"</item> <item msgid="9103697205127645916">"Désactivée"</item> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 40fe254b563e..70a3c68ee29f 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"A wifi non está conectada"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión da cor"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección da cor"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Máis opcións"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuración de usuario"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Feito"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Produciuse un problema ao obter as tarxetas. Téntao de novo máis tarde"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración da pantalla de bloqueo"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Escanear QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Fai clic para escanear un código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de traballo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfacer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Achega o dispositivo para reproducir a música en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Estase reproducindo o contido en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Non se atopou"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicacións que se están executando en segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Deter"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-gl/tiles_states_strings.xml b/packages/SystemUI/res/values-gl/tiles_states_strings.xml index 9045983425df..531e7ff88e4f 100644 --- a/packages/SystemUI/res/values-gl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-gl/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Non"</item> <item msgid="2075645297847971154">"Si"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Non dispoñible"</item> + <item msgid="1909756493418256167">"Desactivada"</item> + <item msgid="4531508423703413340">"Activada"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Non dispoñible"</item> <item msgid="9103697205127645916">"Non"</item> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index da52c40e505a..5f262b57e67c 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"વાઇ-ફાઇ કનેક્ટ નથી"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"તેજ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"વિપરીત રંગમાં બદલવું"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"રંગ સુધારણા"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"વધુ સેટિંગ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"વપરાશકર્તા સેટિંગ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"થઈ ગયું"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ઉપયોગ કરવા માટે અનલૉક કરો"</string> <string name="wallet_error_generic" msgid="257704570182963611">"તમારા કાર્ડની માહિતી મેળવવામાં સમસ્યા આવી હતી, કૃપા કરીને થોડા સમય પછી ફરી પ્રયાસ કરો"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"લૉક સ્ક્રીનના સેટિંગ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR સ્કૅન કરો"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR કોડ સ્કૅન કરવા માટે ક્લિક કરો"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"સ્કૅન કરવા માટે ટૅપ કરો"</string> <string name="status_bar_work" msgid="5238641949837091056">"ઑફિસની પ્રોફાઇલ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"એરપ્લેન મોડ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"તમે <xliff:g id="WHEN">%1$s</xliff:g> એ તમારો આગલો એલાર્મ સાંભળશો નહીં"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> પર <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"છેલ્લો ફેરફાર રદ કરો"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવા માટે વધુ નજીક ખસેડો"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવામાં આવી રહ્યું છે"</string> <string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string> <string name="controls_error_removed" msgid="6675638069846014366">"મળ્યું નથી"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"વપરાશકર્તા પસંદ કરો"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"બૅકગ્રાઉન્ડમાં ચાલતી ઍપ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"રોકો"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-gu/tiles_states_strings.xml b/packages/SystemUI/res/values-gu/tiles_states_strings.xml index 15b0f9fe7fbe..10e7ac7b7de0 100644 --- a/packages/SystemUI/res/values-gu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-gu/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"બંધ છે"</item> <item msgid="2075645297847971154">"ચાલુ છે"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"અનુપલબ્ધ"</item> + <item msgid="1909756493418256167">"બંધ છે"</item> + <item msgid="4531508423703413340">"ચાલુ છે"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ઉપલબ્ધ નથી"</item> <item msgid="9103697205127645916">"બંધ છે"</item> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 54b73897332d..755c4805be36 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाई-फ़ाई कनेक्ट नहीं है"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"स्क्रीन की रोशनी"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"रंग बदलने की सुविधा"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग में सुधार करने की सुविधा"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"और सेटिंग"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"उपयोगकर्ता सेटिंग"</string> <string name="quick_settings_done" msgid="2163641301648855793">"हो गया"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"इस्तेमाल करने के लिए, डिवाइस अनलॉक करें"</string> <string name="wallet_error_generic" msgid="257704570182963611">"आपके कार्ड की जानकारी पाने में कोई समस्या हुई है. कृपया बाद में कोशिश करें"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन की सेटिंग"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"क्यूआर कोड स्कैन करें"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"क्यूआर कोड स्कैन करने के लिए क्लिक करें"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"वर्क प्रोफ़ाइल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"हवाई जहाज़ मोड"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"आपको <xliff:g id="WHEN">%1$s</xliff:g> पर अपना अगला अलार्म नहीं सुनाई देगा"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> पर, <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"पहले जैसा करें"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर गाने चलाने के लिए उसके पास जाएं"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर चल रहा है"</string> <string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string> <string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल नहीं है"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"उपयोगकर्ता चुनें"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"बैकग्राउंड में चल रहे ऐप्लिकेशन"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"बंद करें"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hi/tiles_states_strings.xml b/packages/SystemUI/res/values-hi/tiles_states_strings.xml index 00fa69936dc6..e52ee17c5100 100644 --- a/packages/SystemUI/res/values-hi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hi/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"बंद है"</item> <item msgid="2075645297847971154">"चालू है"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"उपलब्ध नहीं है"</item> + <item msgid="1909756493418256167">"बंद है"</item> + <item msgid="4531508423703413340">"चालू है"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"उपलब्ध नहीं है"</item> <item msgid="9103697205127645916">"बंद है"</item> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 8931d20dda9a..a5be1e360589 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi mreža nije povezana"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svjetlina"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Više postavki"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisničke postavke"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> @@ -453,8 +452,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da biste koristili"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Pojavio se problem prilikom dohvaćanja kartica, pokušajte ponovo kasnije"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključanog zaslona"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skeniraj QR kôd"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknite da biste skenirali QR kôd"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u zrakoplovu"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +799,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Približite se radi reprodukcije na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, provjerite aplik."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> @@ -886,4 +888,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odabir korisnika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije koje se izvode u pozadini"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hr/tiles_states_strings.xml b/packages/SystemUI/res/values-hr/tiles_states_strings.xml index 730081667113..eb9ae525fa2e 100644 --- a/packages/SystemUI/res/values-hr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Isključeno"</item> <item msgid="2075645297847971154">"Uključeno"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nedostupno"</item> + <item msgid="1909756493418256167">"Isključeno"</item> + <item msgid="4531508423703413340">"Uključeno"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nedostupno"</item> <item msgid="9103697205127645916">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index eff9e8222e9e..0275b729ad31 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nem kapcsolódik Wi‑Fi-hálózathoz"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Fényerő"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Színek invertálása"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Színjavítás"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"További beállítások"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Felhasználói beállítások"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Kész"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Oldja fel a használathoz"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Probléma merült fel a kártyák lekérésekor, próbálja újra később"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lezárási képernyő beállításai"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-kód beolvasása"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kattintson a QR-kód beolvasához"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Munkahelyi profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Repülős üzemmód"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nem fogja hallani az ébresztést ekkor: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> lejátszása innen: <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Visszavonás"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Menjen közelebb a következőn való lejátszáshoz: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Lejátszás folyamatban a(z) <xliff:g id="DEVICENAME">%1$s</xliff:g> eszközön"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nem található"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Felhasználóválasztás"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Több alkalmazás is fut a háttérben"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Leállítás"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hu/tiles_states_strings.xml b/packages/SystemUI/res/values-hu/tiles_states_strings.xml index 52450e4b3937..ba92bfd3bf74 100644 --- a/packages/SystemUI/res/values-hu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hu/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Ki"</item> <item msgid="2075645297847971154">"Be"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nem áll rendelkezésre"</item> + <item msgid="1909756493418256167">"Ki"</item> + <item msgid="4531508423703413340">"Be"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nem áll rendelkezésre"</item> <item msgid="9103697205127645916">"Ki"</item> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 084b309e3751..c32b16686eba 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-ը միացված չէ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Պայծառություն"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Գունաշրջում"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Գունաշտկում"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Հավելյալ կարգավորումներ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Օգտատիրոջ կարգավորումներ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Պատրաստ է"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ապակողպել՝ օգտագործելու համար"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Չհաջողվեց բեռնել քարտերը։ Նորից փորձեք։"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Կողպէկրանի կարգավորումներ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR կոդերի սկանավորում"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Սեղմեք՝ QR կոդը սկանավորելու համար"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Հպեք՝ սկանավորելու համար"</string> <string name="status_bar_work" msgid="5238641949837091056">"Android for Work-ի պրոֆիլ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Ավիառեժիմ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ժամը <xliff:g id="WHEN">%1$s</xliff:g>-ի զարթուցիչը չի զանգի"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="APP_LABEL">%2$s</xliff:g> հավելվածից"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Հետարկել"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Ավելի մոտ եկեք՝ <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում նվագարկելու համար"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Նվագարկվում է <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Չի գտնվել"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Ընտրեք օգտատեր"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ֆոնային ռեժիմում աշխատող հավելվածներ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Դադարեցնել"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hy/tiles_states_strings.xml b/packages/SystemUI/res/values-hy/tiles_states_strings.xml index 23a096b194af..b52646f352b5 100644 --- a/packages/SystemUI/res/values-hy/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hy/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Անջատված է"</item> <item msgid="2075645297847971154">"Միացված է"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Անհասանելի է"</item> + <item msgid="1909756493418256167">"Անջատված է"</item> + <item msgid="4531508423703413340">"Միացված է"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Հասանելի չէ"</item> <item msgid="9103697205127645916">"Անջատված է"</item> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index e595045e913b..24f8698cf7ee 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi tidak terhubung"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversi warna"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koreksi warna"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Setelan lainnya"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setelan pengguna"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Terjadi masalah saat mendapatkan kartu Anda, coba lagi nanti"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setelan layar kunci"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Pindai QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik untuk memindai kode QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode pesawat"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar alarm berikutnya <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> dari <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Urungkan"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Dekatkan untuk memutar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Diputar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikasi berjalan di latar belakang"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Berhenti"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-in/tiles_states_strings.xml b/packages/SystemUI/res/values-in/tiles_states_strings.xml index c296e5443190..0007dfc3b581 100644 --- a/packages/SystemUI/res/values-in/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-in/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Nonaktif"</item> <item msgid="2075645297847971154">"Aktif"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Tidak tersedia"</item> + <item msgid="1909756493418256167">"Nonaktif"</item> + <item msgid="4531508423703413340">"Aktif"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Tidak tersedia"</item> <item msgid="9103697205127645916">"Nonaktif"</item> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 43ae1b651f0e..a7a43e6f7562 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ekki tengt"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Birtustig"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Umsnúningur lita"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Litaleiðrétting"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Fleiri stillingar"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Notandastillingar"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Lokið"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Taktu úr lás til að nota"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Vandamál kom upp við að sækja kortin þín. Reyndu aftur síðar"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Stillingar fyrir læstan skjá"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skanna QR-kóða"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Smelltu til að skanna QR-kóða"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Vinnusnið"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flugstilling"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ekki mun heyrast í vekjaranum <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> í <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> í <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Afturkalla"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Færðu nær til að spila í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Spilast í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Fannst ekki"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velja notanda"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Forrit keyra í bakgrunni"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stöðva"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-is/tiles_states_strings.xml b/packages/SystemUI/res/values-is/tiles_states_strings.xml index 25b3dcc53661..88472ef4b2fc 100644 --- a/packages/SystemUI/res/values-is/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-is/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Slökkt"</item> <item msgid="2075645297847971154">"Kveikt"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Ekki í boði"</item> + <item msgid="1909756493418256167">"Slökkt"</item> + <item msgid="4531508423703413340">"Kveikt"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Ekki í boði"</item> <item msgid="9103697205127645916">"Slökkt"</item> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 00d7fb35d15e..2f3d50ff1816 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nessuna connessione Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosità"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversione dei colori"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correzione del colore"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Altre impostazioni"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Impostazioni utente"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Fine"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Sblocca per usare"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Si è verificato un problema durante il recupero delle tue carte. Riprova più tardi."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Impostazioni schermata di blocco"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scansiona QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Fai clic per scansionare un codice QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tocca per scansionare"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profilo di lavoro"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modalità aereo"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Non sentirai la tua prossima sveglia <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> da <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annulla"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Avvicinati per riprodurre su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"In riproduzione su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Controllo non trovato"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleziona utente"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"App in esecuzione in background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Interrompi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-it/tiles_states_strings.xml b/packages/SystemUI/res/values-it/tiles_states_strings.xml index 7e6827a4d8c2..071a970d2260 100644 --- a/packages/SystemUI/res/values-it/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-it/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Off"</item> <item msgid="2075645297847971154">"On"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Non disponibile"</item> + <item msgid="1909756493418256167">"Off"</item> + <item msgid="4531508423703413340">"On"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Non disponibile"</item> <item msgid="9103697205127645916">"Off"</item> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index b150eb7e6757..757ff778a018 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"אין חיבור ל-Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"בהירות"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"היפוך צבעים"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"תיקון צבע"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"הגדרות נוספות"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"הגדרות המשתמש"</string> <string name="quick_settings_done" msgid="2163641301648855793">"בוצע"</string> @@ -456,8 +455,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"יש לבטל את הנעילה כדי להשתמש"</string> <string name="wallet_error_generic" msgid="257704570182963611">"הייתה בעיה בקבלת הכרטיסים שלך. כדאי לנסות שוב מאוחר יותר"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"הגדרות מסך הנעילה"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"סריקת קוד QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"צריך ללחוץ כאן כדי לסרוק קוד QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"פרופיל עבודה"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"מצב טיסה"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"לא ניתן יהיה לשמוע את ההתראה הבאה שלך <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -804,7 +805,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> מ-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ביטול"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"צריך להתקרב כדי להפעיל מוזיקה במכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"ההפעלה הועברה למכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"לא פעיל, יש לבדוק את האפליקציה"</string> <string name="controls_error_removed" msgid="6675638069846014366">"לא נמצא"</string> @@ -892,4 +894,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"בחירת משתמש"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"אפליקציות שפועלות ברקע"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"עצירה"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-iw/tiles_states_strings.xml b/packages/SystemUI/res/values-iw/tiles_states_strings.xml index bf3c2b6ad767..49fb4b671546 100644 --- a/packages/SystemUI/res/values-iw/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-iw/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"כבוי"</item> <item msgid="2075645297847971154">"פועל"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"לא זמין"</item> + <item msgid="1909756493418256167">"כבוי"</item> + <item msgid="4531508423703413340">"פועל"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"לא זמין"</item> <item msgid="9103697205127645916">"כבוי"</item> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 1dde2100db8d..f219de3824b8 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi 未接続"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"画面の明るさ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色反転"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色補正"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"詳細設定"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ユーザー設定"</string> <string name="quick_settings_done" msgid="2163641301648855793">"完了"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ロックを解除して使用"</string> <string name="wallet_error_generic" msgid="257704570182963611">"カードの取得中に問題が発生しました。しばらくしてからもう一度お試しください"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ロック画面の設定"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR のスキャン"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"クリックすると、QR コードをスキャンします"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR コード"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"タップしてスキャンします"</string> <string name="status_bar_work" msgid="5238641949837091056">"仕事用プロファイル"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"機内モード"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"次回のアラーム(<xliff:g id="WHEN">%1$s</xliff:g>)は鳴りません"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)を <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> を <xliff:g id="APP_LABEL">%2$s</xliff:g> で再生"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"元に戻す"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生するにはもっと近づけてください"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生しています"</string> <string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string> <string name="controls_error_removed" msgid="6675638069846014366">"見つかりませんでした"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ユーザーの選択"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"バックグラウンドで実行中のアプリ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ja/tiles_states_strings.xml b/packages/SystemUI/res/values-ja/tiles_states_strings.xml index 9197aab790b0..55cbe8ba868a 100644 --- a/packages/SystemUI/res/values-ja/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ja/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"OFF"</item> <item msgid="2075645297847971154">"ON"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"使用不可"</item> + <item msgid="1909756493418256167">"OFF"</item> + <item msgid="4531508423703413340">"ON"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"使用不可"</item> <item msgid="9103697205127645916">"OFF"</item> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 304c19d2af38..b932c452207a 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi არ არის დაკავშირებული"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"განათება"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ფერთა ინვერსია"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ფერთა კორექცია"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"დამატებითი პარამეტრები"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"მომხმარებლის პარამეტრები"</string> <string name="quick_settings_done" msgid="2163641301648855793">"დასრულდა"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"გამოსაყენებლად განბლოკვა"</string> <string name="wallet_error_generic" msgid="257704570182963611">"თქვენი ბარათების მიღებისას პრობლემა წარმოიშვა. ცადეთ ხელახლა მოგვიანებით"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ჩაკეტილი ეკრანის პარამეტრები"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-ის სკანირება"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"დააწკაპუნეთ QR კოდის სკანირებისთვის"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR კოდი"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"შეეხეთ დასასკანირებლად"</string> <string name="status_bar_work" msgid="5238641949837091056">"სამსახურის პროფილი"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"თვითმფრინავის რეჟიმი"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ვერ გაიგონებთ მომდევნო მაღვიძარას <xliff:g id="WHEN">%1$s</xliff:g>-ზე"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g>-დან"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"მოქმედების გაუქმება"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"მიიტანეტ უფრო ახლოს, რომ დაუკრათ <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"მიმდინარეობს დაკვრა <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string> <string name="controls_error_timeout" msgid="794197289772728958">"არააქტიურია, გადაამოწმეთ აპი"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ვერ მოიძებნა"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"მომხმარებლის არჩევა"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ფონურად მომუშავე აპები"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"შეწყვეტა"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ka/tiles_states_strings.xml b/packages/SystemUI/res/values-ka/tiles_states_strings.xml index 485c3de7bdcf..34caeff0a9b9 100644 --- a/packages/SystemUI/res/values-ka/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ka/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"გამორთულია"</item> <item msgid="2075645297847971154">"ჩართულია"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"მიუწვდომელია"</item> + <item msgid="1909756493418256167">"გამორთვა"</item> + <item msgid="4531508423703413340">"ჩართვა"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"მიუწვდომელია"</item> <item msgid="9103697205127645916">"გამორთულია"</item> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 90865f0169b4..1ac6c7ec66af 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi желісіне жалғанбаған"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарықтығы"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түс инверсиясы"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсті түзету"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Қосымша параметрлер"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Пайдаланушы параметрлері"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Дайын"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Пайдалану үшін құлыпты ашу"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Карталарыңыз алынбады, кейінірек қайталап көріңіз."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Экран құлпының параметрлері"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR кодын сканерлеу"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR кодын сканерлеу үшін басыңыз."</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Сканерлеу үшін түртіңіз."</string> <string name="status_bar_work" msgid="5238641949837091056">"Жұмыс профилі"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Ұшақ режимі"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Келесі <xliff:g id="WHEN">%1$s</xliff:g> дабылыңызды есітпейсіз"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> қолданбасында \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Қайтару"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында музыка ойнату үшін оған жақындаңыз."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында ойнатылуда."</string> <string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Табылмады"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Пайдаланушыны таңдау"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Фондық режимде жұмыс істеп тұрған қолданбалар"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Тоқтату"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-kk/tiles_states_strings.xml b/packages/SystemUI/res/values-kk/tiles_states_strings.xml index b143632803cb..616ad5362e63 100644 --- a/packages/SystemUI/res/values-kk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-kk/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Өшірулі"</item> <item msgid="2075645297847971154">"Қосулы"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Қолжетімді емес"</item> + <item msgid="1909756493418256167">"Өшірулі"</item> + <item msgid="4531508423703413340">"Қосулы"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Қолжетімсіз"</item> <item msgid="9103697205127645916">"Өшірулі"</item> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 4b669998723b..bc69da90b510 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"មិនមានការតភ្ជាប់ Wi-Fi ទេ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ពន្លឺ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ការបញ្ច្រាសពណ៌"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ការកែតម្រូវពណ៌"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ការកំណត់ច្រើនទៀត"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ការកំណត់អ្នកប្រើប្រាស់"</string> <string name="quick_settings_done" msgid="2163641301648855793">"រួចរាល់"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ដោះសោដើម្បីប្រើប្រាស់"</string> <string name="wallet_error_generic" msgid="257704570182963611">"មានបញ្ហាក្នុងការទាញយកកាតរបស់អ្នក សូមព្យាយាមម្ដងទៀតនៅពេលក្រោយ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ការកំណត់អេក្រង់ចាក់សោ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"ស្កេនកូដ QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"ចុចដើម្បីស្កេនកូដ QR"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"កូដ QR"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ចុចដើម្បីស្កេន"</string> <string name="status_bar_work" msgid="5238641949837091056">"ប្រវត្តិរូបការងារ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ពេលជិះយន្តហោះ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"អ្នកនឹងមិនលឺម៉ោងរោទ៍ <xliff:g id="WHEN">%1$s</xliff:g> បន្ទាប់របស់អ្នកទេ"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ពី <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ត្រឡប់វិញ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"រំកិលឱ្យកាន់តែជិត ដើម្បីចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"កំពុងចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើលកម្មវិធី"</string> <string name="controls_error_removed" msgid="6675638069846014366">"រកមិនឃើញទេ"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ជ្រើសរើសអ្នកប្រើប្រាស់"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"កម្មវិធីដែលកំពុងដំណើរការនៅផ្ទៃខាងក្រោយ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ឈប់"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-km/tiles_states_strings.xml b/packages/SystemUI/res/values-km/tiles_states_strings.xml index 38d3894d07e1..b1a1a8fbcd44 100644 --- a/packages/SystemUI/res/values-km/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-km/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"បិទ"</item> <item msgid="2075645297847971154">"បើក"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"មិនអាចកែតម្រូវបានទេ"</item> + <item msgid="1909756493418256167">"បិទ"</item> + <item msgid="4531508423703413340">"បើក"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"មិនមានទេ"</item> <item msgid="9103697205127645916">"បិទ"</item> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index bbb9ef1bbfc9..c388d82ab962 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ವೈ-ಫೈ ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ಕಲರ್ ಇನ್ವರ್ಶನ್"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ಹೆಚ್ಚಿನ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ಬಳಕೆದಾರರ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ಮುಗಿದಿದೆ"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ಬಳಸಲು ಅನ್ಲಾಕ್ ಮಾಡಿ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ನಿಮ್ಮ ಕಾರ್ಡ್ಗಳನ್ನು ಪಡೆಯುವಾಗ ಸಮಸ್ಯೆ ಉಂಟಾಗಿದೆ, ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ಲಾಕ್ ಸ್ಕ್ರ್ರೀನ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR ಕೋಡ್ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR ಕೋಡ್ ಅನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ಏರ್ಪ್ಲೇನ್ ಮೋಡ್"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ನಿಮ್ಮ ಮುಂದಿನ <xliff:g id="WHEN">%1$s</xliff:g> ಅಲಾರಮ್ ಅನ್ನು ನೀವು ಆಲಿಸುವುದಿಲ್ಲ"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%2$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ರದ್ದುಗೊಳಿಸಿ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು ಅದರ ಹತ್ತಿರಕ್ಕೆ ಸರಿಯಿರಿ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತಿದೆ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ಕಂಡುಬಂದಿಲ್ಲ"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ಬಳಕೆದಾರ ಆಯ್ಕೆಮಾಡಿ"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ಹಿನ್ನೆಲೆಯಲ್ಲಿ ರನ್ ಆಗುತ್ತಿರುವ ಆ್ಯಪ್ಗಳು"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ನಿಲ್ಲಿಸಿ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml index 022c5cf35f5c..e5bf6efc4edd 100644 --- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ಆಫ್ ಮಾಡಿ"</item> <item msgid="2075645297847971154">"ಆನ್ ಮಾಡಿ"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ಲಭ್ಯವಿಲ್ಲ"</item> + <item msgid="1909756493418256167">"ಆಫ್ ಮಾಡಿ"</item> + <item msgid="4531508423703413340">"ಆನ್ ಮಾಡಿ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ಲಭ್ಯವಿಲ್ಲ"</item> <item msgid="9103697205127645916">"ಆಫ್ ಮಾಡಿ"</item> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 4275960b8cbc..16b5447df366 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi가 연결되지 않음"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"밝기"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"색상 반전"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"색상 보정"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"설정 더보기"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"사용자 설정"</string> <string name="quick_settings_done" msgid="2163641301648855793">"완료"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"잠금 해제하여 사용"</string> <string name="wallet_error_generic" msgid="257704570182963611">"카드를 가져오는 중에 문제가 발생했습니다. 나중에 다시 시도해 보세요."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"잠금 화면 설정"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR 스캔"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR 코드를 스캔하려면 클릭하세요."</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"직장 프로필"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"비행기 모드"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>에 다음 알람을 들을 수 없습니다."</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>에서 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"실행취소"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생하려면 기기를 더 가까이로 옮기세요."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생 중"</string> <string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string> <string name="controls_error_removed" msgid="6675638069846014366">"찾을 수 없음"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"사용자 선택"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"백그라운드에서 실행 중인 앱"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"중지"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ko/tiles_states_strings.xml b/packages/SystemUI/res/values-ko/tiles_states_strings.xml index ae6f148270c5..595b12c4baa0 100644 --- a/packages/SystemUI/res/values-ko/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ko/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"꺼짐"</item> <item msgid="2075645297847971154">"켜짐"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"사용할 수 없음"</item> + <item msgid="1909756493418256167">"꺼짐"</item> + <item msgid="4531508423703413340">"켜짐"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"이용 불가"</item> <item msgid="9103697205127645916">"꺼짐"</item> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 448fb843ea2c..31c8522de2bc 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi туташкан жок"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарыктыгы"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түстү инверсиялоо"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсүн тууралоо"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Дагы жөндөөлөр"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Колдонуучунун жөндөөлөрү"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Бүттү"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Колдонуу үчүн кулпусун ачыңыз"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Кыйытмаларды алууда ката кетти. Бир аздан кийин кайталап көрүңүз."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Кулпуланган экран жөндөөлөрү"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR кодун скандоо"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR кодун скандоо үчүн чыкылдатыңыз"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Скандоо үчүн таптап коюңуз"</string> <string name="status_bar_work" msgid="5238641949837091056">"Жумуш профили"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Учак режими"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> боло турган кийинки эскертмени укпайсыз"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын <xliff:g id="APP_LABEL">%2$s</xliff:g> колдонмосунан ойнотуу"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Кайтаруу"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүндө ойнотуу үчүн жакыныраак жылдырыңыз"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> аркылуу ойнотулууда"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Табылган жок"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Колдонуучуну тандоо"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Фондо иштеп жаткан колдонмолор"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Токтотуу"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ky/tiles_states_strings.xml b/packages/SystemUI/res/values-ky/tiles_states_strings.xml index 0eadc34e37ba..3bcbf531d14a 100644 --- a/packages/SystemUI/res/values-ky/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ky/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Өчүк"</item> <item msgid="2075645297847971154">"Күйүк"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Жеткиликсиз"</item> + <item msgid="1909756493418256167">"Өчүк"</item> + <item msgid="4531508423703413340">"Күйүк"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Жеткиликсиз"</item> <item msgid="9103697205127645916">"Өчүк"</item> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 702b76e1e7ec..f3884bc110ad 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ບໍ່ໄດ້ເຊື່ອມຕໍ່ Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ຄວາມແຈ້ງ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ການປີ້ນສີ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ການແກ້ໄຂສີ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ການຕັ້ງຄ່າເພີ່ມເຕີມ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ຕັ້ງຄ່າຜູ້ໃຊ້"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ແລ້ວໆ"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ປົດລັອກເພື່ອໃຊ້"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ເກີດບັນຫາໃນການໂຫຼດບັດຂອງທ່ານ, ກະລຸນາລອງໃໝ່ໃນພາຍຫຼັງ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ການຕັ້ງຄ່າໜ້າຈໍລັອກ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"ສະແກນ QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"ຄລິກເພື່ອສະແກນລະຫັດ QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ແຕະເພື່ອສະແກນ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ໂໝດເຮືອບິນ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ທ່ານຈະບໍ່ໄດ້ຍິນສຽງໂມງປ <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ຍົກເລີກ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"ຍ້າຍໄປໃກ້ຂຶ້ນເພື່ອຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"ກຳລັງຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ບໍ່ພົບ"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ເລືອກຜູ້ໃຊ້"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ແອັບທີ່ກຳລັງເອີ້ນໃຊ້ໃນພື້ນຫຼັງ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ຢຸດ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-lo/tiles_states_strings.xml b/packages/SystemUI/res/values-lo/tiles_states_strings.xml index 5fe5cfff03bf..0cb8afd2aca3 100644 --- a/packages/SystemUI/res/values-lo/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lo/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ປິດ"</item> <item msgid="2075645297847971154">"ເປີດ"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ບໍ່ສາມາດໃຊ້ໄດ້"</item> + <item msgid="1909756493418256167">"ປິດ"</item> + <item msgid="4531508423703413340">"ເປີດ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ບໍ່ສາມາດໃຊ້ໄດ້"</item> <item msgid="9103697205127645916">"ປິດ"</item> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index eb593623d363..f2918d58e837 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"„Wi-Fi“ neprijungtas"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Šviesumas"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Spalvų inversija"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Spalvų taisymas"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Daugiau nustatymų"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Naudotojo nustatymai"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Atlikta"</string> @@ -456,8 +455,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Atrakinti, kad būtų galima naudoti"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Gaunant korteles kilo problema, bandykite dar kartą vėliau"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Užrakinimo ekrano nustatymai"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Nuskaityti QR kodą"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Spustelėkite, kad nuskaitytumėte QR kodą"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Palieskite, kad nuskaitytumėte"</string> <string name="status_bar_work" msgid="5238641949837091056">"Darbo profilis"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lėktuvo režimas"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Negirdėsite kito signalo <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -804,7 +804,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Leisti <xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Leisti „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%2$s</xliff:g>“"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anuliuoti"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Prieikite arčiau, kad galėtumėte leisti įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Leidžiama įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nerasta"</string> @@ -892,4 +893,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Naudotojo pasirinkimas"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Fone veikiančios programos"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Sustabdyti"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-lt/tiles_states_strings.xml b/packages/SystemUI/res/values-lt/tiles_states_strings.xml index 7a0caa9c9afa..44a3fd52a6ef 100644 --- a/packages/SystemUI/res/values-lt/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lt/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Išjungta"</item> <item msgid="2075645297847971154">"Įjungta"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nepasiekiama"</item> + <item msgid="1909756493418256167">"Išjungta"</item> + <item msgid="4531508423703413340">"Įjungta"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nepasiekiama"</item> <item msgid="9103697205127645916">"Išjungta"</item> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index a27036e7728b..86a5df8ff901 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nav izveidots savienojums ar Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Spilgtums"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Krāsu inversija"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Krāsu korekcija"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Vairāk iestatījumu"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Lietotāja iestatījumi"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gatavs"</string> @@ -453,8 +452,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lai izmantotu, atbloķējiet ekrānu"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ienesot jūsu kartes, radās problēma. Lūdzu, vēlāk mēģiniet vēlreiz."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Bloķēšanas ekrāna iestatījumi"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Ātrās atbildes koda skeneris"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Noklikšķiniet, lai skenētu ātrās atbildes kodu."</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Darba profils"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lidojuma režīms"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nākamais signāls (<xliff:g id="WHEN">%1$s</xliff:g>) netiks atskaņots."</string> @@ -798,7 +799,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” no lietotnes <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Atsaukt"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Pārvietojiet savu ierīci tuvāk, lai atskaņotu mūziku ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Notiek atskaņošana ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Netika atrasta"</string> @@ -886,4 +888,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Lietotāja atlase"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Lietotnes, kas darbojas fonā"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Apturēt"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-lv/tiles_states_strings.xml b/packages/SystemUI/res/values-lv/tiles_states_strings.xml index 872dba60ca1d..35264ae2459d 100644 --- a/packages/SystemUI/res/values-lv/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lv/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Izslēgts"</item> <item msgid="2075645297847971154">"Ieslēgts"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nav pieejama"</item> + <item msgid="1909756493418256167">"Izslēgta"</item> + <item msgid="4531508423703413340">"Ieslēgta"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nav pieejama"</item> <item msgid="9103697205127645916">"Izslēgta"</item> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 7f60c25f5bc7..b21f18375222 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi не е поврзано"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветленост"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија на боите"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција на боите"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Повеќе поставки"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Поставки на корисникот"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отклучете за да користите"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Имаше проблем при преземањето на картичките. Обидете се повторно подоцна"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Поставки за заклучен екран"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Скенирајте QR-код"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Кликнете за да скенирате QR-код"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-код"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Допрете за скенирање"</string> <string name="status_bar_work" msgid="5238641949837091056">"Работен профил"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Авионски режим"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Нема да го слушнете следниот аларм <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Врати"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Приближете се за да пуштите на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Се репродуцира на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивна, провери апликација"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не е најдено"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изберете корисник"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Апликации се извршуваат во заднина"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Крај"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-mk/tiles_states_strings.xml b/packages/SystemUI/res/values-mk/tiles_states_strings.xml index 65e94f371e7f..c2c6f5dd1f23 100644 --- a/packages/SystemUI/res/values-mk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mk/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Исклучено"</item> <item msgid="2075645297847971154">"Вклучено"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недостапна"</item> + <item msgid="1909756493418256167">"Исклучена"</item> + <item msgid="4531508423703413340">"Вклучена"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Недостапно"</item> <item msgid="9103697205127645916">"Исклучено"</item> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index a65593cef66a..798ece02b4f7 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"വൈഫൈ കണക്റ്റ് ചെയ്തിട്ടില്ല"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"നിറം വിപരീതമാക്കൽ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"നിറം ശരിയാക്കൽ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"കൂടുതൽ ക്രമീകരണങ്ങൾ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ഉപയോക്തൃ ക്രമീകരണം"</string> <string name="quick_settings_done" msgid="2163641301648855793">"പൂർത്തിയാക്കി"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ഉപയോഗിക്കാൻ അൺലോക്ക് ചെയ്യുക"</string> <string name="wallet_error_generic" msgid="257704570182963611">"നിങ്ങളുടെ കാർഡുകൾ ലഭ്യമാക്കുന്നതിൽ ഒരു പ്രശ്നമുണ്ടായി, പിന്നീട് വീണ്ടും ശ്രമിക്കുക"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ലോക്ക് സ്ക്രീൻ ക്രമീകരണം"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR സ്കാൻ ചെയ്യുക"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR കോഡ് സ്കാൻ ചെയ്യാൻ ക്ലിക്ക് ചെയ്യുക"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR കോഡ്"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"സ്കാൻ ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> <string name="status_bar_work" msgid="5238641949837091056">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ഫ്ലൈറ്റ് മോഡ്"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-നുള്ള നിങ്ങളുടെ അടുത്ത അലാറം കേൾക്കില്ല"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%2$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"പഴയപടിയാക്കുക"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യാൻ അടുത്തേക്ക് നീക്കുക"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string> <string name="controls_error_timeout" msgid="794197289772728958">"നിഷ്ക്രിയം, ആപ്പ് പരിശോധിക്കൂ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"കണ്ടെത്തിയില്ല"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ഉപയോക്താവിനെ തിരഞ്ഞെടുക്കൂ"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ആപ്പുകൾ പശ്ചാത്തലത്തിൽ റൺ ചെയ്യുന്നു"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"നിർത്തുക"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ml/tiles_states_strings.xml b/packages/SystemUI/res/values-ml/tiles_states_strings.xml index 8746c74bd00a..c683c1b14c7d 100644 --- a/packages/SystemUI/res/values-ml/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ml/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ഓഫാണ്"</item> <item msgid="2075645297847971154">"ഓണാണ്"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ലഭ്യമല്ല"</item> + <item msgid="1909756493418256167">"ഓഫാണ്"</item> + <item msgid="4531508423703413340">"ഓണാണ്"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ലഭ്യമല്ല"</item> <item msgid="9103697205127645916">"ഓഫാണ്"</item> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 9cc0b3f0628b..d283916dd0e8 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-д холбогдоогүй байна"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Тодрол"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Өнгө урвуулах"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Өнгөний засвар"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Бусад тохиргоо"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Хэрэглэгчийн тохиргоо"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Дууссан"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ашиглахын тулд түгжээг тайлах"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Таны картыг авахад асуудал гарлаа. Дараа дахин оролдоно уу"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Түгжигдсэн дэлгэцийн тохиргоо"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-г скан хийх"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR кодыг скан хийхийн тулд товшино уу"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR код"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Скан хийхийн тулд товшино уу"</string> <string name="status_bar_work" msgid="5238641949837091056">"Ажлын профайл"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Нислэгийн горим"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-т та дараагийн сэрүүлгээ сонсохгүй"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулах"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%2$s</xliff:g> дээр тоглуулах"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Болих"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулахын тулд төхөөрөмжөө ойртуулна уу"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулж байна"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Олдсонгүй"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Хэрэглэгч сонгох"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ард ажиллаж байгаа аппууд"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Зогсоох"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-mn/tiles_states_strings.xml b/packages/SystemUI/res/values-mn/tiles_states_strings.xml index 07dde9f76a98..7e01fbd139d3 100644 --- a/packages/SystemUI/res/values-mn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mn/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Унтраалттай"</item> <item msgid="2075645297847971154">"Асаалттай"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Боломжгүй"</item> + <item msgid="1909756493418256167">"Унтраалттай"</item> + <item msgid="4531508423703413340">"Асаалттай"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Боломжгүй"</item> <item msgid="9103697205127645916">"Унтраалттай"</item> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 4d3258785b60..933a0e406518 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाय-फाय नाही"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"चमक"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्व्हर्जन"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग सुधारणा"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"अधिक सेटिंग्ज"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"वापरकर्ता सेटिंग्ज"</string> <string name="quick_settings_done" msgid="2163641301648855793">"पूर्ण झाले"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"वापरण्यासाठी अनलॉक करा"</string> <string name="wallet_error_generic" msgid="257704570182963611">"तुमची कार्ड मिळवताना समस्या आली, कृपया नंतर पुन्हा प्रयत्न करा"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन सेटिंग्ज"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR स्कॅन करा"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR कोड स्कॅन करण्यासाठी क्लिक करा"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR कोड"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"स्कॅन करण्यासाठी टॅप करा"</string> <string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाईल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"विमान मोड"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"तुम्ही तुमचा <xliff:g id="WHEN">%1$s</xliff:g> वाजता होणारा पुढील अलार्म ऐकणार नाही"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> मध्ये <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"पहिल्यासारखे करा"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले करण्यासाठी जवळ जा"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले केला जात आहे"</string> <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string> <string name="controls_error_removed" msgid="6675638069846014366">"आढळले नाही"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"वापरकर्ता निवडा"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ॲप्स बॅकग्राउंडमध्ये रन होत आहेत"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"थांबवा"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-mr/tiles_states_strings.xml b/packages/SystemUI/res/values-mr/tiles_states_strings.xml index f0ca33356bb6..7fd88cceecc9 100644 --- a/packages/SystemUI/res/values-mr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"बंद आहे"</item> <item msgid="2075645297847971154">"सुरू आहे"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"उपलब्ध नाही"</item> + <item msgid="1909756493418256167">"बंद आहे"</item> + <item msgid="4531508423703413340">"सुरू आहे"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"उपलब्ध नाही"</item> <item msgid="9103697205127645916">"बंद आहे"</item> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 355580406b95..4cf476b5db32 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tidak disambungkan"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Penyongsangan warna"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pembetulan warna"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Lagi tetapan"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Tetapan pengguna"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Terdapat masalah sewaktu mendapatkan kad anda. Sila cuba sebentar lagi"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Tetapan skrin kunci"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Imbas QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik untuk mengimbas kod QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Ketik untuk membuat imbasan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mod pesawat"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar penggera yang seterusnya <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> daripada <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Buat asal"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Alihkan lebih dekat untuk bermain pada<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Dimainkan pada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apl berjalan di latar"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Berhenti"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ms/tiles_states_strings.xml b/packages/SystemUI/res/values-ms/tiles_states_strings.xml index b682df1ca324..eaafd192506c 100644 --- a/packages/SystemUI/res/values-ms/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ms/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Mati"</item> <item msgid="2075645297847971154">"Hidup"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Tidak tersedia"</item> + <item msgid="1909756493418256167">"Mati"</item> + <item msgid="4531508423703413340">"Hidup"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Tidak tersedia"</item> <item msgid="9103697205127645916">"Mati"</item> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 26333edb77b1..8964ed529cb5 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ချိတ်ဆက်ထားခြင်းမရှိပါ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"အလင်းတောက်ပမှု"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"အရောင်ပြောင်းပြန်ပြုလုပ်ရန်"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"အရောင် အမှန်ပြင်ခြင်း"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"နောက်ထပ် ဆက်တင်များ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"အသုံးပြုသူ ဆက်တင်များ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ပြီးပါပြီ"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"သုံးရန် လော့ခ်ဖွင့်ပါ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"သင်၏ကတ်များ ရယူရာတွင် ပြဿနာရှိနေသည်၊ နောက်မှ ထပ်စမ်းကြည့်ပါ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"လော့ခ်မျက်နှာပြင် ဆက်တင်များ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR စကင်ဖတ်ခြင်း"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR ကုဒ် စကင်ဖတ်ရန် ကလစ်နှိပ်ပါ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"အလုပ် ပရိုဖိုင်"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"လေယာဉ်ပျံမုဒ်"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> ၌သင့်နောက်ထပ် နှိုးစက်ကို ကြားမည်မဟုတ်ပါ"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ပါ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%2$s</xliff:g> တွင် ဖွင့်ပါ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"နောက်ပြန်ရန်"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ရန် အနီးသို့ရွှေ့ပါ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ထားသည်"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"မတွေ့ပါ"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"အသုံးပြုသူ ရွေးခြင်း"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"နောက်ခံတွင် ဖွင့်ထားသောအက်ပ်များ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ရပ်ရန်"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-my/tiles_states_strings.xml b/packages/SystemUI/res/values-my/tiles_states_strings.xml index af8d55c8cd7f..dfc8ccca736f 100644 --- a/packages/SystemUI/res/values-my/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-my/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ပိတ်"</item> <item msgid="2075645297847971154">"ဖွင့်"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"မရနိုင်ပါ"</item> + <item msgid="1909756493418256167">"ပိတ်"</item> + <item msgid="4531508423703413340">"ဖွင့်"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"မရနိုင်ပါ"</item> <item msgid="9103697205127645916">"ပိတ်"</item> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 0102801191bf..6e66156a87ef 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi er ikke tilkoblet"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Flere innstillinger"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Brukerinnstillinger"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Ferdig"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås opp for å bruke"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Det oppsto et problem med henting av kortene. Prøv igjen senere"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Innstillinger for låseskjermen"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skann QR-kode"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klikk for å skanne en QR-kode"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Work-profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flymodus"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Du hører ikke neste innstilte alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> fra <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Angre"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Flytt nærmere for å spille av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Spilles av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ikke funnet"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velg bruker"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apper som kjører i bakgrunnen"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stopp"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-nb/tiles_states_strings.xml b/packages/SystemUI/res/values-nb/tiles_states_strings.xml index 619f6135d56f..38e10456d612 100644 --- a/packages/SystemUI/res/values-nb/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-nb/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Av"</item> <item msgid="2075645297847971154">"På"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Utilgjengelig"</item> + <item msgid="1909756493418256167">"Av"</item> + <item msgid="4531508423703413340">"På"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Utilgjengelig"</item> <item msgid="9103697205127645916">"Av"</item> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index dc220c7ccf77..db64a7e4879f 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi जडान गरिएको छैन"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"उज्यालपन"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्भर्सन"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रङ सच्याउने कार्य"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"थप सेटिङहरू"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"प्रयोगकर्तासम्बन्धी सेटिङ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"भयो"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"यो वालेट प्रयोग गर्न डिभाइस अनलक गर्नुहोस्"</string> <string name="wallet_error_generic" msgid="257704570182963611">"तपाईंका कार्डहरू प्राप्त गर्ने क्रममा समस्या भयो, कृपया पछि फेरि प्रयास गर्नुहोस्"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लक स्क्रिनसम्बन्धी सेटिङ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR स्क्यान गर्नुहोस्"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR कोड स्क्यान गर्न क्लिक गर्नुहोस्"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR कोड"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"स्क्यान गर्न ट्याप गर्नुहोस्"</string> <string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाइल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"हवाइजहाज मोड"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"तपाईँले आफ्नो अर्को अलार्म <xliff:g id="WHEN">%1$s</xliff:g> सुन्नुहुने छैन"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बजाउनुहोस्"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%2$s</xliff:g> मा बजाउनुहोस्"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"अन्डू गर्नुहोस्"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गर्न आफ्नो डिभाइस नजिकै लैजानुहोस्"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गरिँदै छ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string> <string name="controls_error_removed" msgid="6675638069846014366">"फेला परेन"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"प्रयोगकर्ता चयन गर्नु…"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"अहिले ब्याकग्राउन्डमा चलिरहेका एप"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"रोक्नुहोस्"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ne/tiles_states_strings.xml b/packages/SystemUI/res/values-ne/tiles_states_strings.xml index 808b58d31ea1..abe94e763481 100644 --- a/packages/SystemUI/res/values-ne/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ne/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"अफ छ"</item> <item msgid="2075645297847971154">"अन छ"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"उपलब्ध छैन"</item> + <item msgid="1909756493418256167">"अफ छ"</item> + <item msgid="4531508423703413340">"अन छ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"उपलब्ध छैन"</item> <item msgid="9103697205127645916">"अफ छ"</item> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index cb963e6ffc89..1f815b79ec30 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -38,6 +38,15 @@ <item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item> </style> + <!-- Clipboard overlay's edit text activity. --> + <style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowLightStatusBar">false</item> + <item name="android:windowLightNavigationBar">false</item> + <item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item> + <item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item> + </style> + <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight"> <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item> </style> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index e7dbd2ecb019..b895d9ebd542 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi niet verbonden"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleurinversie"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurcorrectie"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Meer instellingen"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Gebruikersinstellingen"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontgrendelen om te gebruiken"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Er is een probleem opgetreden bij het ophalen van je kaarten. Probeer het later opnieuw."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Instellingen voor vergrendelscherm"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-code scannen"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik om een QR-code te scannen"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tik om te scannen"</string> <string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Je hoort je volgende wekker niet <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ongedaan maken"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Ga dichter naar <xliff:g id="DEVICENAME">%1$s</xliff:g> toe om af te spelen"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Afspelen op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Niet gevonden"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Gebruiker selecteren"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps die op de achtergrond worden uitgevoerd"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stoppen"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-nl/tiles_states_strings.xml b/packages/SystemUI/res/values-nl/tiles_states_strings.xml index 92332ca81f4a..ac85f28daa37 100644 --- a/packages/SystemUI/res/values-nl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-nl/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Uit"</item> <item msgid="2075645297847971154">"Aan"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Niet beschikbaar"</item> + <item msgid="1909756493418256167">"Uit"</item> + <item msgid="4531508423703413340">"Aan"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Niet beschikbaar"</item> <item msgid="9103697205127645916">"Uit"</item> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 334b7565be67..e47513c3e8f8 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ୱାଇ-ଫାଇ ସଂଯୋଜିତ ହୋଇନାହିଁ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ଉଜ୍ଜ୍ୱଳତା"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ରଙ୍ଗ ଇନଭାର୍ସନ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ରଙ୍ଗ ସଂଶୋଧନ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ଅଧିକ ସେଟିଂସ୍"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ଉପଯୋଗକର୍ତ୍ତା ସେଟିଂସ୍"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ହୋଇଗଲା"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ବ୍ୟବହାର କରିବାକୁ ଅନଲକ୍ କରନ୍ତୁ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ଆପଣଙ୍କ କାର୍ଡଗୁଡ଼ିକ ପାଇବାରେ ଏକ ସମସ୍ୟା ହୋଇଥିଲା। ଦୟାକରି ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ସ୍କ୍ରିନ୍ ଲକ୍ ସେଟିଂସ୍"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR ସ୍କାନ କରନ୍ତୁ"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"ଏକ QR କୋଡ ସ୍କାନ କରିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ସ୍କାନ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ୱର୍କ ପ୍ରୋଫାଇଲ୍"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ଏରୋପ୍ଲେନ୍ ମୋଡ୍"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>ବେଳେ ଆପଣ ନିଜର ପରବର୍ତ୍ତୀ ଆଲାର୍ମ ଶୁଣିପାରିବେ ନାହିଁ"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ରୁ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚଲାଇବା ପାଇଁ ନିକଟକୁ ଯାଆନ୍ତୁ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚାଲୁଛି"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ମିଳିଲା ନାହିଁ"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ଉପଯୋଗକର୍ତ୍ତା ଚୟନ କର"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ପୃଷ୍ଠପଟରେ ଚାଲୁଥିବା ଆପଗୁଡ଼ିକ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ବନ୍ଦ କରନ୍ତୁ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-or/tiles_states_strings.xml b/packages/SystemUI/res/values-or/tiles_states_strings.xml index 94b012297f49..48ebb63e8ad2 100644 --- a/packages/SystemUI/res/values-or/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-or/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ବନ୍ଦ ଅଛି"</item> <item msgid="2075645297847971154">"ଚାଲୁ ଅଛି"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ଉପଲବ୍ଧ ନାହିଁ"</item> + <item msgid="1909756493418256167">"ବନ୍ଦ ଅଛି"</item> + <item msgid="4531508423703413340">"ଚାଲୁ ଅଛି"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ଉପଲବ୍ଧ ନାହିଁ"</item> <item msgid="9103697205127645916">"ବନ୍ଦ ଅଛି"</item> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 256ed366a087..3eae70aa7b8c 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ਵਾਈ-ਫਾਈ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ਚਮਕ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ਰੰਗ ਪਲਟਨਾ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ਰੰਗ ਸੁਧਾਈ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ਹੋਰ ਸੈਟਿੰਗਾਂ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ਵਰਤੋਂਕਾਰ ਸੈਟਿੰਗਾਂ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ਹੋ ਗਿਆ"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ਵਰਤਣ ਲਈ ਅਣਲਾਕ ਕਰੋ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ਤੁਹਾਡੇ ਕਾਰਡ ਪ੍ਰਾਪਤ ਕਰਨ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਆਈ, ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ਲਾਕ ਸਕ੍ਰੀਨ ਸੈਟਿੰਗਾਂ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR ਸਕੈਨ ਕਰੋ"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR ਕੋਡ ਨੂੰ ਸਕੈਨ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ਸਕੈਨ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ਤੁਸੀਂ <xliff:g id="WHEN">%1$s</xliff:g> ਵਜੇ ਆਪਣਾ ਅਗਲਾ ਅਲਾਰਮ ਨਹੀਂ ਸੁਣੋਗੇ"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ਤੋਂ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ਅਣਕੀਤਾ ਕਰੋ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਉਣ ਲਈ ਨੇੜੇ ਲਿਜਾਓ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ਨਹੀਂ ਮਿਲਿਆ"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ਵਰਤੋਂਕਾਰ ਚੁਣੋ"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚੱਲ ਰਹੀਆਂ ਐਪਾਂ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ਬੰਦ ਕਰੋ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pa/tiles_states_strings.xml b/packages/SystemUI/res/values-pa/tiles_states_strings.xml index a7fc06626636..85c5d89eb5a2 100644 --- a/packages/SystemUI/res/values-pa/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pa/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ਬੰਦ ਹੈ"</item> <item msgid="2075645297847971154">"ਚਾਲੂ ਹੈ"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ਉਪਲਬਧ ਨਹੀਂ"</item> + <item msgid="1909756493418256167">"ਬੰਦ ਹੈ"</item> + <item msgid="4531508423703413340">"ਚਾਲੂ ਹੈ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ਅਣਉਪਲਬਧ ਹੈ"</item> <item msgid="9103697205127645916">"ਬੰਦ ਹੈ"</item> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 8f57f0e59419..41ad75a3e64b 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Brak połączenia z Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jasność"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Odwrócenie kolorów"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcja kolorów"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Więcej ustawień"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ustawienia użytkownika"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotowe"</string> @@ -456,8 +455,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odblokuj, aby użyć"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Podczas pobierania kart wystąpił problem. Spróbuj ponownie później."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ustawienia ekranu blokady"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skanowanie kodu QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknij, aby zeskanować kod QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profil służbowy"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Tryb samolotowy"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nie usłyszysz swojego następnego alarmu <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -804,7 +805,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) w aplikacji <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> w aplikacji <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Cofnij"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Przysuń się bliżej, aby odtwarzać na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Odtwarzam na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nie znaleziono"</string> @@ -892,4 +894,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Wybierz użytkownika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacje działające w tle"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zatrzymaj"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pl/tiles_states_strings.xml b/packages/SystemUI/res/values-pl/tiles_states_strings.xml index 94fa858a0abb..8b922e53c18f 100644 --- a/packages/SystemUI/res/values-pl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pl/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Wyłączony"</item> <item msgid="2075645297847971154">"Włączony"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Brak dostępu"</item> + <item msgid="1909756493418256167">"Wyłączono"</item> + <item msgid="4531508423703413340">"Włączono"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Niedostępny"</item> <item msgid="9103697205127645916">"Wyłączony"</item> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 3b37834f22e4..d21ea5498b4a 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -449,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Ler código QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Clique para ler um código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Toque para fazer a leitura"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Aproxime os dispositivos para ouvir música no <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduzido em <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string> @@ -879,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 1195413fd280..556e92a9ff52 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para utilizar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao obter os seus cartões. Tente novamente mais tarde."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Definições do ecrã de bloqueio"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Leia o QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Clique para ler um código QR"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"Código QR"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Toque para ler"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avião"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Não vai ouvir o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anular"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Aproxime-se para reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"A reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inativa. Consulte a app."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado."</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecione utilizador"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 3b37834f22e4..d21ea5498b4a 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -449,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Ler código QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Clique para ler um código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Toque para fazer a leitura"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Aproxime os dispositivos para ouvir música no <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduzido em <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string> @@ -879,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index f8023cf19c3d..ab3c186dbe5b 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Rețeaua Wi-Fi nu este conectată"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminozitate"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversarea culorilor"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Mai multe setări"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setări de utilizator"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Terminat"</string> @@ -453,8 +452,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblocați pentru a folosi"</string> <string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încercați din nou mai târziu"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setările ecranului de blocare"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scanați un cod QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Dați clic pentru a scana un cod QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Atingeți pentru a scana"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil de serviciu"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mod Avion"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nu veți auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +798,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anulați"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Apropiați-vă pentru a reda pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Se redă pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verificați aplicația"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string> @@ -886,4 +887,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alegeți utilizatorul"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicațiile rulează în fundal"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Opriți"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ro/tiles_states_strings.xml b/packages/SystemUI/res/values-ro/tiles_states_strings.xml index 708c6f03a1d7..ba936966550e 100644 --- a/packages/SystemUI/res/values-ro/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ro/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Dezactivat"</item> <item msgid="2075645297847971154">"Activat"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Indisponibilă"</item> + <item msgid="1909756493418256167">"Dezactivată"</item> + <item msgid="4531508423703413340">"Activată"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Indisponibilă"</item> <item msgid="9103697205127645916">"Dezactivată"</item> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 9f40eab5e532..e92364c943d9 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нет подключения к сети Wi-Fi."</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркость"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверсия цветов"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Коррекция цвета"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Настройки"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Пользовательские настройки"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -456,8 +455,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблокировать для использования"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Не удалось получить информацию о картах. Повторите попытку позже."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки заблокированного экрана"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Сканер QR-кодов"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Нажмите, чтобы отсканировать QR-код."</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-код"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Нажмите, чтобы отсканировать код"</string> <string name="status_bar_work" msgid="5238641949837091056">"Рабочий профиль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим полета"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Следующий будильник: <xliff:g id="WHEN">%1$s</xliff:g>. Звук отключен."</string> @@ -804,7 +803,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" из приложения \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Отменить"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Чтобы включить музыку на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", подойдите к нему ближе."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Воспроизводится на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string> <string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не найдено."</string> @@ -892,4 +892,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выберите профиль"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Приложения, работающие в фоновом режиме"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Остановить"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ru/tiles_states_strings.xml b/packages/SystemUI/res/values-ru/tiles_states_strings.xml index 3a51c2e1b2b0..32e6ac978c77 100644 --- a/packages/SystemUI/res/values-ru/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ru/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Откл."</item> <item msgid="2075645297847971154">"Вкл."</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недоступно"</item> + <item msgid="1909756493418256167">"Отключено"</item> + <item msgid="4531508423703413340">"Включено"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Функция недоступна"</item> <item msgid="9103697205127645916">"Откл."</item> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index ce5b12690ff3..327025c488ff 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi සම්බන්ධ නොවීය"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"දීප්තිමත් බව"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"වර්ණ අපවර්තනය"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"වර්ණ නිවැරදි කිරීම"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"තව සැකසීම්"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"පරිශීලක සැකසීම්"</string> <string name="quick_settings_done" msgid="2163641301648855793">"නිමයි"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"භාවිත කිරීමට අගුලු හරින්න"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ඔබගේ කාඩ්පත ලබා ගැනීමේ ගැටලුවක් විය, කරුණාකර පසුව නැවත උත්සාහ කරන්න"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"අගුලු තිර සැකසීම්"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR කේතය ස්කෑන් කරන්න"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR කේතයක් ස්කෑන් කිරීමට ක්ලික් කරන්න"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"කාර්යාල පැතිකඩ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ගුවන්යානා ප්රකාරය"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ඔබට ඔබේ ඊළඟ එලාමය <xliff:g id="WHEN">%1$s</xliff:g> නොඇසෙනු ඇත"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් වාදනය කරන්න"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> වෙතින් වාදනය කරන්න"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"පසුගමනය කරන්න"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කිරීමට වඩාත් ළං වන්න"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කරමින්"</string> <string name="controls_error_timeout" msgid="794197289772728958">"අක්රියයි, යෙදුම පරීක්ෂා කරන්න"</string> <string name="controls_error_removed" msgid="6675638069846014366">"හමු නොවිණි"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"පරිශීලක තෝරන්න"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"පසුබිමින් ධාවනය වෙමින් පවතින යෙදුම්"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"නවත්වන්න"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-si/tiles_states_strings.xml b/packages/SystemUI/res/values-si/tiles_states_strings.xml index 909d119f2c09..8929a3c5709d 100644 --- a/packages/SystemUI/res/values-si/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-si/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"අක්රියයි"</item> <item msgid="2075645297847971154">"සක්රියයි"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"නොමැත"</item> + <item msgid="1909756493418256167">"ක්රියාවිරහිතයි"</item> + <item msgid="4531508423703413340">"ක්රියාත්මකයි"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"නොමැත"</item> <item msgid="9103697205127645916">"අක්රියයි"</item> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 209b031bc484..bb8870fb7429 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -455,8 +455,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odomknúť a použiť"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Pri načítavaní kariet sa vyskytol problém. Skúste to neskôr."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavenia uzamknutej obrazovky"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skenovanie QR kódu"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknutím naskenujte QR kód"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kód"</string> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Pracovný profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim v lietadle"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Váš budík o <xliff:g id="WHEN">%1$s</xliff:g> sa nespustí"</string> @@ -803,7 +804,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Späť"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Prehráva sa v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nenájdené"</string> @@ -891,4 +893,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vyberte používateľa"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikácie spustené na pozadí"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ukončiť"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 26d0570b8577..9c2c32c928b3 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -455,8 +455,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odklenite za uporabo"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Pri pridobivanju kartic je prišlo do težave. Poskusite znova pozneje."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavitve zaklepanja zaslona"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Optično branje kode QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknite, če želite optično prebrati kodo QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Dotaknite se za optično branje"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil za Android Work"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način za letalo"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Naslednjega alarma ob <xliff:g id="WHEN">%1$s</xliff:g> ne boste slišali"</string> @@ -803,7 +804,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Razveljavi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Za predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> bolj približajte telefon."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ni mogoče najti"</string> @@ -891,4 +893,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izberite uporabnika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije z izvajanjem v ozadju"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ustavi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 703e681a3762..993ab0cee711 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi nuk është lidhur"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ndriçimi"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Anasjellja e ngjyrës"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korrigjimi i ngjyrës"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Cilësime të tjera"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Cilësimet e përdoruesit"</string> <string name="quick_settings_done" msgid="2163641301648855793">"U krye"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Shkyçe për ta përdorur"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Pati një problem me marrjen e kartave të tua. Provo përsëri më vonë"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cilësimet e ekranit të kyçjes"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skano kodin QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliko për të skanuar një kod QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profili i punës"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modaliteti i aeroplanit"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nuk do ta dëgjosh alarmin e radhës në <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Zhbëj"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Afrohu për të luajtur në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Po luhet në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nuk u gjet"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zgjidh përdoruesin"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacionet që ekzekutohen në sfond"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ndalo"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sq/tiles_states_strings.xml b/packages/SystemUI/res/values-sq/tiles_states_strings.xml index 461cd93b3ba9..c7e3883aa42e 100644 --- a/packages/SystemUI/res/values-sq/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sq/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Joaktive"</item> <item msgid="2075645297847971154">"Aktive"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nuk ofrohet"</item> + <item msgid="1909756493418256167">"Joaktiv"</item> + <item msgid="4531508423703413340">"Aktiv"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nuk ofrohet"</item> <item msgid="9103697205127645916">"Joaktiv"</item> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index bf325e2b2db7..f637f2cf3bb7 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi није повезан"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија боја"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција боја"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Још подешавања"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Корисничка подешавања"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -453,8 +452,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Откључај ради коришћења"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Дошло је до проблема при преузимању картица. Пробајте поново касније"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Подешавања закључаног екрана"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Скенирај QR кôд"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Кликните да бисте скенирали QR кôд"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR кôд"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Додирните да бисте скенирали"</string> <string name="status_bar_work" msgid="5238641949837091056">"Пословни профил"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим рада у авиону"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Нећете чути следећи аларм у <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +797,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Опозови"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Приближите да бисте пуштали музику на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Пушта се на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Није пронађено"</string> @@ -886,4 +886,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изаберите корисника"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Апликације покренуте у позадини"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Заустави"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sr/tiles_states_strings.xml b/packages/SystemUI/res/values-sr/tiles_states_strings.xml index ab10ca1f6881..fda7465bcadf 100644 --- a/packages/SystemUI/res/values-sr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Искључено"</item> <item msgid="2075645297847971154">"Укључено"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недоступно"</item> + <item msgid="1909756493418256167">"Искључено"</item> + <item msgid="4531508423703413340">"Укључено"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Недоступно"</item> <item msgid="9103697205127645916">"Искључено"</item> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 36f4ab193bec..04cdcbe30f0c 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ej ansluten till wifi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ljusstyrka"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Färginvertering"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Färgkorrigering"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Fler inställningar"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Användarinställningar"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Klart"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås upp för att använda"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Det gick inte att hämta dina kort. Försök igen senare."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Inställningar för låsskärm"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skanna QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klicka för att skanna en QR-kod"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kod"</string> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Jobbprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flygplansläge"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nästa alarm, kl. <xliff:g id="WHEN">%1$s</xliff:g>, kommer inte att höras"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> från <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ångra"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Flytta närmare för att spela upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Spelas upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv, kolla appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hittades inte"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Välj användare"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Appar som körs i bakgrunden"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stoppa"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sv/tiles_states_strings.xml b/packages/SystemUI/res/values-sv/tiles_states_strings.xml index cdcf6e6c5f41..11026981c1a0 100644 --- a/packages/SystemUI/res/values-sv/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sv/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Av"</item> <item msgid="2075645297847971154">"På"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Inte tillgängligt"</item> + <item msgid="1909756493418256167">"Av"</item> + <item msgid="4531508423703413340">"På"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Inte tillgängligt"</item> <item msgid="9103697205127645916">"Av"</item> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 6643348c4bdd..2618484925e6 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi haijaunganishwa"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ung\'avu"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ugeuzaji rangi"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Usahihishaji wa rangirangi"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Mipangilio zaidi"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Mipangilio ya mtumiaji"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Nimemaliza"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Fungua ili utumie"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Hitilafu imetokea wakati wa kuleta kadi zako, tafadhali jaribu tena baadaye"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mipangilio ya kufunga skrini"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Changanua QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Bofya ili uchanganue msimbo wa QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Wasifu wa kazini"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Hali ya ndegeni"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Hutasikia kengele yako inayofuata ya saa <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> katika <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Tendua"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Sogea karibu ili ucheze kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Inacheza kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hakipatikani"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chagua mtumiaji"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Programu zinazotumika chinichini"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Simamisha"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sw/tiles_states_strings.xml b/packages/SystemUI/res/values-sw/tiles_states_strings.xml index 563c07803d12..d186d5192855 100644 --- a/packages/SystemUI/res/values-sw/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sw/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Kimezimwa"</item> <item msgid="2075645297847971154">"Kimewashwa"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Haupatikani"</item> + <item msgid="1909756493418256167">"Umezimwa"</item> + <item msgid="4531508423703413340">"Umewashwa"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Hakipatikani"</item> <item msgid="9103697205127645916">"Kimezimwa"</item> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index f721b9848b5e..e45f0721676f 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"வைஃபை இணைக்கப்படவில்லை"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ஒளிர்வு"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"கலர் இன்வெர்ஷன்"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"கலர் கரெக்ஷன்"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"அமைப்பில் மாற்று"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"பயனர் அமைப்புகள்"</string> <string name="quick_settings_done" msgid="2163641301648855793">"முடிந்தது"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"பயன்படுத்துவதற்கு அன்லாக் செய்க"</string> <string name="wallet_error_generic" msgid="257704570182963611">"உங்கள் கார்டுகளின் விவரங்களைப் பெறுவதில் சிக்கல் ஏற்பட்டது, பிறகு முயலவும்"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"பூட்டுத் திரை அமைப்புகள்"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR குறியீட்டை ஸ்கேன் செய்யுங்கள்"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR குறியீட்டை ஸ்கேன் செய்யக் கிளிக் செய்யவும்"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR குறியீடு"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ஸ்கேன் செய்யத் தட்டவும்"</string> <string name="status_bar_work" msgid="5238641949837091056">"பணிக் கணக்கு"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"விமானப் பயன்முறை"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"அடுத்த அலாரத்தை <xliff:g id="WHEN">%1$s</xliff:g> மணிக்கு கேட்க மாட்டீர்கள்"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%2$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"செயல்தவிர்"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் பிளே செய்ய உங்கள் சாதனத்தை அருகில் எடுத்துச் செல்லவும்"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் பிளே ஆகிறது"</string> <string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string> <string name="controls_error_removed" msgid="6675638069846014366">"இல்லை"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"பயனரைத் தேர்வுசெய்க"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"பின்னணியில் இயங்கும் ஆப்ஸ்"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"நிறுத்து"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ta/tiles_states_strings.xml b/packages/SystemUI/res/values-ta/tiles_states_strings.xml index a9e0eabc3d8c..0883d2202dfb 100644 --- a/packages/SystemUI/res/values-ta/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ta/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"முடக்கப்பட்டுள்ளது"</item> <item msgid="2075645297847971154">"இயக்கப்பட்டுள்ளது"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"இல்லை"</item> + <item msgid="1909756493418256167">"முடக்கப்பட்டுள்ளது"</item> + <item msgid="4531508423703413340">"இயக்கப்பட்டுள்ளது"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"கிடைக்கவில்லை"</item> <item msgid="9103697205127645916">"முடக்கப்பட்டுள்ளது"</item> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 501ee1753815..4e4102358fb1 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi కనెక్ట్ కాలేదు"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ప్రకాశం"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"కలర్ మార్పిడి"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"కలర్ కరెక్షన్"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"మరిన్ని సెట్టింగ్లు"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"యూజర్ సెట్టింగ్లు"</string> <string name="quick_settings_done" msgid="2163641301648855793">"పూర్తయింది"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ఉపయోగించడానికి అన్లాక్ చేయండి"</string> <string name="wallet_error_generic" msgid="257704570182963611">"మీ కార్డ్లను పొందడంలో సమస్య ఉంది, దయచేసి తర్వాత మళ్లీ ట్రై చేయండి"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"లాక్ స్క్రీన్ సెట్టింగ్లు"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QRను స్కాన్ చేయండి"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR కోడ్ను స్కాన్ చేయడానికి క్లిక్ చేయండి"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR కోడ్"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"స్కాన్ చేయడానికి ట్యాప్ చేయండి"</string> <string name="status_bar_work" msgid="5238641949837091056">"ఆఫీస్ ప్రొఫైల్"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ఎయిర్ప్లేన్ మోడ్"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"మీరు <xliff:g id="WHEN">%1$s</xliff:g> సెట్ చేసిన మీ తర్వాత అలారం మీకు వినిపించదు"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి <xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> నుండి <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"చర్య రద్దు"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే చేయడానికి దగ్గరగా వెళ్లండి"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే అవుతోంది"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ఇన్యాక్టివ్, యాప్ చెక్ చేయండి"</string> <string name="controls_error_removed" msgid="6675638069846014366">"కనుగొనబడలేదు"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"యూజర్ను ఎంచుకోండి"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"యాప్లు బ్యాక్గ్రౌండ్లో రన్ అవుతున్నాయి"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ఆపివేయండి"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-te/tiles_states_strings.xml b/packages/SystemUI/res/values-te/tiles_states_strings.xml index 4cb7291cafd3..5c8ae3da6d9f 100644 --- a/packages/SystemUI/res/values-te/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-te/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ఆఫ్లో ఉంది"</item> <item msgid="2075645297847971154">"ఆన్లో ఉంది"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"అందుబాటులో లేదు"</item> + <item msgid="1909756493418256167">"ఆఫ్లో ఉంది"</item> + <item msgid="4531508423703413340">"ఆన్లో ఉంది"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"అందుబాటులో లేదు"</item> <item msgid="9103697205127645916">"ఆఫ్లో ఉంది"</item> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 6b7c05f47d4e..8897186e4a10 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ไม่ได้เชื่อมต่อ Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ความสว่าง"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"การกลับสี"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"การแก้สี"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"การตั้งค่าเพิ่มเติม"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"การตั้งค่าของผู้ใช้"</string> <string name="quick_settings_done" msgid="2163641301648855793">"เสร็จสิ้น"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ปลดล็อกเพื่อใช้"</string> <string name="wallet_error_generic" msgid="257704570182963611">"เกิดปัญหาในการดึงข้อมูลบัตรของคุณ โปรดลองอีกครั้งในภายหลัง"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"การตั้งค่าหน้าจอล็อก"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"สแกนคิวอาร์"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"คลิกเพื่อสแกนคิวอาร์โค้ด"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"คิวอาร์โค้ด"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"แตะเพื่อสแกน"</string> <string name="status_bar_work" msgid="5238641949837091056">"โปรไฟล์งาน"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"โหมดบนเครื่องบิน"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"คุณจะไม่ได้ยินเสียงปลุกครั้งถัดไปในเวลา <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> จาก <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"เลิกทำ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"ขยับเข้ามาใกล้ขึ้นเพื่อเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"กำลังเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ไม่มีการใช้งาน โปรดตรวจสอบแอป"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ไม่พบ"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"เลือกผู้ใช้"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"แอปที่ทำงานอยู่เบื้องหลัง"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"หยุด"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-th/tiles_states_strings.xml b/packages/SystemUI/res/values-th/tiles_states_strings.xml index 67768736afd9..e7eae732ba46 100644 --- a/packages/SystemUI/res/values-th/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-th/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ปิด"</item> <item msgid="2075645297847971154">"เปิด"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ไม่พร้อมใช้งาน"</item> + <item msgid="1909756493418256167">"ปิด"</item> + <item msgid="4531508423703413340">"เปิด"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ไม่พร้อมใช้งาน"</item> <item msgid="9103697205127645916">"ปิด"</item> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index df546587e5b9..6ab6ef661ab4 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Hindi nakakonekta sa Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Pag-invert ng kulay"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pagtatama ng kulay"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Higit pang setting"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Mga setting ng user"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Tapos na"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"I-unlock para magamit"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Nagkaproblema sa pagkuha ng iyong mga card, pakisubukan ulit sa ibang pagkakataon"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mga setting ng lock screen"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"I-scan ang QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Mag-click para mag-scan ng QR code"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profile sa trabaho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Hindi mo maririnig ang iyong susunod na alarm ng <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"I-undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Lumapit pa para mag-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Nagpe-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Hindi aktibo, tingnan ang app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hindi nahanap"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pumili ng user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Mga app na tumatakbo sa background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ihinto"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-tl/tiles_states_strings.xml b/packages/SystemUI/res/values-tl/tiles_states_strings.xml index 62172a4dc907..f33d8a2c3180 100644 --- a/packages/SystemUI/res/values-tl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-tl/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Naka-off"</item> <item msgid="2075645297847971154">"Naka-on"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Hindi available"</item> + <item msgid="1909756493418256167">"Naka-off"</item> + <item msgid="4531508423703413340">"Naka-on"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Hindi available"</item> <item msgid="9103697205127645916">"Naka-off"</item> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index f389bf17de5a..ee44b76da88d 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Kablosuz ağ bağlı değil"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaklık"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rengi ters çevirme"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Renk düzeltme"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Diğer ayarlar"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Kullanıcı ayarları"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Bitti"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Kullanmak için kilidi aç"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Kartlarınız alınırken bir sorun oluştu. Lütfen daha sonra tekrar deneyin"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilit ekranı ayarları"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR kodunu tarayın"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR kodu taramak için tıklayın"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Uçak modu"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> olarak ayarlanmış bir sonraki alarmınızı duymayacaksınız"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> uygulamasından <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Geri al"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında çalmak için yaklaşın"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında çalınıyor"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Bulunamadı"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kullanıcı seçin"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Arka planda çalışan uygulamalar"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Durdur"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-tr/tiles_states_strings.xml b/packages/SystemUI/res/values-tr/tiles_states_strings.xml index 9f836fc6bc2d..17b4bb4e1a44 100644 --- a/packages/SystemUI/res/values-tr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-tr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Kapalı"</item> <item msgid="2075645297847971154">"Açık"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Bilinmiyor"</item> + <item msgid="1909756493418256167">"Kapalı"</item> + <item msgid="4531508423703413340">"Açık"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Kullanılamıyor"</item> <item msgid="9103697205127645916">"Kapalı"</item> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 3856cc5fbaf3..27aad2edfdb8 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi не під’єднано"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яскравість"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія кольорів"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекція кольору"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Більше налаштувань"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Налаштування користувача"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -456,8 +455,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Розблокувати, щоб використовувати"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Не вдалось отримати ваші картки. Повторіть спробу пізніше."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Параметри блокування екрана"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Сканувати QR-код"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Натисніть, щоб відсканувати QR-код"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Робочий профіль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим польоту"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Наступний сигнал о <xliff:g id="WHEN">%1$s</xliff:g> не пролунає"</string> @@ -804,7 +805,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, у додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" у додатку <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Відмінити"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Щоб відтворити на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>, наблизьтеся до нього"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Відтворюється на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не знайдено"</string> @@ -892,4 +894,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Виберіть користувача"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Додатки, що працюють у фоновому режимі"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Зупинити"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-uk/tiles_states_strings.xml b/packages/SystemUI/res/values-uk/tiles_states_strings.xml index 34c40d3d2951..c4ac1949a4c6 100644 --- a/packages/SystemUI/res/values-uk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-uk/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Вимкнено"</item> <item msgid="2075645297847971154">"Увімкнено"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недоступно"</item> + <item msgid="1909756493418256167">"Вимкнено"</item> + <item msgid="4531508423703413340">"Увімкнено"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Недоступно"</item> <item msgid="9103697205127645916">"Вимкнено"</item> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index dafaddf1bd66..015d52072f01 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi سے منسلک نہیں ہے"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"چمکیلا پن"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"رنگوں کی تقلیب"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"رنگ کی اصلاح"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"مزید ترتیبات"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"صارف کی ترتیبات"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ہو گیا"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"استعمال کرنے کے لیے غیر مقفل کریں"</string> <string name="wallet_error_generic" msgid="257704570182963611">"آپ کے کارڈز حاصل کرنے میں ایک مسئلہ درپیش تھا، براہ کرم بعد میں دوبارہ کوشش کریں"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"مقفل اسکرین کی ترتیبات"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR اسکین کریں"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR کوڈ اسکین کرنے کے لیے کلک کریں"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR کوڈ"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"اسکین کرنے کے لیے تھپتھپائیں"</string> <string name="status_bar_work" msgid="5238641949837091056">"دفتری پروفائل"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ہوائی جہاز وضع"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"آپ کو <xliff:g id="WHEN">%1$s</xliff:g> بجے اپنا اگلا الارم سنائی نہیں دے گا"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> سے <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"کالعدم کریں"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چلانے کے لیے قریب کریں"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چل رہا ہے"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string> <string name="controls_error_removed" msgid="6675638069846014366">"نہیں ملا"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"صارف منتخب کریں"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ایپس پس منظر میں چل رہی ہیں"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"روکیں"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ur/tiles_states_strings.xml b/packages/SystemUI/res/values-ur/tiles_states_strings.xml index 02943fa8e272..155403151ed7 100644 --- a/packages/SystemUI/res/values-ur/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ur/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"آف ہے"</item> <item msgid="2075645297847971154">"آن ہے"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"غیر دستیاب"</item> + <item msgid="1909756493418256167">"آف"</item> + <item msgid="4531508423703413340">"آن"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"دستیاب نہیں ہے"</item> <item msgid="9103697205127645916">"آف ہے"</item> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 13971dd28cf2..ce7fc3245925 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -449,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Foydalanish uchun qulfdan chiqarish"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Bildirgilarni yuklashda xatolik yuz berdi, keyinroq qaytadan urining"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Qulflangan ekran sozlamalari"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR kodni skanerlash"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR kodni skanerlash uchun bosing"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Ish profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Parvoz rejimi"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Keyingi signal (<xliff:g id="WHEN">%1$s</xliff:g>) chalinmaydi"</string> @@ -791,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Qaytarish"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>da ijro etish uchun yaqinroq keling"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> qurilmasida ijro qilinmoqda"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Nofaol. Ilovani tekshiring"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Topilmadi"</string> @@ -879,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Foydalanuvchini tanlang"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Orqa fonda ishlayotgan ilovalar"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 4d2c90c35216..e4066d9ec47e 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Chưa kết nối với Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Độ sáng"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Đảo màu"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Chỉnh màu"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Chế độ cài đặt khác"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Cài đặt người dùng"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Xong"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Mở khóa để sử dụng"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Đã xảy ra sự cố khi tải thẻ của bạn. Vui lòng thử lại sau"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cài đặt màn hình khóa"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Quét mã QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Nhấp để quét mã QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Hồ sơ công việc"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Chế độ máy bay"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Bạn sẽ không nghe thấy báo thức tiếp theo lúc <xliff:g id="WHEN">%1$s</xliff:g> của mình"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> trên <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Hủy"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Đưa thiết bị đến gần hơn để phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Đang phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Không tìm thấy"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chọn người dùng"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Các ứng dụng chạy trong nền"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Dừng"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-vi/tiles_states_strings.xml b/packages/SystemUI/res/values-vi/tiles_states_strings.xml index bff64ab6b6d3..94e801278637 100644 --- a/packages/SystemUI/res/values-vi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-vi/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Đang tắt"</item> <item msgid="2075645297847971154">"Đang bật"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Không có"</item> + <item msgid="1909756493418256167">"Đang tắt"</item> + <item msgid="4531508423703413340">"Đang bật"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Không hoạt động"</item> <item msgid="9103697205127645916">"Đang tắt"</item> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 038ada0bec29..4666a882f8a3 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未连接到 WLAN 网络"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"颜色反转"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"更多设置"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"用户设置"</string> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解锁设备即可使用"</string> <string name="wallet_error_generic" msgid="257704570182963611">"获取您的卡片时出现问题,请稍后重试"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"锁定屏幕设置"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"扫描二维码"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"点击即可扫描二维码"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"二维码"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"点按即可扫描"</string> <string name="status_bar_work" msgid="5238641949837091056">"工作资料"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飞行模式"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"您在<xliff:g id="WHEN">%1$s</xliff:g>将不会听到下次闹钟响铃"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"通过<xliff:g id="APP_LABEL">%2$s</xliff:g>播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"撤消"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"移近一点以在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"正在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放"</string> <string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string> <string name="controls_error_removed" msgid="6675638069846014366">"未找到"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"选择用户"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"正在在后台运行的应用"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml index 0d72f61092d0..a266d929ec3f 100644 --- a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"已关闭"</item> <item msgid="2075645297847971154">"已开启"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"不可用"</item> + <item msgid="1909756493418256167">"关闭"</item> + <item msgid="4531508423703413340">"开启"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"不可用"</item> <item msgid="9103697205127645916">"已关闭"</item> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index d4406b4b111b..f64e78cab621 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"更多設定"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"使用者設定"</string> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解鎖即可使用"</string> <string name="wallet_error_generic" msgid="257704570182963611">"擷取資訊卡時發生問題,請稍後再試。"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"上鎖畫面設定"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"掃瞄 QR 碼"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"按一下即可掃瞄 QR 碼"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR 碼"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"輕按即可掃瞄"</string> <string name="status_bar_work" msgid="5238641949837091056">"工作設定檔"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飛行模式"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"您不會<xliff:g id="WHEN">%1$s</xliff:g>聽到鬧鐘"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"在 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"在 <xliff:g id="APP_LABEL">%2$s</xliff:g> 播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"移近一點以在 <xliff:g id="DEVICENAME">%1$s</xliff:g> 上播放"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"正在 <xliff:g id="DEVICENAME">%1$s</xliff:g> 上播放"</string> <string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string> <string name="controls_error_removed" msgid="6675638069846014366">"找不到"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"正在背景中執行的應用程式"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml index 571cfba728a4..d5d092f3067d 100644 --- a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"已關閉"</item> <item msgid="2075645297847971154">"已開啟"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"無法使用"</item> + <item msgid="1909756493418256167">"關閉"</item> + <item msgid="4531508423703413340">"開啟"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"無法使用"</item> <item msgid="9103697205127645916">"已關閉"</item> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 83623d434a27..87a6ae828383 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"更多設定"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"使用者設定"</string> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解鎖即可使用"</string> <string name="wallet_error_generic" msgid="257704570182963611">"擷取卡片時發生問題,請稍後再試"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"螢幕鎖定設定"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"掃描 QR 圖碼"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"按一下即可掃描 QR 圖碼"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR 圖碼"</string> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"工作資料夾"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飛航模式"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"你不會聽到下一個<xliff:g id="WHEN">%1$s</xliff:g> 的鬧鐘"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"透過「<xliff:g id="APP_LABEL">%2$s</xliff:g>」播放〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近這部裝置"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string> <string name="controls_error_timeout" msgid="794197289772728958">"無效,請查看應用程式"</string> <string name="controls_error_removed" msgid="6675638069846014366">"找不到控制項"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"目前在背景執行的應用程式"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml index 48896579101a..ad2441344256 100644 --- a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"已關閉"</item> <item msgid="2075645297847971154">"已開啟"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"無法使用"</item> + <item msgid="1909756493418256167">"已關閉"</item> + <item msgid="4531508423703413340">"已開啟"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"無法使用"</item> <item msgid="9103697205127645916">"已關閉"</item> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index fa200dcefaa2..602845b36c3d 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"I-Wi-Fi ayixhunyiwe"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ukugqama"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ukuguqulwa kombala"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ukulungiswa kombala"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Izilungiselelo eziningi"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Amasethingi womsebenzisi"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Kwenziwe"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Vula ukuze usebenzise"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Kube khona inkinga yokuthola amakhadi akho, sicela uzame futhi ngemuva kwesikhathi"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Amasethingi okukhiya isikrini"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skena i-QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Chofoza ukuze uskene ikhodi ye-QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Thepha ukuze uskene"</string> <string name="status_bar_work" msgid="5238641949837091056">"Iphrofayela yomsebenzi"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Imodi yendiza"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ngeke uzwe i-alamu yakho elandelayo ngo-<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Hlehlisa"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Sondeza eduze ukudlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Idlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ayitholakali"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Khetha umsebenzisi"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ama-app ayaqhubeka ngemuva"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Misa"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zu/tiles_states_strings.xml b/packages/SystemUI/res/values-zu/tiles_states_strings.xml index ea40faf4eb56..92290d6d1a0c 100644 --- a/packages/SystemUI/res/values-zu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zu/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Valiwe"</item> <item msgid="2075645297847971154">"Vuliwe"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Akutholakali"</item> + <item msgid="1909756493418256167">"Valiwe"</item> + <item msgid="4531508423703413340">"Vuliwe"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Akutholakali"</item> <item msgid="9103697205127645916">"Valiwe"</item> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 7b8f349777a6..65f22b805d4e 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -294,6 +294,7 @@ <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> + <item>com.android.keyguard.KeyguardBiometricLockoutLogger</item> <item>com.android.systemui.recents.Recents</item> <item>com.android.systemui.volume.VolumeUI</item> <item>com.android.systemui.statusbar.phone.StatusBar</item> @@ -315,6 +316,7 @@ <item>com.android.systemui.accessibility.SystemActions</item> <item>com.android.systemui.toast.ToastUI</item> <item>com.android.systemui.wmshell.WMShell</item> + <item>com.android.systemui.clipboardoverlay.ClipboardListener</item> </string-array> <!-- SystemUI Services: The classes of the additional stuff to start. Services here are @@ -736,4 +738,6 @@ <!-- Class for the communal source connector to be used --> <string name="config_communalSourceConnector" translatable="false"></string> + <!-- How often in milliseconds to jitter the dream overlay in order to avoid burn-in. --> + <integer name="config_dreamOverlayBurnInProtectionUpdateIntervalMillis">500</integer> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2916c1c9b357..08fb2c66e043 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1165,7 +1165,7 @@ <string name="wallet_lockscreen_settings_label">Lock screen settings</string> <!-- QR Code Scanner label, title [CHAR LIMIT=32] --> - <string name="qr_code_scanner_title">QR Code</string> + <string name="qr_code_scanner_title">QR code</string> <!-- QR Code Scanner description [CHAR LIMIT=NONE] --> <string name="qr_code_scanner_description">Tap to scan</string> @@ -2152,8 +2152,8 @@ <!--- ****** Media tap-to-transfer ****** --> <!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] --> <string name="media_transfer_undo">Undo</string> - <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play music on the different device. [CHAR LIMIT=75] --> - <string name="media_move_closer_to_transfer">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> + <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] --> + <string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] --> <string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> @@ -2349,4 +2349,9 @@ <string name="fgs_manager_dialog_title">Apps running in the background</string> <!-- Label of the button to stop the app from running in the background [CHAR LIMIT=12]--> <string name="fgs_manager_app_item_stop_button_label">Stop</string> + + <!-- Label for button to copy edited text back to the clipboard [CHAR LIMIT=20] --> + <string name="clipboard_edit_text_copy">Copy</string> + <!-- Text informing user that content has been copied to the system clipboard [CHAR LIMIT=NONE] --> + <string name="clipboard_overlay_text_copied">Copied</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 18bfb52d3401..ff5699bbc199 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -666,6 +666,14 @@ <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight"/> + <!-- Clipboard overlay's edit text activity. --> + <style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowLightStatusBar">true</item> + <item name="android:windowLightNavigationBar">true</item> + <item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item> + </style> + <!-- Privacy dialog --> <style name="PrivacyDialog" parent="Theme.SystemUI.QuickSettings.Dialog"> <item name="android:windowIsTranslucent">true</item> @@ -968,8 +976,9 @@ </style> <style name="InternetDialog.NetworkSummary"> - <item name="android:layout_marginEnd">34dp</item> + <item name="android:layout_marginEnd">7dp</item> <item name="android:ellipsize">end</item> + <item name="android:maxLines">2</item> <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary</item> </style> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index d172006d986d..3cf5bc1bf13a 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -49,9 +49,12 @@ android_library { "PluginCoreLib", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", + "dagger2", + "jsr330", ], java_version: "1.8", min_sdk_version: "current", + plugins: ["dagger2-compiler"], } java_library { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java index 7b097740ff11..7b097740ff11 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java +++ b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl new file mode 100644 index 000000000000..861a4ed8eaf9 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.systemui.shared.mediattt; + +parcelable DeviceInfo; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt new file mode 100644 index 000000000000..d41aaf30a12f --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt @@ -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.systemui.shared.mediattt + +import android.os.Parcel +import android.os.Parcelable + +/** + * Represents a device that can send or receive media. Includes any device information necessary for + * SysUI to display an informative chip to the user. + */ +class DeviceInfo(val name: String) : Parcelable { + constructor(parcel: Parcel) : this(parcel.readString()) + + override fun writeToParcel(dest: Parcel?, flags: Int) { + dest?.writeString(name) + } + + override fun describeContents() = 0 + + override fun toString() = "name: $name" + + companion object CREATOR : Parcelable.Creator<DeviceInfo> { + override fun createFromParcel(parcel: Parcel) = DeviceInfo(parcel) + override fun newArray(size: Int) = arrayOfNulls<DeviceInfo?>(size) + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl new file mode 100644 index 000000000000..484791df053e --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl @@ -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.systemui.shared.mediattt; + +import android.media.MediaRoute2Info; +import com.android.systemui.shared.mediattt.DeviceInfo; + +/** + * A callback interface that can be invoked to trigger media transfer events on System UI. + * + * This interface is for the *sender* device, which is the device currently playing media. This + * sender device can transfer the media to a different device, called the receiver. + * + * System UI will implement this interface and other services will invoke it. + */ +interface IDeviceSenderCallback { + /** + * Invoke to notify System UI that this device (the sender) is close to a receiver device, so + * the user can potentially *start* a cast to the receiver device if the user moves their device + * a bit closer. + * + * Important notes: + * - When this callback triggers, the device is close enough to inform the user that + * transferring is an option, but the device is *not* close enough to actually initiate a + * transfer yet. + * - This callback is for *starting* a cast. It should be used when this device is currently + * playing media locally and the media should be transferred to be played on the receiver + * device instead. + */ + oneway void closeToReceiverToStartCast( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java index 98083742d707..1d2caf9ab545 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java @@ -28,6 +28,8 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; +import androidx.annotation.VisibleForTesting; + import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -60,6 +62,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, private final Rect mRegisteredSamplingBounds = new Rect(); private final SamplingCallback mCallback; private final Executor mBackgroundExecutor; + private final SysuiCompositionSamplingListener mCompositionSamplingListener; private boolean mSamplingEnabled = false; private boolean mSamplingListenerRegistered = false; @@ -91,9 +94,17 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback, Executor backgroundExecutor) { + this(sampledView, samplingCallback, sampledView.getContext().getMainExecutor(), + backgroundExecutor, new SysuiCompositionSamplingListener()); + } + + @VisibleForTesting + RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback, + Executor mainExecutor, Executor backgroundExecutor, + SysuiCompositionSamplingListener compositionSamplingListener) { mBackgroundExecutor = backgroundExecutor; - mSamplingListener = new CompositionSamplingListener( - sampledView.getContext().getMainExecutor()) { + mCompositionSamplingListener = compositionSamplingListener; + mSamplingListener = new CompositionSamplingListener(mainExecutor) { @Override public void onSampleCollected(float medianLuma) { if (mSamplingEnabled) { @@ -136,7 +147,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, public void stopAndDestroy() { stop(); - mSamplingListener.destroy(); + mBackgroundExecutor.execute(mSamplingListener::destroy); mIsDestroyed = true; } @@ -189,13 +200,12 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, // We only want to re-register if something actually changed unregisterSamplingListener(); mSamplingListenerRegistered = true; - SurfaceControl wrappedStopLayer = stopLayerControl == null - ? null : new SurfaceControl(stopLayerControl, "regionSampling"); + SurfaceControl wrappedStopLayer = wrap(stopLayerControl); mBackgroundExecutor.execute(() -> { if (wrappedStopLayer != null && !wrappedStopLayer.isValid()) { return; } - CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY, + mCompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY, wrappedStopLayer, mSamplingRequestBounds); }); mRegisteredSamplingBounds.set(mSamplingRequestBounds); @@ -208,14 +218,21 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, } } + @VisibleForTesting + protected SurfaceControl wrap(SurfaceControl stopLayerControl) { + return stopLayerControl == null ? null : new SurfaceControl(stopLayerControl, + "regionSampling"); + } + private void unregisterSamplingListener() { if (mSamplingListenerRegistered) { mSamplingListenerRegistered = false; SurfaceControl wrappedStopLayer = mWrappedStopLayer; mRegisteredStopLayer = null; + mWrappedStopLayer = null; mRegisteredSamplingBounds.setEmpty(); mBackgroundExecutor.execute(() -> { - CompositionSamplingListener.unregister(mSamplingListener); + mCompositionSamplingListener.unregister(mSamplingListener); if (wrappedStopLayer != null && wrappedStopLayer.isValid()) { wrappedStopLayer.release(); } @@ -299,4 +316,19 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, return true; } } + + @VisibleForTesting + public static class SysuiCompositionSamplingListener { + public void register(CompositionSamplingListener listener, + int displayId, SurfaceControl stopLayer, Rect samplingArea) { + CompositionSamplingListener.register(listener, displayId, stopLayer, samplingArea); + } + + /** + * Unregisters a sampling listener. + */ + public void unregister(CompositionSamplingListener listener) { + CompositionSamplingListener.unregister(listener); + } + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index 7539f995dab4..851ea6558697 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -137,7 +137,8 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage filter.addAction(PLUGIN_CHANGED); filter.addAction(DISABLE_PLUGIN); filter.addDataScheme("package"); - mContext.registerReceiver(this, filter, PluginActionManager.PLUGIN_PERMISSION, null); + mContext.registerReceiver(this, filter, PluginActionManager.PLUGIN_PERMISSION, null, + Context.RECEIVER_EXPORTED_UNAUDITED); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiver(this, filter); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index 605d37628ec7..d518cb5cdba1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.rotation; +import static android.content.pm.PackageManager.FEATURE_PC; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION; @@ -200,7 +201,7 @@ public class RotationButtonController { } public void registerListeners() { - if (mListenersRegistered) { + if (mListenersRegistered || getContext().getPackageManager().hasSystemFeature(FEATURE_PC)) { return; } @@ -414,6 +415,9 @@ public class RotationButtonController { } public void onTaskbarStateChange(boolean visible, boolean stashed) { + if (getRotationButton() == null) { + return; + } getRotationButton().onTaskbarStateChanged(visible, stashed); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index d182399239cc..54c798c7d95d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -60,8 +60,6 @@ public class QuickStepContract { // See IRecentTasks.aidl public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks"; - public static final String NAV_BAR_MODE_2BUTTON_OVERLAY = - WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; public static final String NAV_BAR_MODE_3BUTTON_OVERLAY = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; public static final String NAV_BAR_MODE_GESTURAL_OVERLAY = diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index e84b552d65eb..618d2d2f213a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -183,8 +183,8 @@ public class RemoteAnimationAdapterCompat { } // Make wallpaper visible immediately since launcher apparently won't do this. for (int i = wallpapersCompat.length - 1; i >= 0; --i) { - t.show(wallpapersCompat[i].leash.getSurfaceControl()); - t.setAlpha(wallpapersCompat[i].leash.getSurfaceControl(), 1.f); + t.show(wallpapersCompat[i].leash); + t.setAlpha(wallpapersCompat[i].leash, 1.f); } } else { if (launcherTask != null) { @@ -205,8 +205,10 @@ public class RemoteAnimationAdapterCompat { @Override @SuppressLint("NewApi") public void run() { - counterLauncher.cleanUp(info.getRootLeash()); - counterWallpaper.cleanUp(info.getRootLeash()); + final SurfaceControl.Transaction finishTransaction = + new SurfaceControl.Transaction(); + counterLauncher.cleanUp(finishTransaction); + counterWallpaper.cleanUp(finishTransaction); // Release surface references now. This is apparently to free GPU memory // while doing quick operations (eg. during CTS). for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -216,7 +218,7 @@ public class RemoteAnimationAdapterCompat { leashMap.valueAt(i).release(); } try { - finishCallback.onTransitionFinished(null /* wct */, null /* sct */); + finishCallback.onTransitionFinished(null /* wct */, finishTransaction); } catch (RemoteException e) { Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + " finished callback", e); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index 4ec65d832851..4d0c443ca5a4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -17,7 +17,6 @@ package com.android.systemui.shared.system; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; -import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -57,7 +56,7 @@ public class RemoteAnimationTargetCompat { public final int activityType; public final int taskId; - public final SurfaceControlCompat leash; + public final SurfaceControl leash; public final boolean isTranslucent; public final Rect clipRect; public final int prefixOrderIndex; @@ -82,7 +81,7 @@ public class RemoteAnimationTargetCompat { public RemoteAnimationTargetCompat(RemoteAnimationTarget app) { taskId = app.taskId; mode = app.mode; - leash = new SurfaceControlCompat(app.leash); + leash = app.leash; isTranslucent = app.isTranslucent; clipRect = app.clipRect; position = app.position; @@ -119,7 +118,7 @@ public class RemoteAnimationTargetCompat { public RemoteAnimationTarget unwrap() { return new RemoteAnimationTarget( - taskId, mode, leash.getSurfaceControl(), isTranslucent, clipRect, contentInsets, + taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration, isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType ); @@ -140,22 +139,11 @@ public class RemoteAnimationTargetCompat { // changes should be ordered top-to-bottom in z final int mode = change.getMode(); - // Don't move anything that isn't independent within its parents - if (!TransitionInfo.isIndependent(change, info)) { - if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { - t.show(leash); - t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); - } - return; - } - - boolean hasParent = change.getParent() != null; + // Launcher animates leaf tasks directly, so always reparent all task leashes to root leash. + t.reparent(leash, info.getRootLeash()); + t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, + change.getStartAbsBounds().top - info.getRootOffset().y); - if (!hasParent) { - t.reparent(leash, info.getRootLeash()); - t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, - change.getStartAbsBounds().top - info.getRootOffset().y); - } t.show(leash); // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { @@ -211,7 +199,7 @@ public class RemoteAnimationTargetCompat { mode = newModeToLegacyMode(change.getMode()); // TODO: once we can properly sync transactions across process, then get rid of this leash. - leash = new SurfaceControlCompat(createLeash(info, change, order, t)); + leash = createLeash(info, change, order, t); isTranslucent = (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0 || (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0; @@ -266,14 +254,15 @@ public class RemoteAnimationTargetCompat { SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) { final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>(); for (int i = 0; i < info.getChanges().size(); i++) { - boolean changeIsWallpaper = - (info.getChanges().get(i).getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0; + final TransitionInfo.Change change = info.getChanges().get(i); + final boolean changeIsWallpaper = + (change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0; if (wallpapers != changeIsWallpaper) continue; - out.add(new RemoteAnimationTargetCompat(info.getChanges().get(i), - info.getChanges().size() - i, info, t)); - if (leashMap == null) continue; - leashMap.put(info.getChanges().get(i).getLeash(), - out.get(out.size() - 1).leash.mSurfaceControl); + + out.add(new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t)); + if (leashMap != null) { + leashMap.put(change.getLeash(), out.get(out.size() - 1).leash); + } } return out.toArray(new RemoteAnimationTargetCompat[out.size()]); } @@ -282,8 +271,8 @@ public class RemoteAnimationTargetCompat { * @see SurfaceControl#release() */ public void release() { - if (leash.mSurfaceControl != null) { - leash.mSurfaceControl.release(); + if (leash != null) { + leash.release(); } if (mStartLeash != null) { mStartLeash.release(); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 2d5080eaaa22..2ae32c71269a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -149,7 +149,7 @@ public class RemoteTransitionCompat implements Parcelable { } // Also make all the wallpapers opaque since we want the visible from the start for (int i = wallpapers.length - 1; i >= 0; --i) { - t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1); + t.setAlpha(wallpapers[i].leash, 1); } t.apply(); mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask, @@ -267,10 +267,10 @@ public class RemoteTransitionCompat implements Parcelable { // We are receiving new opening tasks, so convert to onTasksAppeared. final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat( openingTasks.get(i), layer, info, t); - mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl); - t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash()); - t.setLayer(target.leash.mSurfaceControl, layer); - t.hide(target.leash.mSurfaceControl); + mLeashMap.put(mOpeningLeashes.get(i), target.leash); + t.reparent(target.leash, mInfo.getRootLeash()); + t.setLayer(target.leash, layer); + t.hide(target.leash); targets[i] = target; } t.apply(); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java deleted file mode 100644 index acc691304ca5..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java +++ /dev/null @@ -1,47 +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 - */ - -package com.android.systemui.shared.system; - -import android.view.SurfaceControl; -import android.view.View; -import android.view.ViewRootImpl; - -/** - * TODO: Remove this class - */ -public class SurfaceControlCompat { - final SurfaceControl mSurfaceControl; - - public SurfaceControlCompat(SurfaceControl surfaceControl) { - mSurfaceControl = surfaceControl; - } - - public SurfaceControlCompat(View v) { - ViewRootImpl viewRootImpl = v.getViewRootImpl(); - mSurfaceControl = viewRootImpl != null - ? viewRootImpl.getSurfaceControl() - : null; - } - - public boolean isValid() { - return mSurfaceControl != null && mSurfaceControl.isValid(); - } - - public SurfaceControl getSurfaceControl() { - return mSurfaceControl; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java index e281914d560e..30c062b66da9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java @@ -205,13 +205,6 @@ public class SyncRtSurfaceTransactionApplierCompat { /** * @param surface The surface to modify. */ - public Builder(SurfaceControlCompat surface) { - this(surface.mSurfaceControl); - } - - /** - * @param surface The surface to modify. - */ public Builder(SurfaceControl surface) { this.surface = surface; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java index c043fba48228..43a882a5f6be 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java @@ -35,70 +35,69 @@ public class TransactionCompat { mTransaction.apply(); } - public TransactionCompat show(SurfaceControlCompat surfaceControl) { - mTransaction.show(surfaceControl.mSurfaceControl); + public TransactionCompat show(SurfaceControl surfaceControl) { + mTransaction.show(surfaceControl); return this; } - public TransactionCompat hide(SurfaceControlCompat surfaceControl) { - mTransaction.hide(surfaceControl.mSurfaceControl); + public TransactionCompat hide(SurfaceControl surfaceControl) { + mTransaction.hide(surfaceControl); return this; } - public TransactionCompat setPosition(SurfaceControlCompat surfaceControl, float x, float y) { - mTransaction.setPosition(surfaceControl.mSurfaceControl, x, y); + public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) { + mTransaction.setPosition(surfaceControl, x, y); return this; } - public TransactionCompat setSize(SurfaceControlCompat surfaceControl, int w, int h) { - mTransaction.setBufferSize(surfaceControl.mSurfaceControl, w, h); + public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) { + mTransaction.setBufferSize(surfaceControl, w, h); return this; } - public TransactionCompat setLayer(SurfaceControlCompat surfaceControl, int z) { - mTransaction.setLayer(surfaceControl.mSurfaceControl, z); + public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) { + mTransaction.setLayer(surfaceControl, z); return this; } - public TransactionCompat setAlpha(SurfaceControlCompat surfaceControl, float alpha) { - mTransaction.setAlpha(surfaceControl.mSurfaceControl, alpha); + public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) { + mTransaction.setAlpha(surfaceControl, alpha); return this; } - public TransactionCompat setOpaque(SurfaceControlCompat surfaceControl, boolean opaque) { - mTransaction.setOpaque(surfaceControl.mSurfaceControl, opaque); + public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) { + mTransaction.setOpaque(surfaceControl, opaque); return this; } - public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, float dsdx, float dtdx, + public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx, float dtdy, float dsdy) { - mTransaction.setMatrix(surfaceControl.mSurfaceControl, dsdx, dtdx, dtdy, dsdy); + mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy); return this; } - public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, Matrix matrix) { - mTransaction.setMatrix(surfaceControl.mSurfaceControl, matrix, mTmpValues); + public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) { + mTransaction.setMatrix(surfaceControl, matrix, mTmpValues); return this; } - public TransactionCompat setWindowCrop(SurfaceControlCompat surfaceControl, Rect crop) { - mTransaction.setWindowCrop(surfaceControl.mSurfaceControl, crop); + public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) { + mTransaction.setWindowCrop(surfaceControl, crop); return this; } - public TransactionCompat setCornerRadius(SurfaceControlCompat surfaceControl, float radius) { - mTransaction.setCornerRadius(surfaceControl.mSurfaceControl, radius); + public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) { + mTransaction.setCornerRadius(surfaceControl, radius); return this; } - public TransactionCompat setBackgroundBlurRadius(SurfaceControlCompat surfaceControl, - int radius) { - mTransaction.setBackgroundBlurRadius(surfaceControl.mSurfaceControl, radius); + public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) { + mTransaction.setBackgroundBlurRadius(surfaceControl, radius); return this; } - public TransactionCompat setColor(SurfaceControlCompat surfaceControl, float[] color) { - mTransaction.setColor(surfaceControl.mSurfaceControl, color); + public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) { + mTransaction.setColor(surfaceControl, color); return this; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt new file mode 100644 index 000000000000..ac62cf9f9d5b --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt @@ -0,0 +1,62 @@ +/* + * 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.systemui.unfold + +import android.content.ContentResolver +import android.content.Context +import android.hardware.SensorManager +import android.hardware.devicestate.DeviceStateManager +import android.os.Handler +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.updates.screen.ScreenStatusProvider +import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix +import dagger.BindsInstance +import dagger.Component +import java.util.Optional +import java.util.concurrent.Executor +import javax.inject.Singleton + +/** + * Provides [UnfoldTransitionProgressProvider]. The [Optional] is empty when the transition + * animation is disabled. + * + * This component is meant to be used for places that don't use dagger. By providing those + * parameters to the factory, all dagger objects are correctly instantiated. See + * [createUnfoldTransitionProgressProvider] for an example. + */ +@Singleton +@Component(modules = [UnfoldSharedModule::class]) +internal interface UnfoldSharedComponent { + + @Component.Factory + interface Factory { + fun create( + @BindsInstance context: Context, + @BindsInstance config: UnfoldTransitionConfig, + @BindsInstance screenStatusProvider: ScreenStatusProvider, + @BindsInstance deviceStateManager: DeviceStateManager, + @BindsInstance sensorManager: SensorManager, + @BindsInstance @Main handler: Handler, + @BindsInstance @Main executor: Executor, + @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String, + @BindsInstance contentResolver: ContentResolver = context.contentResolver + ): UnfoldSharedComponent + } + + val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider> +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt new file mode 100644 index 000000000000..23e4c97fc271 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt @@ -0,0 +1,77 @@ +/* + * 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.systemui.unfold + +import android.hardware.SensorManager +import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider +import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider +import com.android.systemui.unfold.updates.DeviceFoldStateProvider +import com.android.systemui.unfold.updates.FoldStateProvider +import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider +import com.android.systemui.unfold.updates.hinge.HingeAngleProvider +import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider +import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener +import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider +import dagger.Module +import dagger.Provides +import java.util.Optional +import javax.inject.Singleton + +@Module +class UnfoldSharedModule { + @Provides + @Singleton + fun unfoldTransitionProgressProvider( + config: UnfoldTransitionConfig, + scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory, + tracingListener: ATraceLoggerTransitionProgressListener, + foldStateProvider: FoldStateProvider + ): Optional<UnfoldTransitionProgressProvider> = + if (!config.isEnabled) { + Optional.empty() + } else { + val baseProgressProvider = + if (config.isHingeAngleEnabled) { + PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider) + } else { + FixedTimingTransitionProgressProvider(foldStateProvider) + } + Optional.of( + scaleAwareProviderFactory.wrap(baseProgressProvider).apply { + // Always present callback that logs animation beginning and end. + addCallback(tracingListener) + }) + } + + @Provides + @Singleton + fun provideFoldStateProvider( + deviceFoldStateProvider: DeviceFoldStateProvider + ): FoldStateProvider = deviceFoldStateProvider + + @Provides + fun hingeAngleProvider( + config: UnfoldTransitionConfig, + sensorManager: SensorManager + ): HingeAngleProvider = + if (config.isHingeAngleEnabled) { + HingeSensorAngleProvider(sensorManager) + } else { + EmptyHingeAngleProvider + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index 953b0e018306..d5d636208747 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -23,22 +23,16 @@ import android.hardware.devicestate.DeviceStateManager import android.os.Handler import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig -import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider -import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider -import com.android.systemui.unfold.updates.DeviceFoldStateProvider -import com.android.systemui.unfold.updates.FoldStateProvider -import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider -import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider -import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener -import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider import java.util.concurrent.Executor /** * Factory for [UnfoldTransitionProgressProvider]. * - * This is needed as Launcher has to create the object manually. Sysui create it using dagger (see - * [UnfoldTransitionModule]). + * This is needed as Launcher has to create the object manually. If dagger is available, this object + * is provided in [UnfoldSharedModule]. + * + * This should **never** be called from sysui, as the object is already provided in that process. */ fun createUnfoldTransitionProgressProvider( context: Context, @@ -49,62 +43,21 @@ fun createUnfoldTransitionProgressProvider( mainHandler: Handler, mainExecutor: Executor, tracingTagPrefix: String -): UnfoldTransitionProgressProvider { - - if (!config.isEnabled) { - throw IllegalStateException( - "Trying to create " + - "UnfoldTransitionProgressProvider when the transition is disabled") - } - - val foldStateProvider = - createFoldStateProvider( +): UnfoldTransitionProgressProvider = + DaggerUnfoldSharedComponent.factory() + .create( context, config, screenStatusProvider, deviceStateManager, sensorManager, mainHandler, - mainExecutor) - - val unfoldTransitionProgressProvider = - if (config.isHingeAngleEnabled) { - PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider) - } else { - FixedTimingTransitionProgressProvider(foldStateProvider) - } - - return ScaleAwareTransitionProgressProvider( - unfoldTransitionProgressProvider, context.contentResolver) - .apply { - // Always present callback that logs animation beginning and end. - addCallback(ATraceLoggerTransitionProgressListener(tracingTagPrefix)) - } -} - -fun createFoldStateProvider( - context: Context, - config: UnfoldTransitionConfig, - screenStatusProvider: ScreenStatusProvider, - deviceStateManager: DeviceStateManager, - sensorManager: SensorManager, - mainHandler: Handler, - mainExecutor: Executor -): FoldStateProvider { - val hingeAngleProvider = - if (config.isHingeAngleEnabled) { - HingeSensorAngleProvider(sensorManager) - } else { - EmptyHingeAngleProvider() - } - - return DeviceFoldStateProvider( - context, - hingeAngleProvider, - screenStatusProvider, - deviceStateManager, - mainExecutor, - mainHandler) -} + mainExecutor, + tracingTagPrefix) + .unfoldTransitionProvider + .orElse(null) + ?: throw IllegalStateException( + "Trying to create " + + "UnfoldTransitionProgressProvider when the transition is disabled") fun createConfig(context: Context): UnfoldTransitionConfig = ResourceUnfoldTransitionConfig(context) diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt index e17f43e64b21..409dc95ab131 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt @@ -16,12 +16,14 @@ package com.android.systemui.unfold import android.annotation.FloatRange -import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener /** * Interface that allows to receive unfold transition progress updates. + * * It can be used to update view properties based on the current animation progress. + * * onTransitionProgress callback could be called on each frame. * * Use [createUnfoldTransitionProgressProvider] to create instances of this interface diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt index 3f027e30b473..d1b06394b818 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt @@ -18,9 +18,8 @@ package com.android.systemui.unfold.config import android.content.Context import android.os.SystemProperties -internal class ResourceUnfoldTransitionConfig( - private val context: Context -) : UnfoldTransitionConfig { +internal class ResourceUnfoldTransitionConfig(private val context: Context) : + UnfoldTransitionConfig { override val isEnabled: Boolean get() = readIsEnabledResource() && isPropertyEnabled @@ -29,19 +28,22 @@ internal class ResourceUnfoldTransitionConfig( get() = readIsHingeAngleEnabled() private val isPropertyEnabled: Boolean - get() = SystemProperties.getInt(UNFOLD_TRANSITION_MODE_PROPERTY_NAME, - UNFOLD_TRANSITION_PROPERTY_ENABLED) == UNFOLD_TRANSITION_PROPERTY_ENABLED + get() = + SystemProperties.getInt( + UNFOLD_TRANSITION_MODE_PROPERTY_NAME, UNFOLD_TRANSITION_PROPERTY_ENABLED) == + UNFOLD_TRANSITION_PROPERTY_ENABLED - private fun readIsEnabledResource(): Boolean = context.resources - .getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled) + private fun readIsEnabledResource(): Boolean = + context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled) - private fun readIsHingeAngleEnabled(): Boolean = context.resources - .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle) + private fun readIsHingeAngleEnabled(): Boolean = + context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle) } /** - * Temporary persistent property to control unfold transition mode - * See [com.android.unfold.config.AnimationMode] + * Temporary persistent property to control unfold transition mode. + * + * See [com.android.unfold.config.AnimationMode]. */ private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_enabled" private const val UNFOLD_TRANSITION_PROPERTY_ENABLED = 1 diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt index 732882e99038..4c85b055aeae 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt @@ -25,21 +25,17 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate -/** - * Emits animation progress with fixed timing after unfolding - */ +/** Emits animation progress with fixed timing after unfolding */ internal class FixedTimingTransitionProgressProvider( private val foldStateProvider: FoldStateProvider ) : UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener { private val animatorListener = AnimatorListener() private val animator = - ObjectAnimator.ofFloat(this, AnimationProgressProperty, 0f, 1f) - .apply { - duration = TRANSITION_TIME_MILLIS - addListener(animatorListener) - } - + ObjectAnimator.ofFloat(this, AnimationProgressProperty, 0f, 1f).apply { + duration = TRANSITION_TIME_MILLIS + addListener(animatorListener) + } private var transitionProgress: Float = 0.0f set(value) { @@ -62,10 +58,8 @@ internal class FixedTimingTransitionProgressProvider( override fun onFoldUpdate(@FoldUpdate update: Int) { when (update) { - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> - animator.start() - FOLD_UPDATE_FINISH_CLOSED -> - animator.cancel() + FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> animator.start() + FOLD_UPDATE_FINISH_CLOSED -> animator.cancel() } } @@ -77,16 +71,12 @@ internal class FixedTimingTransitionProgressProvider( listeners.remove(listener) } - override fun onHingeAngleUpdate(angle: Float) { - } + override fun onHingeAngleUpdate(angle: Float) {} private object AnimationProgressProperty : FloatProperty<FixedTimingTransitionProgressProvider>("animation_progress") { - override fun setValue( - provider: FixedTimingTransitionProgressProvider, - value: Float - ) { + override fun setValue(provider: FixedTimingTransitionProgressProvider, value: Float) { provider.transitionProgress = value } @@ -104,11 +94,9 @@ internal class FixedTimingTransitionProgressProvider( listeners.forEach { it.onTransitionFinished() } } - override fun onAnimationRepeat(animator: Animator) { - } + override fun onAnimationRepeat(animator: Animator) {} - override fun onAnimationCancel(animator: Animator) { - } + override fun onAnimationCancel(animator: Animator) {} } private companion object { diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index a701b44cf916..5266f096e12c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -23,10 +23,10 @@ import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener -import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED -import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN +import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate @@ -35,13 +35,10 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener /** Maps fold updates to unfold transition progress using DynamicAnimation. */ internal class PhysicsBasedUnfoldTransitionProgressProvider( private val foldStateProvider: FoldStateProvider -) : - UnfoldTransitionProgressProvider, - FoldUpdatesListener, - DynamicAnimation.OnAnimationEndListener { +) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { - private val springAnimation = SpringAnimation(this, AnimationProgressProperty) - .apply { + private val springAnimation = + SpringAnimation(this, AnimationProgressProperty).apply { addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) } @@ -121,9 +118,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( isTransitionRunning = false springAnimation.cancel() - listeners.forEach { - it.onTransitionFinished() - } + listeners.forEach { it.onTransitionFinished() } if (DEBUG) { Log.d(TAG, "onTransitionFinished") @@ -143,9 +138,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( } private fun onStartTransition() { - listeners.forEach { - it.onTransitionStarted() - } + listeners.forEach { it.onTransitionStarted() } isTransitionRunning = true if (DEBUG) { @@ -157,11 +150,12 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( if (!isTransitionRunning) onStartTransition() springAnimation.apply { - spring = SpringForce().apply { - finalPosition = startValue - dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY - stiffness = SPRING_STIFFNESS - } + spring = + SpringForce().apply { + finalPosition = startValue + dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY + stiffness = SPRING_STIFFNESS + } minimumVisibleChange = MINIMAL_VISIBLE_CHANGE setStartValue(startValue) setMinValue(0f) diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index cd1ea215ccdd..24ecf8781a18 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -22,6 +22,7 @@ import android.os.Handler import android.util.Log import androidx.annotation.VisibleForTesting import androidx.core.util.Consumer +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES @@ -29,23 +30,24 @@ import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import java.util.concurrent.Executor +import javax.inject.Inject -class DeviceFoldStateProvider( +class DeviceFoldStateProvider +@Inject +constructor( context: Context, private val hingeAngleProvider: HingeAngleProvider, private val screenStatusProvider: ScreenStatusProvider, private val deviceStateManager: DeviceStateManager, - private val mainExecutor: Executor, - private val handler: Handler + @Main private val mainExecutor: Executor, + @Main private val handler: Handler ) : FoldStateProvider { private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf() - @FoldUpdate - private var lastFoldUpdate: Int? = null + @FoldUpdate private var lastFoldUpdate: Int? = null - @FloatRange(from = 0.0, to = 180.0) - private var lastHingeAngle: Float = 0f + @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f private val hingeAngleListener = HingeAngleListener() private val screenListener = ScreenStatusListener() @@ -56,10 +58,7 @@ class DeviceFoldStateProvider( private var isUnfoldHandled = true override fun start() { - deviceStateManager.registerCallback( - mainExecutor, - foldStateListener - ) + deviceStateManager.registerCallback(mainExecutor, foldStateListener) screenStatusProvider.addCallback(screenListener) hingeAngleProvider.addCallback(hingeAngleListener) } @@ -83,11 +82,14 @@ class DeviceFoldStateProvider( get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN private val isTransitionInProgess: Boolean - get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING || + get() = + lastFoldUpdate == FOLD_UPDATE_START_OPENING || lastFoldUpdate == FOLD_UPDATE_START_CLOSING private fun onHingeAngle(angle: Float) { - if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") } + if (DEBUG) { + Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") + } val isClosing = angle < lastHingeAngle val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES @@ -112,24 +114,28 @@ class DeviceFoldStateProvider( } private inner class FoldStateListener(context: Context) : - DeviceStateManager.FoldStateListener(context, { folded: Boolean -> - isFolded = folded - lastHingeAngle = FULLY_CLOSED_DEGREES - - if (folded) { - hingeAngleProvider.stop() - notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) - cancelTimeout() - isUnfoldHandled = false - } else { - notifyFoldUpdate(FOLD_UPDATE_START_OPENING) - rescheduleAbortAnimationTimeout() - hingeAngleProvider.start() - } - }) + DeviceStateManager.FoldStateListener( + context, + { folded: Boolean -> + isFolded = folded + lastHingeAngle = FULLY_CLOSED_DEGREES + + if (folded) { + hingeAngleProvider.stop() + notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + cancelTimeout() + isUnfoldHandled = false + } else { + notifyFoldUpdate(FOLD_UPDATE_START_OPENING) + rescheduleAbortAnimationTimeout() + hingeAngleProvider.start() + } + }) private fun notifyFoldUpdate(@FoldUpdate update: Int) { - if (DEBUG) { Log.d(TAG, stateToString(update)) } + if (DEBUG) { + Log.d(TAG, stateToString(update)) + } outputListeners.forEach { it.onFoldUpdate(update) } lastFoldUpdate = update } @@ -145,8 +151,7 @@ class DeviceFoldStateProvider( handler.removeCallbacks(timeoutRunnable) } - private inner class ScreenStatusListener : - ScreenStatusProvider.ScreenListener { + private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { override fun onScreenTurnedOn() { // Trigger this event only if we are unfolded and this is the first screen @@ -194,9 +199,7 @@ private const val DEBUG = false * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a * [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not reached. */ -@VisibleForTesting -const val HALF_OPENED_TIMEOUT_MILLIS = 1000L +@VisibleForTesting const val HALF_OPENED_TIMEOUT_MILLIS = 1000L /** Threshold after which we consider the device fully unfolded. */ -@VisibleForTesting -const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file +@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt index df3563df5fc6..5495316cd5b2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt @@ -17,8 +17,8 @@ package com.android.systemui.unfold.updates import android.annotation.FloatRange import android.annotation.IntDef -import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener /** * Allows to subscribe to main events related to fold/unfold process such as hinge angle update, @@ -35,14 +35,16 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { fun onFoldUpdate(@FoldUpdate update: Int) } - @IntDef(prefix = ["FOLD_UPDATE_"], value = [ - FOLD_UPDATE_START_OPENING, - FOLD_UPDATE_START_CLOSING, - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, - FOLD_UPDATE_FINISH_HALF_OPEN, - FOLD_UPDATE_FINISH_FULL_OPEN, - FOLD_UPDATE_FINISH_CLOSED - ]) + @IntDef( + prefix = ["FOLD_UPDATE_"], + value = + [ + FOLD_UPDATE_START_OPENING, + FOLD_UPDATE_START_CLOSING, + FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, + FOLD_UPDATE_FINISH_HALF_OPEN, + FOLD_UPDATE_FINISH_FULL_OPEN, + FOLD_UPDATE_FINISH_CLOSED]) @Retention(AnnotationRetention.SOURCE) annotation class FoldUpdate } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt index 9b58b1fcad46..b351585de364 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt @@ -2,16 +2,12 @@ package com.android.systemui.unfold.updates.hinge import androidx.core.util.Consumer -internal class EmptyHingeAngleProvider : HingeAngleProvider { - override fun start() { - } +internal object EmptyHingeAngleProvider : HingeAngleProvider { + override fun start() {} - override fun stop() { - } + override fun stop() {} - override fun removeCallback(listener: Consumer<Float>) { - } + override fun removeCallback(listener: Consumer<Float>) {} - override fun addCallback(listener: Consumer<Float>) { - } + override fun addCallback(listener: Consumer<Float>) {} } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt index 6f524560de99..48a5b12c759a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt @@ -5,9 +5,10 @@ import com.android.systemui.statusbar.policy.CallbackController /** * Emits device hinge angle values (angle between two integral parts of the device). - * The hinge angle could be from 0 to 360 degrees inclusive. - * For foldable devices usually 0 corresponds to fully closed (folded) state and - * 180 degrees corresponds to fully open (flat) state + * + * The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0 + * corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat) + * state. */ interface HingeAngleProvider : CallbackController<Consumer<Float>> { fun start() diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt index a42ebef04de1..f6fe1ede9ce0 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt @@ -6,9 +6,8 @@ import android.hardware.SensorEventListener import android.hardware.SensorManager import androidx.core.util.Consumer -internal class HingeSensorAngleProvider( - private val sensorManager: SensorManager -) : HingeAngleProvider { +internal class HingeSensorAngleProvider(private val sensorManager: SensorManager) : + HingeAngleProvider { private val sensorListener = HingeAngleSensorListener() private val listeners: MutableList<Consumer<Float>> = arrayListOf() @@ -32,8 +31,7 @@ internal class HingeSensorAngleProvider( private inner class HingeAngleSensorListener : SensorEventListener { - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - } + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} override fun onSensorChanged(event: SensorEvent) { listeners.forEach { it.accept(event.values[0]) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt index 1eec8033ac5d..668c69442cac 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.unfold.updates.screen -import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener interface ScreenStatusProvider : CallbackController<ScreenListener> { diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt index f3eeb3210ece..1574c8d37ab1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt @@ -2,6 +2,8 @@ package com.android.systemui.unfold.util import android.os.Trace import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import javax.inject.Inject +import javax.inject.Qualifier /** * Listener that logs start and end of the fold-unfold transition. @@ -9,7 +11,10 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr * [tracePrefix] arg helps in differentiating those. Currently, this is expected to be logged twice * for each fold/unfold: in (1) systemui and (2) launcher process. */ -class ATraceLoggerTransitionProgressListener(tracePrefix: String) : TransitionProgressListener { +class ATraceLoggerTransitionProgressListener +@Inject +internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) : + TransitionProgressListener { private val traceName = "$tracePrefix#$UNFOLD_TRANSITION_TRACE_NAME" @@ -27,3 +32,5 @@ class ATraceLoggerTransitionProgressListener(tracePrefix: String) : TransitionPr } private const val UNFOLD_TRANSITION_TRACE_NAME = "FoldUnfoldTransitionInProgress" + +@Qualifier annotation class UnfoldTransitionATracePrefix diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt new file mode 100644 index 000000000000..2b38f3d6aab8 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt @@ -0,0 +1,37 @@ +/* + * 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.systemui.unfold.util + +import android.view.View +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.jank.InteractionJankMonitor.CUJ_UNFOLD_ANIM +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import java.util.function.Supplier + +class JankMonitorTransitionProgressListener(private val attachedViewProvider: Supplier<View>) : + TransitionProgressListener { + + private val interactionJankMonitor = InteractionJankMonitor.getInstance() + + override fun onTransitionStarted() { + interactionJankMonitor.begin(attachedViewProvider.get(), CUJ_UNFOLD_ANIM) + } + + override fun onTransitionFinished() { + interactionJankMonitor.end(CUJ_UNFOLD_ANIM) + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt index 58d7dfb133a5..53c528ff24a8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt @@ -10,9 +10,8 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr /** * [UnfoldTransitionProgressProvider] that emits transition progress only when the display has - * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180). - * It could be helpful to run the animation only when the display's rotation is perpendicular - * to the fold. + * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180). It could be + * helpful to run the animation only when the display's rotation is perpendicular to the fold. */ class NaturalRotationUnfoldProgressProvider( private val context: Context, @@ -21,7 +20,7 @@ class NaturalRotationUnfoldProgressProvider( ) : UnfoldTransitionProgressProvider { private val scopedUnfoldTransitionProgressProvider = - ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) + ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) private val rotationWatcher = RotationWatcher() private var isNaturalRotation: Boolean = false @@ -37,8 +36,8 @@ class NaturalRotationUnfoldProgressProvider( } private fun onRotationChanged(rotation: Int) { - val isNewRotationNatural = rotation == Surface.ROTATION_0 || - rotation == Surface.ROTATION_180 + val isNewRotationNatural = + rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 if (isNaturalRotation != isNewRotationNatural) { isNaturalRotation = isNewRotationNatural diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt index df9078a15520..dfe87921dd42 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt @@ -6,27 +6,33 @@ import android.database.ContentObserver import android.provider.Settings import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject /** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */ -class ScaleAwareTransitionProgressProvider( - unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, +class ScaleAwareTransitionProgressProvider +@AssistedInject +constructor( + @Assisted progressProviderToWrap: UnfoldTransitionProgressProvider, private val contentResolver: ContentResolver ) : UnfoldTransitionProgressProvider { private val scopedUnfoldTransitionProgressProvider = - ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) + ScopedUnfoldTransitionProgressProvider(progressProviderToWrap) - private val animatorDurationScaleObserver = object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - onAnimatorScaleChanged() + private val animatorDurationScaleObserver = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + onAnimatorScaleChanged() + } } - } init { contentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), - /* notifyForDescendants= */ false, - animatorDurationScaleObserver) + Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), + /* notifyForDescendants= */ false, + animatorDurationScaleObserver) onAnimatorScaleChanged() } @@ -47,4 +53,11 @@ class ScaleAwareTransitionProgressProvider( contentResolver.unregisterContentObserver(animatorDurationScaleObserver) scopedUnfoldTransitionProgressProvider.destroy() } + + @AssistedFactory + interface Factory { + fun wrap( + progressProvider: UnfoldTransitionProgressProvider + ): ScaleAwareTransitionProgressProvider + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt index a274b74f336b..7b6791770295 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt @@ -20,16 +20,18 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr /** * Manages progress listeners that can have smaller lifespan than the unfold animation. + * * Allows to limit getting transition updates to only when - * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called - * with readyToHandleTransition = true + * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called with + * readyToHandleTransition = true * - * If the transition has already started by the moment when the clients are ready to play - * the transition then it will report transition started callback and current animation progress. + * If the transition has already started by the moment when the clients are ready to play the + * transition then it will report transition started callback and current animation progress. */ -class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor( - source: UnfoldTransitionProgressProvider? = null -) : UnfoldTransitionProgressProvider, TransitionProgressListener { +class ScopedUnfoldTransitionProgressProvider +@JvmOverloads +constructor(source: UnfoldTransitionProgressProvider? = null) : + UnfoldTransitionProgressProvider, TransitionProgressListener { private var source: UnfoldTransitionProgressProvider? = null @@ -43,8 +45,8 @@ class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor( setSourceProvider(source) } /** - * Sets the source for the unfold transition progress updates, - * it replaces current provider if it is already set + * Sets the source for the unfold transition progress updates. Replaces current provider if it + * is already set * @param provider transition provider that emits transition progress updates */ fun setSourceProvider(provider: UnfoldTransitionProgressProvider?) { @@ -60,8 +62,10 @@ class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor( /** * Allows to notify this provide whether the listeners can play the transition or not. - * Call this method with readyToHandleTransition = true when all listeners - * are ready to consume the transition progress events. + * + * Call this method with readyToHandleTransition = true when all listeners are ready to consume + * the transition progress events. + * * Call it with readyToHandleTransition = false when listeners can't process the events. */ fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) { diff --git a/core/java/android/bluetooth/BluetoothUtils.java b/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java index 867469241f84..dc804ca61dcf 100644 --- a/core/java/android/bluetooth/BluetoothUtils.java +++ b/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java @@ -14,28 +14,10 @@ * limitations under the License. */ -package android.bluetooth; +package com.android.systemui.util; -import java.time.Duration; - -/** - * {@hide} - */ -public final class BluetoothUtils { - /** - * This utility class cannot be instantiated - */ - private BluetoothUtils() {} - - /** - * Timeout value for synchronous binder call - */ - private static final Duration SYNC_CALLS_TIMEOUT = Duration.ofSeconds(5); - - /** - * @return timeout value for synchronous binder call - */ - static Duration getSyncTimeout() { - return SYNC_CALLS_TIMEOUT; - } +/** Constants that vary by compilation configuration. */ +public class Compile { + /** Whether SystemUI was compiled in debug mode, and supports debug features */ + public static final boolean IS_DEBUG = true; } diff --git a/packages/SystemUI/src-release/com/android/systemui/util/Compile.java b/packages/SystemUI/src-release/com/android/systemui/util/Compile.java new file mode 100644 index 000000000000..8a6376326660 --- /dev/null +++ b/packages/SystemUI/src-release/com/android/systemui/util/Compile.java @@ -0,0 +1,23 @@ +/* + * 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.systemui.util; + +/** Constants that vary by compilation configuration. */ +public class Compile { + /** Whether SystemUI was compiled in debug mode, and supports debug features */ + public static final boolean IS_DEBUG = false; +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index cc6df45c598f..d02b8752469a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -30,7 +30,6 @@ import com.android.systemui.R; */ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { protected View mEcaView; - protected boolean mEnableHaptics; // To avoid accidental lockout due to events while the device in in the pocket, ignore // any passwords with length less than or equal to this length. @@ -45,10 +44,6 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { super(context, attrs); } - void setEnableHaptics(boolean enableHaptics) { - mEnableHaptics = enableHaptics; - } - protected abstract int getPasswordTextViewId(); protected abstract void resetState(); @@ -80,11 +75,9 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { // Cause a VIRTUAL_KEY vibration public void doHapticKeyClick() { - if (mEnableHaptics) { - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } public void setKeyDownListener(KeyDownListener keyDownListener) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 1c4559eb0364..f86d08de87fc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -98,7 +98,6 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey protected void onViewAttached() { super.onViewAttached(); mView.setKeyDownListener(mKeyDownListener); - mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt new file mode 100644 index 000000000000..214b284ac4b9 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt @@ -0,0 +1,176 @@ +/* + * 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.keyguard + +import android.content.Context +import android.hardware.biometrics.BiometricSourceType +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE +import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Logs events when primary authentication requirements change. Primary authentication is considered + * authentication using pin/pattern/password input. + * + * See [PrimaryAuthRequiredEvent] for all the events and their descriptions. + */ +@SysUISingleton +class KeyguardBiometricLockoutLogger @Inject constructor( + context: Context?, + private val uiEventLogger: UiEventLogger, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val dumpManager: DumpManager +) : CoreStartable(context) { + private var fingerprintLockedOut = false + private var faceLockedOut = false + private var encryptedOrLockdown = false + private var unattendedUpdate = false + private var timeout = false + + override fun start() { + dumpManager.registerDumpable(this) + mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged( + KeyguardUpdateMonitor.getCurrentUser()) + keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback) + } + + private val mKeyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback = + object : KeyguardUpdateMonitorCallback() { + override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT) { + val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut + if (lockedOut && !fingerprintLockedOut) { + uiEventLogger.log( + PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT) + } else if (!lockedOut && fingerprintLockedOut) { + uiEventLogger.log( + PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET) + } + fingerprintLockedOut = lockedOut + } else if (biometricSourceType == BiometricSourceType.FACE) { + val lockedOut = keyguardUpdateMonitor.isFaceLockedOut + if (lockedOut && !faceLockedOut) { + uiEventLogger.log( + PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT) + } else if (!lockedOut && faceLockedOut) { + uiEventLogger.log( + PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET) + } + faceLockedOut = lockedOut + } + } + + override fun onStrongAuthStateChanged(userId: Int) { + if (userId != KeyguardUpdateMonitor.getCurrentUser()) { + return + } + val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker + .getStrongAuthForUser(userId) + + val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId) + if (newEncryptedOrLockdown && !encryptedOrLockdown) { + uiEventLogger.log( + PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN) + } + encryptedOrLockdown = newEncryptedOrLockdown + + val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags) + if (newUnattendedUpdate && !unattendedUpdate) { + uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + } + unattendedUpdate = newUnattendedUpdate + + val newTimeout = isStrongAuthTimeout(strongAuthFlags) + if (newTimeout && !timeout) { + uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT) + } + timeout = newTimeout + } + } + + private fun isUnattendedUpdate( + @LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int + ) = containsFlag(flags, STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) + + private fun isStrongAuthTimeout( + @LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int + ) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) || + containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { + pw.println(" mFingerprintLockedOut=$fingerprintLockedOut") + pw.println(" mFaceLockedOut=$faceLockedOut") + pw.println(" mIsEncryptedOrLockdown=$encryptedOrLockdown") + pw.println(" mIsUnattendedUpdate=$unattendedUpdate") + pw.println(" mIsTimeout=$timeout") + } + + /** + * Events pertaining to whether primary authentication (pin/pattern/password input) is required + * for device entry. + */ + @VisibleForTesting + enum class PrimaryAuthRequiredEvent(private val mId: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "Fingerprint cannot be used to authenticate for device entry. This" + + "can persist until the next primary auth or may timeout.") + PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT(924), + + @UiEvent(doc = "Fingerprint can be used to authenticate for device entry.") + PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET(925), + + @UiEvent(doc = "Face cannot be used to authenticate for device entry.") + PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT(926), + + @UiEvent(doc = "Face can be used to authenticate for device entry.") + PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET(927), + + @UiEvent(doc = "Device is encrypted (ie: after reboot) or device is locked down by DPM " + + "or a manual user lockdown.") + PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN(928), + + @UiEvent(doc = "Primary authentication is required because it hasn't been used for a " + + "time required by a device admin or because primary auth hasn't been used for a " + + "time after a non-strong biometric (weak or convenience) is used to unlock the " + + "device.") + PRIMARY_AUTH_REQUIRED_TIMEOUT(929), + + @UiEvent(doc = "Strong authentication is required to prepare for unattended upgrade.") + PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE(931); + + override fun getId(): Int { + return mId + } + } + + companion object { + private fun containsFlag(strongAuthFlags: Int, flagCheck: Int): Boolean { + return strongAuthFlags and flagCheck != 0 + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 8c7ede26e2e6..848b8ab8ff84 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -262,7 +262,6 @@ public class KeyguardDisplayManager { private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; - private final Context mContext; private KeyguardClockSwitchController mKeyguardClockSwitchController; private View mClock; private int mUsableWidth; @@ -286,7 +285,6 @@ public class KeyguardDisplayManager { WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; setCancelable(false); - mContext = context; } @Override @@ -311,7 +309,7 @@ public class KeyguardDisplayManager { updateBounds(); - setContentView(LayoutInflater.from(mContext) + setContentView(LayoutInflater.from(getContext()) .inflate(R.layout.keyguard_presentation, null)); // Logic to make the lock screen fullscreen diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java index 26c227dc8929..8bcb7c9e29c2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java @@ -91,7 +91,8 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { protected void onAttachedToWindow() { super.onAttachedToWindow(); mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_DISABLE_ESIM), - PERMISSION_SELF, null /* scheduler */); + PERMISSION_SELF, null /* scheduler */, + Context.RECEIVER_EXPORTED_UNAUDITED); } public static boolean isEsimLocked(Context context, int subId) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index 03f04d3a2cde..36fe5ba1a851 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -64,3 +64,19 @@ data class KeyguardFaceListenModel( val secureCameraLaunched: Boolean, val switchingUser: Boolean ) : KeyguardListenModel() +/** + * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock]. + */ +data class KeyguardActiveUnlockModel( + @CurrentTimeMillisLong override val timeMillis: Long, + override val userId: Int, + override val listening: Boolean, + // keep sorted + val authInterruptActive: Boolean, + val encryptedOrTimedOut: Boolean, + val fpLockout: Boolean, + val lockDown: Boolean, + val switchingUser: Boolean, + val triggerActiveUnlockForAssistant: Boolean, + val userCanDismissLockScreen: Boolean +) : KeyguardListenModel() diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt index f13a59a84811..210f5e763911 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt @@ -32,15 +32,17 @@ class KeyguardListenQueue( ) { private val faceQueue = ArrayDeque<KeyguardFaceListenModel>() private val fingerprintQueue = ArrayDeque<KeyguardFingerprintListenModel>() + private val activeUnlockQueue = ArrayDeque<KeyguardActiveUnlockModel>() @get:VisibleForTesting val models: List<KeyguardListenModel> - get() = faceQueue + fingerprintQueue + get() = faceQueue + fingerprintQueue + activeUnlockQueue /** Push a [model] to the queue (will be logged until the queue exceeds [sizePerModality]). */ fun add(model: KeyguardListenModel) { val queue = when (model) { is KeyguardFaceListenModel -> faceQueue.apply { add(model) } is KeyguardFingerprintListenModel -> fingerprintQueue.apply { add(model) } + is KeyguardActiveUnlockModel -> activeUnlockQueue.apply { add(model) } } if (queue.size > sizePerModality) { @@ -63,5 +65,9 @@ class KeyguardListenQueue( for (model in fingerprintQueue) { writer.println(stringify(model)) } + writer.println(" Active unlock triggers (last ${activeUnlockQueue.size} calls):") + for (model in activeUnlockQueue) { + writer.println(stringify(model)) + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java index 099dd5d82a10..75579b05aeeb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -30,6 +30,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.internal.policy.SystemBarUtils; import com.android.settingslib.Utils; import com.android.systemui.R; @@ -57,6 +59,11 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR); private boolean mBouncerVisible; private boolean mAltBouncerShowing; + /** + * Container that wraps the KeyguardMessageArea - may be null if current view hierarchy doesn't + * contain {@link R.id.keyguard_message_area_container}. + */ + @Nullable private ViewGroup mContainer; private int mTopMargin; @@ -75,6 +82,9 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp } void onConfigChanged() { + if (mContainer == null) { + return; + } final int newTopMargin = SystemBarUtils.getStatusBarHeight(getContext()); if (mTopMargin == newTopMargin) { return; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 94e07b713915..238acd5db621 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -224,8 +224,6 @@ public class KeyguardPatternViewController mLockPatternView.setSaveEnabled(false); mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( KeyguardUpdateMonitor.getCurrentUser())); - // vibrate mode will be the same for the life of this screen - mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); mLockPatternView.setOnTouchListener((v, event) -> { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mFalsingCollector.avoidGesture(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 98721fd78a93..a348b423d3c7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -37,6 +37,8 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.UserSwitchObserver; import android.app.admin.DevicePolicyManager; @@ -102,6 +104,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -109,6 +113,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; +import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.RingerModeTracker; import com.google.android.collect.Lists; @@ -143,8 +148,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES; private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE; private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE; private static final boolean DEBUG_SPEW = false; private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600; + private int mNumActiveUnlockTriggers = 0; private static final String ACTION_FACE_UNLOCK_STARTED = "com.android.facelock.FACE_UNLOCK_STARTED"; @@ -183,7 +190,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_USER_STOPPED = 340; private static final int MSG_USER_REMOVED = 341; private static final int MSG_KEYGUARD_GOING_AWAY = 342; - private static final int MSG_LOCK_SCREEN_MODE = 343; private static final int MSG_TIME_FORMAT_UPDATE = 344; private static final int MSG_REQUIRE_NFC_UNLOCK = 345; @@ -221,7 +227,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1; public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2; - private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000; /** * If no cancel signal has been received after this amount of time, set the biometric running * state to stopped to allow Keyguard to retry authentication. @@ -231,7 +236,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName( "com.android.settings", "com.android.settings.FallbackHome"); - /** * If true, the system is in the half-boot-to-decryption-screen state. * Prudently disable lockscreen. @@ -334,6 +338,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final Executor mBackgroundExecutor; private SensorPrivacyManager mSensorPrivacyManager; + private FeatureFlags mFeatureFlags; private int mFaceAuthUserId; /** @@ -1250,7 +1255,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); } - private boolean isEncryptedOrLockdown(int userId) { + /** + * Returns true if primary authentication is required for the given user due to lockdown + * or encryption after reboot. + */ + public boolean isEncryptedOrLockdown(int userId) { final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId); final boolean isLockDown = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) @@ -1311,6 +1320,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab void setAssistantVisible(boolean assistantVisible) { mAssistantVisible = assistantVisible; updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); + if (mAssistantVisible) { + requestActiveUnlock(); + } } static class DisplayClientState { @@ -1650,6 +1662,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp"); Assert.isMainThread(); updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); + requestActiveUnlock(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1777,7 +1790,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab AuthController authController, TelephonyListenerManager telephonyListenerManager, InteractionJankMonitor interactionJankMonitor, - LatencyTracker latencyTracker) { + LatencyTracker latencyTracker, + FeatureFlags featureFlags) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); mTelephonyListenerManager = telephonyListenerManager; @@ -1795,6 +1809,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); + mFeatureFlags = featureFlags; mHandler = new Handler(mainLooper) { @Override @@ -2180,6 +2195,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mAuthInterruptActive = active; updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); + requestActiveUnlock(); } /** @@ -2228,6 +2244,97 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + /** + * Attempts to trigger active unlock. + */ + public void requestActiveUnlock() { + // If this message exists, FP has already authenticated, so wait until that is handled + if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) { + return; + } + + if (shouldTriggerActiveUnlock() && mFeatureFlags.isEnabled(Flags.ACTIVE_UNLOCK)) { + // TODO (b/192405661): call new TrustManager API + mNumActiveUnlockTriggers++; + Log.d("ActiveUnlock", "would have triggered times=" + mNumActiveUnlockTriggers); + showActiveUnlockNotification(mNumActiveUnlockTriggers); + } + } + + /** + * TODO (b/192405661): Only for testing. Remove before release. + */ + private void showActiveUnlockNotification(int times) { + final String message = "Active unlock triggered " + times + " times."; + final Notification.Builder nb = + new Notification.Builder(mContext, NotificationChannels.GENERAL) + .setSmallIcon(R.drawable.ic_volume_ringer) + .setContentTitle(message) + .setStyle(new Notification.BigTextStyle().bigText(message)); + mContext.getSystemService(NotificationManager.class).notifyAsUser( + "active_unlock", + 0, + nb.build(), + UserHandle.ALL); + } + + private boolean shouldTriggerActiveUnlock() { + // TODO: check if active unlock is ENABLED / AVAILABLE + + // Triggers: + final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant(); + final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep + && mStatusBarState != StatusBarState.SHADE_LOCKED; + + // Gates: + final int user = getCurrentUser(); + + // No need to trigger active unlock if we're already unlocked or don't have + // pin/pattern/password setup + final boolean userCanDismissLockScreen = getUserCanSkipBouncer(user) + || !mLockPatternUtils.isSecure(user); + + // Don't trigger active unlock if fp is locked out TODO: confirm this one + final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent; + + // Don't trigger active unlock if primary auth is required + final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); + final boolean isLockDown = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) + || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + final boolean isEncryptedOrTimedOut = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT) + || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); + + final boolean shouldTriggerActiveUnlock = + (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard) + && !mSwitchingUser + && !userCanDismissLockScreen + && !fpLockedout + && !isLockDown + && !isEncryptedOrTimedOut + && !mKeyguardGoingAway + && !mSecureCameraLaunched; + + // Aggregate relevant fields for debug logging. + if (DEBUG_ACTIVE_UNLOCK || DEBUG_SPEW) { + maybeLogListenerModelData( + new KeyguardActiveUnlockModel( + System.currentTimeMillis(), + user, + shouldTriggerActiveUnlock, + mAuthInterruptActive, + isEncryptedOrTimedOut, + fpLockedout, + isLockDown, + mSwitchingUser, + triggerActiveUnlockForAssistant, + userCanDismissLockScreen)); + } + + return shouldTriggerActiveUnlock; + } + private boolean shouldListenForFingerprintAssistant() { BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser()); return mAssistantVisible && mKeyguardOccluded @@ -2242,6 +2349,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mUserHasTrust.get(getCurrentUser(), false); } + private boolean shouldTriggerActiveUnlockForAssistant() { + return mAssistantVisible && mKeyguardOccluded + && !mUserHasTrust.get(getCurrentUser(), false); + } + @VisibleForTesting protected boolean shouldListenForFingerprint(boolean isUdfps) { final int user = getCurrentUser(); @@ -2406,6 +2518,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Log.v(TAG, model.toString()); } + if (DEBUG_ACTIVE_UNLOCK + && model instanceof KeyguardActiveUnlockModel + && model.getListening()) { + mListenModels.add(model); + return; + } + // Add model data to the historical buffer. final boolean notYetRunning = (DEBUG_FACE @@ -2514,6 +2633,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mFingerprintLockedOut || mFingerprintLockedOutPermanent; } + public boolean isFaceLockedOut() { + return mFaceLockedOutPermanent; + } + /** * If biometrics hardware is available, not disabled, and user has enrolled templates. * This does NOT check if the device is encrypted or in lockdown. @@ -3171,11 +3294,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } public void clearBiometricRecognized() { + clearBiometricRecognized(UserHandle.USER_NULL); + } + + public void clearBiometricRecognizedWhenKeyguardDone(int unlockedUser) { + clearBiometricRecognized(unlockedUser); + } + + private void clearBiometricRecognized(int unlockedUser) { Assert.isMainThread(); mUserFingerprintAuthenticated.clear(); mUserFaceAuthenticated.clear(); - mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT); - mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE); + mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT, unlockedUser); + mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, unlockedUser); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index e79ea9a44843..a5a3f80d07b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -213,10 +213,8 @@ public class NumPadKey extends ViewGroup { // Cause a VIRTUAL_KEY vibration public void doHapticKeyClick() { - if (mLockPatternUtils.isTactileFeedbackEnabled()) { - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 33538ec25fcd..9c2971cda493 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -96,6 +96,8 @@ import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.settings.SecureSettings; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -107,7 +109,7 @@ import javax.inject.Inject; * for antialiasing and emulation purposes. */ @SysUISingleton -public class ScreenDecorations extends CoreStartable implements Tunable { +public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable{ private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; @@ -342,7 +344,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable { mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics); mDensity = metrics.density; - mExecutor.execute(() -> mTunerService.addTunable(this, SIZE)); + mMainExecutor.execute(() -> mTunerService.addTunable(this, SIZE)); // Watch color inversion and invert the overlay as needed. if (mColorInversionSetting == null) { @@ -677,6 +679,20 @@ public class ScreenDecorations extends CoreStartable implements Tunable { }); } + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("ScreenDecorations state:"); + pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS); + pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius); + pw.println(" mIsPrivacyDotEnabled:" + mIsPrivacyDotEnabled); + pw.println(" mPendingRotationChange:" + mPendingRotationChange); + pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")"); + pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y + + ")"); + pw.println(" mRoundedDefaultBottom(x,y)=(" + mRoundedDefaultBottom.x + "," + + mRoundedDefaultBottom.y + ")"); + } + private void updateOrientation() { Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(), "must call on " + mHandler.getLooper().getThread() diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java index d7da63b9e262..1f2de4cfc346 100644 --- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java +++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java @@ -112,7 +112,8 @@ public class SliceBroadcastRelayHandler extends CoreStartable { public void register(Context context, ComponentName receiver, IntentFilter filter) { mReceivers.add(receiver); - context.registerReceiver(this, filter); + context.registerReceiver(this, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); } public void unregister(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 63962fa6da11..daca918ec0c5 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -32,6 +32,9 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Dumpable; +import android.util.DumpableContainer; import android.util.Log; import android.util.TimingsTraceLog; import android.view.SurfaceControl; @@ -53,13 +56,19 @@ import java.util.Collections; * Application class for SystemUI. */ public class SystemUIApplication extends Application implements - SystemUIAppComponentFactory.ContextInitializer { + SystemUIAppComponentFactory.ContextInitializer, DumpableContainer { public static final String TAG = "SystemUIService"; private static final boolean DEBUG = false; private ContextComponentHelper mComponentHelper; private BootCompleteCacheImpl mBootCompleteCache; + private DumpManager mDumpManager; + + /** + * Map of dumpables added externally. + */ + private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>(); /** * Hold a reference on the stuff we start. @@ -214,7 +223,7 @@ public class SystemUIApplication extends Application implements } } - final DumpManager dumpManager = mSysUIComponent.createDumpManager(); + mDumpManager = mSysUIComponent.createDumpManager(); Log.v(TAG, "Starting SystemUI services for user " + Process.myUserHandle().getIdentifier() + "."); @@ -255,7 +264,7 @@ public class SystemUIApplication extends Application implements mServices[i].onBootCompleted(); } - dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]); + mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]); } mSysUIComponent.getInitController().executePostInitTasks(); log.traceEnd(); @@ -263,6 +272,29 @@ public class SystemUIApplication extends Application implements mServicesStarted = true; } + // TODO(b/149254050): add unit tests? There doesn't seem to be a SystemUiApplicationTest... + @Override + public boolean addDumpable(Dumpable dumpable) { + String name = dumpable.getDumpableName(); + if (mDumpables.containsKey(name)) { + // This is normal because SystemUIApplication is an application context that is shared + // among multiple components + if (DEBUG) { + Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable" + + " with that name (" + name + "): " + mDumpables.get(name)); + } + return false; + } + if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable); + mDumpables.put(name, dumpable); + + // TODO(b/149254050): replace com.android.systemui.dump.Dumpable by + // com.android.util.Dumpable and get rid of the intermediate lambda + mDumpManager.registerDumpable(dumpable.getDumpableName(), + (fd, pw, args) -> dumpable.dump(pw, args)); + return true; + } + @Override public void onConfigurationChanged(Configuration newConfig) { if (mServicesStarted) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 1ac9016a1ee8..2b12f67acd5b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -518,7 +518,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.registerReceiver(mBroadcastReceiver, filter); + context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java index ab8162f9464d..11498dbc0b83 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java @@ -107,6 +107,5 @@ public class AuthCredentialPatternView extends AuthCredentialView { mLockPatternView.setOnPatternListener(new UnlockPatternListener()); mLockPatternView.setInStealthMode( !mLockPatternUtils.isVisiblePatternEnabled(mUserId)); - mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt index 7bb4708443e9..4c00735ab785 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt @@ -26,6 +26,7 @@ import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.SensorLocationInternal import android.hardware.display.DisplayManager import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintSensorPropertiesInternal @@ -113,6 +114,7 @@ class SidefpsController @Inject constructor( orientationListener.enable() } } + private var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT private val overlayViewParams = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, @@ -158,11 +160,19 @@ class SidefpsController @Inject constructor( val view = layoutInflater.inflate(R.layout.sidefps_view, null, false) val display = context.display!! + val offsets = sensorProps.getLocation(display.uniqueId).let { location -> + if (location == null) { + Log.w(TAG, "No location specified for display: ${display.uniqueId}") + } + location ?: sensorProps.location + } + overlayOffsets = offsets + val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView - lottie.setAnimation(display.asSideFpsAnimation()) - view.rotation = display.asSideFpsAnimationRotation() + view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned()) updateOverlayParams(display, lottie.composition?.bounds ?: Rect()) + lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned())) lottie.addLottieOnCompositionLoadedListener { if (overlayView == view) { updateOverlayParams(display, it.bounds) @@ -179,24 +189,37 @@ class SidefpsController @Inject constructor( val size = windowManager.maximumWindowMetrics.bounds val displayWidth = if (isPortrait) size.width() else size.height() val displayHeight = if (isPortrait) size.height() else size.width() - val offsets = sensorProps.getLocation(display.uniqueId).let { location -> - if (location == null) { - Log.w(TAG, "No location specified for display: ${display.uniqueId}") - } - location ?: sensorProps.location - } - // ignore sensorLocationX and sensorRadius since it's assumed to be on the side - // of the device and centered at sensorLocationY - val (x, y) = when (display.rotation) { - Surface.ROTATION_90 -> - Pair(offsets.sensorLocationY, 0) - Surface.ROTATION_270 -> - Pair(displayHeight - offsets.sensorLocationY - bounds.width(), displayWidth) - Surface.ROTATION_180 -> - Pair(0, displayHeight - offsets.sensorLocationY - bounds.height()) - else -> - Pair(displayWidth, offsets.sensorLocationY) + // ignore sensorRadius since it's assumed that the sensor is on the side and centered at + // either sensorLocationX or sensorLocationY (both should not be set) + val (x, y) = if (overlayOffsets.isYAligned()) { + when (display.rotation) { + Surface.ROTATION_90 -> + Pair(overlayOffsets.sensorLocationY, 0) + Surface.ROTATION_270 -> + Pair( + displayHeight - overlayOffsets.sensorLocationY - bounds.width(), + displayWidth + bounds.height() + ) + Surface.ROTATION_180 -> + Pair(0, displayHeight - overlayOffsets.sensorLocationY - bounds.height()) + else -> + Pair(displayWidth, overlayOffsets.sensorLocationY) + } + } else { + when (display.rotation) { + Surface.ROTATION_90 -> + Pair(0, displayWidth - overlayOffsets.sensorLocationX - bounds.height()) + Surface.ROTATION_270 -> + Pair(displayWidth, overlayOffsets.sensorLocationX - bounds.height()) + Surface.ROTATION_180 -> + Pair( + displayWidth - overlayOffsets.sensorLocationX - bounds.width(), + displayHeight + ) + else -> + Pair(overlayOffsets.sensorLocationX, 0) + } } overlayViewParams.x = x overlayViewParams.y = y @@ -209,8 +232,10 @@ class SidefpsController @Inject constructor( // hide after a few seconds if the sensor is oriented down and there are // large overlapping system bars - if ((context.display?.rotation == Surface.ROTATION_270) && - windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar()) { + val rotation = context.display?.rotation + if (windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() && + ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) || + (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))) { overlayHideAnimator = view.animate() .alpha(0f) .setStartDelay(3_000) @@ -245,18 +270,21 @@ private fun ActivityTaskManager.topClass(): String = getTasks(1).firstOrNull()?.topActivity?.className ?: "" @RawRes -private fun Display.asSideFpsAnimation(): Int = when (rotation) { - Surface.ROTATION_0 -> R.raw.sfps_pulse - Surface.ROTATION_180 -> R.raw.sfps_pulse - else -> R.raw.sfps_pulse_landscape +private fun Display.asSideFpsAnimation(yAligned: Boolean): Int = when (rotation) { + Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape + Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape + else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse } -private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) { +private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float = when (rotation) { + Surface.ROTATION_90 -> if (yAligned) 0f else 180f Surface.ROTATION_180 -> 180f - Surface.ROTATION_270 -> 180f + Surface.ROTATION_270 -> if (yAligned) 180f else 0f else -> 0f } +private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0 + private fun Display.isPortrait(): Boolean = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index d20844143ad6..6581490030dc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -539,7 +539,8 @@ public class UdfpsController implements DozeReceiver { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.registerReceiver(mBroadcastReceiver, filter); + context.registerReceiver(mBroadcastReceiver, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); udfpsHapticsSimulator.setUdfpsController(this); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index e7015115d84c..1317492aefac 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -29,9 +29,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.LinearInterpolator; -import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,18 +41,11 @@ import com.android.systemui.R; public class UdfpsEnrollDrawable extends UdfpsDrawable { private static final String TAG = "UdfpsAnimationEnroll"; - private static final long HINT_COLOR_ANIM_DELAY_MS = 233L; - private static final long HINT_COLOR_ANIM_DURATION_MS = 517L; - private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L; private static final long TARGET_ANIM_DURATION_LONG = 800L; private static final long TARGET_ANIM_DURATION_SHORT = 600L; // 1 + SCALE_MAX is the maximum that the moving target will animate to private static final float SCALE_MAX = 0.25f; - private static final float HINT_PADDING_DP = 10f; - private static final float HINT_MAX_WIDTH_DP = 6f; - private static final float HINT_ANGLE = 40f; - private final Handler mHandler = new Handler(Looper.getMainLooper()); @NonNull private final Drawable mMovingTargetFpIcon; @@ -72,30 +63,10 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { // Moving target size float mCurrentScale = 1.f; - @ColorInt private final int mHintColorFaded; - @ColorInt private final int mHintColorHighlight; - private final float mHintMaxWidthPx; - private final float mHintPaddingPx; - @NonNull private final Animator.AnimatorListener mTargetAnimListener; private boolean mShouldShowTipHint = false; - @NonNull private final Paint mTipHintPaint; - @Nullable private AnimatorSet mTipHintAnimatorSet; - @Nullable private ValueAnimator mTipHintColorAnimator; - @Nullable private ValueAnimator mTipHintWidthAnimator; - @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener; - @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener; - @NonNull private final Animator.AnimatorListener mTipHintPulseListener; - private boolean mShouldShowEdgeHint = false; - @NonNull private final Paint mEdgeHintPaint; - @Nullable private AnimatorSet mEdgeHintAnimatorSet; - @Nullable private ValueAnimator mEdgeHintColorAnimator; - @Nullable private ValueAnimator mEdgeHintWidthAnimator; - @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener; - @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener; - @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener; UdfpsEnrollDrawable(@NonNull Context context) { super(context); @@ -117,11 +88,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { getFingerprintDrawable().setTint(context.getColor(R.color.udfps_enroll_icon)); - mHintColorFaded = context.getColor(R.color.udfps_moving_target_fill); - mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress); - mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP); - mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP); - mTargetAnimListener = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) {} @@ -137,80 +103,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { @Override public void onAnimationRepeat(Animator animation) {} }; - - mTipHintPaint = new Paint(0 /* flags */); - mTipHintPaint.setAntiAlias(true); - mTipHintPaint.setColor(mHintColorFaded); - mTipHintPaint.setStyle(Paint.Style.STROKE); - mTipHintPaint.setStrokeCap(Paint.Cap.ROUND); - mTipHintPaint.setStrokeWidth(0f); - mTipHintColorUpdateListener = animation -> { - mTipHintPaint.setColor((int) animation.getAnimatedValue()); - invalidateSelf(); - }; - mTipHintWidthUpdateListener = animation -> { - mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); - invalidateSelf(); - }; - mTipHintPulseListener = new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - mHandler.postDelayed(() -> { - mTipHintColorAnimator = - ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded); - mTipHintColorAnimator.setInterpolator(new LinearInterpolator()); - mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); - mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); - mTipHintColorAnimator.start(); - }, HINT_COLOR_ANIM_DELAY_MS); - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }; - - mEdgeHintPaint = new Paint(0 /* flags */); - mEdgeHintPaint.setAntiAlias(true); - mEdgeHintPaint.setColor(mHintColorFaded); - mEdgeHintPaint.setStyle(Paint.Style.STROKE); - mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND); - mEdgeHintPaint.setStrokeWidth(0f); - mEdgeHintColorUpdateListener = animation -> { - mEdgeHintPaint.setColor((int) animation.getAnimatedValue()); - invalidateSelf(); - }; - mEdgeHintWidthUpdateListener = animation -> { - mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); - invalidateSelf(); - }; - mEdgeHintPulseListener = new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - mHandler.postDelayed(() -> { - mEdgeHintColorAnimator = - ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded); - mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator()); - mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); - mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); - mEdgeHintColorAnimator.start(); - }, HINT_COLOR_ANIM_DELAY_MS); - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }; } void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { @@ -287,25 +179,12 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { private void updateTipHintVisibility() { final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage(); + // With the new update, we will git rid of most of this code, and instead + // we will change the fingerprint icon. if (mShouldShowTipHint == shouldShow) { return; } mShouldShowTipHint = shouldShow; - - if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) { - mTipHintWidthAnimator.cancel(); - } - - final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; - mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth); - mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); - mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener); - - if (shouldShow) { - startTipHintPulseAnimation(); - } else { - mTipHintWidthAnimator.start(); - } } private void updateEdgeHintVisibility() { @@ -314,71 +193,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { return; } mShouldShowEdgeHint = shouldShow; - - if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) { - mEdgeHintWidthAnimator.cancel(); - } - - final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; - mEdgeHintWidthAnimator = - ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth); - mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); - mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener); - - if (shouldShow) { - startEdgeHintPulseAnimation(); - } else { - mEdgeHintWidthAnimator.start(); - } - } - - private void startTipHintPulseAnimation() { - mHandler.removeCallbacksAndMessages(null); - if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) { - mTipHintAnimatorSet.cancel(); - } - if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) { - mTipHintColorAnimator.cancel(); - } - - mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight); - mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); - mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); - mTipHintColorAnimator.addListener(mTipHintPulseListener); - - mTipHintAnimatorSet = new AnimatorSet(); - mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator); - mTipHintAnimatorSet.start(); - } - - private void startEdgeHintPulseAnimation() { - mHandler.removeCallbacksAndMessages(null); - if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) { - mEdgeHintAnimatorSet.cancel(); - } - if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) { - mEdgeHintColorAnimator.cancel(); - } - - mEdgeHintColorAnimator = - ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight); - mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); - mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); - mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener); - - mEdgeHintAnimatorSet = new AnimatorSet(); - mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator); - mEdgeHintAnimatorSet.start(); - } - - private boolean isTipHintVisible() { - return mTipHintPaint.getStrokeWidth() > 0f; - } - - private boolean isEdgeHintVisible() { - return mEdgeHintPaint.getStrokeWidth() > 0f; } @Override @@ -409,58 +223,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mSensorOutlinePaint.setAlpha(getAlpha()); } - // Draw the finger tip or edges hint. - if (isTipHintVisible() || isEdgeHintVisible()) { - canvas.save(); - - // Make arcs start from the top, rather than the right. - canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); - - final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f; - final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f; - final float hintXOffset = halfSensorWidth + mHintPaddingPx; - final float hintYOffset = halfSensorHeight + mHintPaddingPx; - - if (isTipHintVisible()) { - canvas.drawArc( - mSensorRect.centerX() - hintXOffset, - mSensorRect.centerY() - hintYOffset, - mSensorRect.centerX() + hintXOffset, - mSensorRect.centerY() + hintYOffset, - -HINT_ANGLE / 2f, - HINT_ANGLE, - false /* useCenter */, - mTipHintPaint); - } - - if (isEdgeHintVisible()) { - // Draw right edge hint. - canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); - canvas.drawArc( - mSensorRect.centerX() - hintXOffset, - mSensorRect.centerY() - hintYOffset, - mSensorRect.centerX() + hintXOffset, - mSensorRect.centerY() + hintYOffset, - -HINT_ANGLE / 2f, - HINT_ANGLE, - false /* useCenter */, - mEdgeHintPaint); - - // Draw left edge hint. - canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY()); - canvas.drawArc( - mSensorRect.centerX() - hintXOffset, - mSensorRect.centerY() - hintYOffset, - mSensorRect.centerX() + hintXOffset, - mSensorRect.centerY() + hintYOffset, - -HINT_ANGLE / 2f, - HINT_ANGLE, - false /* useCenter */, - mEdgeHintPaint); - } - - canvas.restore(); - } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java new file mode 100644 index 000000000000..41a496332768 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -0,0 +1,72 @@ +/* + * 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.systemui.clipboardoverlay; + +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED; + +import static java.util.Objects.requireNonNull; + +import android.content.ClipboardManager; +import android.content.Context; +import android.provider.DeviceConfig; + +import com.android.systemui.CoreStartable; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.screenshot.TimeoutHandler; + +import javax.inject.Inject; + +/** + * ClipboardListener brings up a clipboard overlay when something is copied to the clipboard. + */ +@SysUISingleton +public class ClipboardListener extends CoreStartable + implements ClipboardManager.OnPrimaryClipChangedListener { + + private ClipboardOverlayController mClipboardOverlayController; + private ClipboardManager mClipboardManager; + + @Inject + public ClipboardListener(Context context) { + super(context); + } + + @Override + public void start() { + if (DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) { + mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class)); + mClipboardManager.addPrimaryClipChangedListener(this); + } + } + + @Override + public void onPrimaryClipChanged() { + if (!mClipboardManager.hasPrimaryClip()) { + return; + } + if (mClipboardOverlayController == null) { + mClipboardOverlayController = new ClipboardOverlayController(mContext, + new TimeoutHandler(mContext)); + } + mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip()); + mClipboardOverlayController.setOnSessionCompleteListener(() -> { + // Session is complete, free memory until it's needed again. + mClipboardOverlayController = null; + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java new file mode 100644 index 000000000000..ae0702c68201 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -0,0 +1,471 @@ +/* + * 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.systemui.clipboardoverlay; + +import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; + +import static java.util.Objects.requireNonNull; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.MainThread; +import android.content.BroadcastReceiver; +import android.content.ClipData; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Insets; +import android.graphics.Rect; +import android.graphics.drawable.Icon; +import android.hardware.display.DisplayManager; +import android.hardware.input.InputManager; +import android.net.Uri; +import android.os.Looper; +import android.text.TextUtils; +import android.util.Log; +import android.util.Size; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.Gravity; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.InputMonitor; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.internal.policy.PhoneWindow; +import com.android.systemui.R; +import com.android.systemui.screenshot.FloatingWindowUtil; +import com.android.systemui.screenshot.ScreenshotActionChip; +import com.android.systemui.screenshot.TimeoutHandler; + +import java.io.IOException; + +/** + * Controls state and UI for the overlay that appears when something is added to the clipboard + */ +public class ClipboardOverlayController { + private static final String TAG = "ClipboardOverlayCtrlr"; + private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; + + /** Constants for screenshot/copy deconflicting */ + public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT"; + public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF"; + public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY"; + + private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000; + + private final Context mContext; + private final DisplayManager mDisplayManager; + private final WindowManager mWindowManager; + private final WindowManager.LayoutParams mWindowLayoutParams; + private final PhoneWindow mWindow; + private final TimeoutHandler mTimeoutHandler; + + private final DraggableConstraintLayout mView; + private final ImageView mImagePreview; + private final TextView mTextPreview; + private final ScreenshotActionChip mEditChip; + private final ScreenshotActionChip mRemoteCopyChip; + private final View mActionContainerBackground; + + private Runnable mOnSessionCompleteListener; + + private InputEventReceiver mInputEventReceiver; + + private BroadcastReceiver mCloseDialogsReceiver; + private BroadcastReceiver mScreenshotReceiver; + + private boolean mBlockAttach = false; + + public ClipboardOverlayController(Context context, TimeoutHandler timeoutHandler) { + mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); + final Context displayContext = context.createDisplayContext(getDefaultDisplay()); + mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null); + + mWindowManager = mContext.getSystemService(WindowManager.class); + + mTimeoutHandler = timeoutHandler; + mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS); + + // Setup the window that we are going to use + mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams(); + mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mWindowLayoutParams.height = WRAP_CONTENT; + mWindowLayoutParams.gravity = Gravity.BOTTOM; + mWindowLayoutParams.setTitle("ClipboardOverlay"); + mWindow = FloatingWindowUtil.getFloatingWindow(mContext); + mWindow.setWindowManager(mWindowManager, null, null); + + mView = (DraggableConstraintLayout) + LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null); + mActionContainerBackground = requireNonNull( + mView.findViewById(R.id.actions_container_background)); + mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview)); + mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview)); + mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip)); + mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip)); + + mView.setOnDismissCallback(this::hideImmediate); + mView.setOnInteractionCallback(() -> mTimeoutHandler.resetTimeout()); + + mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true); + mRemoteCopyChip.setIcon( + Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true); + + // Only show remote copy if it's available. + PackageManager packageManager = mContext.getPackageManager(); + if (packageManager.resolveActivity(getRemoteCopyIntent(), 0) != null) { + mRemoteCopyChip.setOnClickListener((v) -> { + showNearby(); + }); + mRemoteCopyChip.setAlpha(1f); + } else { + mRemoteCopyChip.setVisibility(View.GONE); + } + + attachWindow(); + withWindowAttached(() -> { + mWindow.setContentView(mView); + updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets()); + getEnterAnimation().start(); + }); + + mTimeoutHandler.setOnTimeoutRunnable(() -> animateOut()); + + mCloseDialogsReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { + animateOut(); + } + } + }; + mContext.registerReceiver(mCloseDialogsReceiver, + new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS)); + + mScreenshotReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (SCREENSHOT_ACTION.equals(intent.getAction())) { + animateOut(); + } + } + }; + mContext.registerReceiver(mScreenshotReceiver, new IntentFilter(SCREENSHOT_ACTION), + SELF_PERMISSION, null); + monitorOutsideTouches(); + + mContext.sendBroadcast(new Intent(COPY_OVERLAY_ACTION), SELF_PERMISSION); + } + + void setClipData(ClipData clipData) { + reset(); + + if (clipData == null || clipData.getItemCount() == 0) { + showTextPreview( + mContext.getResources().getString(R.string.clipboard_overlay_text_copied)); + } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { + showEditableText(clipData.getItemAt(0).getText()); + } else if (clipData.getItemAt(0).getUri() != null) { + // How to handle non-image URIs? + showEditableImage(clipData.getItemAt(0).getUri()); + } else { + showTextPreview( + mContext.getResources().getString(R.string.clipboard_overlay_text_copied)); + } + + mTimeoutHandler.resetTimeout(); + } + + void setOnSessionCompleteListener(Runnable runnable) { + mOnSessionCompleteListener = runnable; + } + + private void monitorOutsideTouches() { + InputManager inputManager = mContext.getSystemService(InputManager.class); + InputMonitor monitor = inputManager.monitorGestureInput("clipboard overlay", 0); + mInputEventReceiver = new InputEventReceiver(monitor.getInputChannel(), + Looper.getMainLooper()) { + @Override + public void onInputEvent(InputEvent event) { + if (event instanceof MotionEvent) { + MotionEvent motionEvent = (MotionEvent) event; + if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { + int[] pt = new int[2]; + mView.getLocationOnScreen(pt); + Rect rect = new Rect(pt[0], pt[1], pt[0] + mView.getWidth(), + pt[1] + mView.getHeight()); + if (!rect.contains((int) motionEvent.getRawX(), + (int) motionEvent.getRawY())) { + animateOut(); + } + } + } + finishInputEvent(event, true /* handled */); + } + }; + } + + private void editImage(Uri uri) { + String editorPackage = mContext.getString(R.string.config_screenshotEditor); + Intent editIntent = new Intent(Intent.ACTION_EDIT); + if (!TextUtils.isEmpty(editorPackage)) { + editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); + } + editIntent.setDataAndType(uri, "image/*"); + editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivity(editIntent); + animateOut(); + } + + private void editText() { + Intent editIntent = new Intent(mContext, EditTextActivity.class); + editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivity(editIntent); + animateOut(); + } + + private void showNearby() { + mContext.startActivity(getRemoteCopyIntent()); + animateOut(); + } + + private void showTextPreview(CharSequence text) { + mTextPreview.setVisibility(View.VISIBLE); + mImagePreview.setVisibility(View.GONE); + mTextPreview.setText(text); + mEditChip.setVisibility(View.GONE); + } + + private void showEditableText(CharSequence text) { + showTextPreview(text); + mEditChip.setVisibility(View.VISIBLE); + mEditChip.setAlpha(1f); + View.OnClickListener listener = v -> editText(); + mEditChip.setOnClickListener(listener); + mTextPreview.setOnClickListener(listener); + } + + private void showEditableImage(Uri uri) { + mTextPreview.setVisibility(View.GONE); + mImagePreview.setVisibility(View.VISIBLE); + mEditChip.setAlpha(1f); + ContentResolver resolver = mContext.getContentResolver(); + try { + int size = mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale); + // The width of the view is capped, height maintains aspect ratio, so allow it to be + // taller if needed. + Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null); + mImagePreview.setImageBitmap(thumbnail); + } catch (IOException e) { + Log.e(TAG, "Thumbnail loading failed", e); + } + View.OnClickListener listener = v -> editImage(uri); + mEditChip.setOnClickListener(listener); + mImagePreview.setOnClickListener(listener); + } + + private Intent getRemoteCopyIntent() { + Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION); + nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return nearbyIntent; + } + + private void animateOut() { + getExitAnimation().start(); + } + + private ValueAnimator getEnterAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + + mView.setAlpha(0); + final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border)); + final View actionBackground = requireNonNull( + mView.findViewById(R.id.actions_container_background)); + mImagePreview.setVisibility(View.VISIBLE); + mActionContainerBackground.setVisibility(View.VISIBLE); + + anim.addUpdateListener(animation -> { + mView.setAlpha(animation.getAnimatedFraction()); + float scale = 0.6f + 0.4f * animation.getAnimatedFraction(); + mView.setPivotY(mView.getHeight() - previewBorder.getHeight() / 2f); + mView.setPivotX(actionBackground.getWidth() / 2f); + mView.setScaleX(scale); + mView.setScaleY(scale); + }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mView.setAlpha(1); + mTimeoutHandler.resetTimeout(); + } + }); + return anim; + } + + private ValueAnimator getExitAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + + anim.addUpdateListener(animation -> { + mView.setAlpha(1 - animation.getAnimatedFraction()); + final View actionBackground = requireNonNull( + mView.findViewById(R.id.actions_container_background)); + mView.setTranslationX( + -animation.getAnimatedFraction() * actionBackground.getWidth() / 2); + }); + + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + hideImmediate(); + } + }); + + return anim; + } + + private void hideImmediate() { + // Note this may be called multiple times if multiple dismissal events happen at the same + // time. + mTimeoutHandler.cancelTimeout(); + final View decorView = mWindow.peekDecorView(); + if (decorView != null && decorView.isAttachedToWindow()) { + mWindowManager.removeViewImmediate(decorView); + } + if (mCloseDialogsReceiver != null) { + mContext.unregisterReceiver(mCloseDialogsReceiver); + mCloseDialogsReceiver = null; + } + if (mScreenshotReceiver != null) { + mContext.unregisterReceiver(mScreenshotReceiver); + mScreenshotReceiver = null; + } + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } + if (mOnSessionCompleteListener != null) { + mOnSessionCompleteListener.run(); + } + } + + private void reset() { + mView.setTranslationX(0); + mView.setAlpha(1); + mTimeoutHandler.cancelTimeout(); + } + + @MainThread + private void attachWindow() { + View decorView = mWindow.getDecorView(); + if (decorView.isAttachedToWindow() || mBlockAttach) { + return; + } + mBlockAttach = true; + mWindowManager.addView(decorView, mWindowLayoutParams); + decorView.requestApplyInsets(); + mView.requestApplyInsets(); + decorView.getViewTreeObserver().addOnWindowAttachListener( + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + mBlockAttach = false; + } + + @Override + public void onWindowDetached() { + } + } + ); + } + + private void withWindowAttached(Runnable action) { + View decorView = mWindow.getDecorView(); + if (decorView.isAttachedToWindow()) { + action.run(); + } else { + decorView.getViewTreeObserver().addOnWindowAttachListener( + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + mBlockAttach = false; + decorView.getViewTreeObserver().removeOnWindowAttachListener(this); + action.run(); + } + + @Override + public void onWindowDetached() { + } + }); + } + } + + private void updateInsets(WindowInsets insets) { + int orientation = mContext.getResources().getConfiguration().orientation; + FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams(); + if (p == null) { + return; + } + DisplayCutout cutout = insets.getDisplayCutout(); + Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()); + if (cutout == null) { + p.setMargins(0, 0, 0, navBarInsets.bottom); + } else { + Insets waterfall = cutout.getWaterfallInsets(); + if (orientation == ORIENTATION_PORTRAIT) { + p.setMargins( + waterfall.left, + Math.max(cutout.getSafeInsetTop(), waterfall.top), + waterfall.right, + Math.max(cutout.getSafeInsetBottom(), + Math.max(navBarInsets.bottom, waterfall.bottom))); + } else { + p.setMargins( + Math.max(cutout.getSafeInsetLeft(), waterfall.left), + waterfall.top, + Math.max(cutout.getSafeInsetRight(), waterfall.right), + Math.max(navBarInsets.bottom, waterfall.bottom)); + } + } + mView.setLayoutParams(p); + mView.requestLayout(); + } + + private Display getDefaultDisplay() { + return mDisplayManager.getDisplay(DEFAULT_DISPLAY); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java new file mode 100644 index 000000000000..6a4be6ee5eae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java @@ -0,0 +1,108 @@ +/* + * 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.systemui.clipboardoverlay; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.android.systemui.R; +import com.android.systemui.screenshot.SwipeDismissHandler; + +/** + * ConstraintLayout that is draggable when touched in a specific region + */ +public class DraggableConstraintLayout extends ConstraintLayout { + private final SwipeDismissHandler mSwipeDismissHandler; + private final GestureDetector mSwipeDetector; + private Runnable mOnDismiss; + private Runnable mOnInteraction; + + public DraggableConstraintLayout(Context context) { + this(context, null); + } + + public DraggableConstraintLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + mSwipeDismissHandler = new SwipeDismissHandler(mContext, this, + new SwipeDismissHandler.SwipeDismissCallbacks() { + @Override + public void onInteraction() { + if (mOnInteraction != null) { + mOnInteraction.run(); + } + } + + @Override + public void onDismiss() { + if (mOnDismiss != null) { + mOnDismiss.run(); + } + } + }); + setOnTouchListener(mSwipeDismissHandler); + + mSwipeDetector = new GestureDetector(mContext, + new GestureDetector.SimpleOnGestureListener() { + final Rect mActionsRect = new Rect(); + + @Override + public boolean onScroll( + MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { + View actionsContainer = findViewById(R.id.actions_container); + actionsContainer.getBoundsOnScreen(mActionsRect); + // return true if we aren't in the actions bar, or if we are but it isn't + // scrollable in the direction of movement + return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY()) + || !actionsContainer.canScrollHorizontally((int) distanceX); + } + }); + mSwipeDetector.setIsLongpressEnabled(false); + } + + @Override // View + protected void onFinishInflate() { + + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + mSwipeDismissHandler.onTouch(this, ev); + } + + return mSwipeDetector.onTouchEvent(ev); + } + + public void setOnDismissCallback(Runnable callback) { + mOnDismiss = callback; + } + + public void setOnInteractionCallback(Runnable callback) { + mOnInteraction = callback; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java new file mode 100644 index 000000000000..be10c359f3c1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java @@ -0,0 +1,82 @@ +/* + * 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.systemui.clipboardoverlay; + +import static java.util.Objects.requireNonNull; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.os.Bundle; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import com.android.systemui.R; + +/** + * Lightweight activity for editing text clipboard contents + */ +public class EditTextActivity extends Activity { + private EditText mEditText; + private ClipboardManager mClipboardManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.clipboard_edit_text_activity); + findViewById(R.id.copy_button).setOnClickListener((v) -> saveToClipboard()); + findViewById(R.id.share).setOnClickListener((v) -> share()); + mEditText = findViewById(R.id.edit_text); + mClipboardManager = requireNonNull(getSystemService(ClipboardManager.class)); + } + + @Override + protected void onStart() { + super.onStart(); + ClipData clip = mClipboardManager.getPrimaryClip(); + if (clip == null) { + finish(); + return; + } + // TODO: put clip attribution in R.id.attribution TextView + mEditText.setText(clip.getItemAt(0).getText()); + mEditText.requestFocus(); + } + + private void saveToClipboard() { + ClipData clip = ClipData.newPlainText("text", mEditText.getText()); + mClipboardManager.setPrimaryClip(clip); + hideImeAndFinish(); + } + + private void share() { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, mEditText.getText()); + sendIntent.setType("text/plain"); + + Intent shareIntent = Intent.createChooser(sendIntent, null); + startActivity(shareIntent); + } + + private void hideImeAndFinish() { + InputMethodManager imm = getSystemService(InputMethodManager.class); + imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + finish(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalManagerUpdater.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalManagerUpdater.java deleted file mode 100644 index ebe804af15e8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalManagerUpdater.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.systemui.communal; - -import android.app.communal.CommunalManager; -import android.content.Context; -import android.util.Log; - -import com.android.systemui.CoreStartable; -import com.android.systemui.dagger.SysUISingleton; - -import java.lang.ref.WeakReference; - -import javax.inject.Inject; - -/** - * The {@link CommunalManagerUpdater} is responsible for forwarding state from SystemUI to - * the {@link CommunalManager} system service. - */ -@SysUISingleton -public class CommunalManagerUpdater extends CoreStartable { - private static final String TAG = "CommunalManagerUpdater"; - - private final CommunalManager mCommunalManager; - private final CommunalSourceMonitor mMonitor; - - private final CommunalSourceMonitor.Callback mSourceCallback = - new CommunalSourceMonitor.Callback() { - @Override - public void onSourceAvailable(WeakReference<CommunalSource> source) { - try { - mCommunalManager.setCommunalViewShowing( - source != null && source.get() != null); - } catch (RuntimeException e) { - Log.e(TAG, "Error updating communal manager service state", e); - } - } - }; - - @Inject - public CommunalManagerUpdater(Context context, CommunalSourceMonitor monitor) { - super(context); - mCommunalManager = context.getSystemService(CommunalManager.class); - mMonitor = monitor; - } - - @Override - public void start() { - if (mCommunalManager != null) { - mMonitor.addCallback(mSourceCallback); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 25985181364d..8367e1128033 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -244,7 +244,8 @@ class ControlsControllerImpl @Inject constructor ( restoreFinishedReceiver, IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), PERMISSION_SELF, - null + null, + Context.RECEIVER_NOT_EXPORTED ) listingController.addCallback(listingCallback) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 977e46ac3b44..d2ded71487dc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -131,6 +131,12 @@ class ControlsProviderLifecycleManager( wrapper = null bindService(false) } + + override fun onNullBinding(name: ComponentName?) { + if (DEBUG) Log.d(TAG, "onNullBinding $name") + wrapper = null + context.unbindService(this) + } } private fun handlePendingServiceMethods() { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index cffb2f79ebfb..b23569241f59 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -28,6 +28,8 @@ import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerR import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; import com.android.systemui.people.PeopleProvider; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.unfold.FoldStateLogger; +import com.android.systemui.unfold.FoldStateLoggingProvider; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.unfold.UnfoldLatencyTracker; import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; @@ -139,6 +141,8 @@ public interface SysUIComponent { getMediaTttChipControllerReceiver(); getMediaTttCommandLineHelper(); getUnfoldLatencyTracker().init(); + getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init); + getFoldStateLogger().ifPresent(FoldStateLogger::init); } /** @@ -166,6 +170,18 @@ public interface SysUIComponent { UnfoldLatencyTracker getUnfoldLatencyTracker(); /** + * Creates a FoldStateLoggingProvider. + */ + @SysUISingleton + Optional<FoldStateLoggingProvider> getFoldStateLoggingProvider(); + + /** + * Creates a FoldStateLogger. + */ + @SysUISingleton + Optional<FoldStateLogger> getFoldStateLogger(); + + /** * Main dependency providing module. */ @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 9dddbb1d67c6..154f6fad5998 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -16,6 +16,7 @@ package com.android.systemui.dagger; +import com.android.keyguard.KeyguardBiometricLockoutLogger; import com.android.systemui.CoreStartable; import com.android.systemui.LatencyTester; import com.android.systemui.ScreenDecorations; @@ -23,7 +24,7 @@ import com.android.systemui.SliceBroadcastRelayHandler; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.accessibility.WindowMagnification; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.communal.CommunalManagerUpdater; +import com.android.systemui.clipboardoverlay.ClipboardListener; import com.android.systemui.dreams.DreamOverlayRegistrant; import com.android.systemui.dreams.appwidgets.ComplicationPrimer; import com.android.systemui.globalactions.GlobalActionsComponent; @@ -72,6 +73,12 @@ public abstract class SystemUIBinder { @ClassKey(GarbageMonitor.Service.class) public abstract CoreStartable bindGarbageMonitorService(GarbageMonitor.Service sysui); + /** Inject into ClipboardListener. */ + @Binds + @IntoMap + @ClassKey(ClipboardListener.class) + public abstract CoreStartable bindClipboardListener(ClipboardListener sysui); + /** Inject into GlobalActionsComponent. */ @Binds @IntoMap @@ -90,6 +97,13 @@ public abstract class SystemUIBinder { @ClassKey(KeyguardViewMediator.class) public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui); + /** Inject into KeyguardBiometricLockoutLogger. */ + @Binds + @IntoMap + @ClassKey(KeyguardBiometricLockoutLogger.class) + public abstract CoreStartable bindKeyguardBiometricLockoutLogger( + KeyguardBiometricLockoutLogger sysui); + /** Inject into LatencyTests. */ @Binds @IntoMap @@ -205,11 +219,4 @@ public abstract class SystemUIBinder { @ClassKey(ComplicationPrimer.class) public abstract CoreStartable bindAppWidgetOverlayPrimer( ComplicationPrimer complicationPrimer); - - /** Inject into CommunalManagerUpdater. */ - @Binds - @IntoMap - @ClassKey(CommunalManagerUpdater.class) - public abstract CoreStartable bindCommunalManagerUpdater( - CommunalManagerUpdater communalManagerUpdater); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 572bb4467c97..5b46079c87cc 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -16,8 +16,11 @@ package com.android.systemui.dreams; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; + import android.graphics.Rect; import android.graphics.Region; +import android.os.Handler; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -26,6 +29,7 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.dagger.DreamOverlayModule; import com.android.systemui.util.ViewController; @@ -47,6 +51,15 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve // the space into which widgets are placed. private final ViewGroup mDreamOverlayContentView; + // The maximum translation offset to apply to the overlay container to avoid screen burn-in. + private final int mMaxBurnInOffset; + + // The interval in milliseconds between burn-in protection updates. + private final long mBurnInProtectionUpdateInterval; + + // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates). + private final Handler mHandler; + // A hook into the internal inset calculation where we declare the overlays as the only // touchable regions. private final ViewTreeObserver.OnComputeInternalInsetsListener @@ -81,13 +94,21 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve public DreamOverlayContainerViewController( DreamOverlayContainerView containerView, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, - DreamOverlayStatusBarViewController statusBarViewController) { + DreamOverlayStatusBarViewController statusBarViewController, + @Main Handler handler, + @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset, + @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long + burnInProtectionUpdateInterval) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; mDreamOverlayNotificationsDragAreaHeight = mView.getResources().getDimensionPixelSize( R.dimen.dream_overlay_notifications_drag_area_height); + + mHandler = handler; + mMaxBurnInOffset = maxBurnInOffset; + mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval; } @Override @@ -99,10 +120,12 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve protected void onViewAttached() { mView.getViewTreeObserver() .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); + mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval); } @Override protected void onViewDetached() { + mHandler.removeCallbacks(this::updateBurnInOffsets); mView.getViewTreeObserver() .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); } @@ -123,4 +146,15 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve int getDreamOverlayNotificationsDragAreaHeight() { return mDreamOverlayNotificationsDragAreaHeight; } + + private void updateBurnInOffsets() { + // These translation values change slowly, and the set translation methods are idempotent, + // so no translation occurs when the values don't change. + mView.setTranslationX(getBurnInOffset(mMaxBurnInOffset * 2, true) + - mMaxBurnInOffset); + mView.setTranslationY(getBurnInOffset(mMaxBurnInOffset * 2, false) + - mMaxBurnInOffset); + + mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 5b588a9d9023..d2912032ed39 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -17,6 +17,7 @@ package com.android.systemui.dreams.dagger; import android.content.ContentResolver; +import android.content.res.Resources; import android.os.Handler; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -45,6 +46,9 @@ public abstract class DreamOverlayModule { public static final String DREAM_OVERLAY_BATTERY_CONTROLLER = "dream_overlay_battery_controller"; public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view"; + public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset"; + public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL = + "burn_in_protection_update_interval"; /** */ @Provides @@ -104,4 +108,20 @@ public abstract class DreamOverlayModule { contentResolver, batteryController); } + + /** */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + @Named(MAX_BURN_IN_OFFSET) + static int providesMaxBurnInOffset(@Main Resources resources) { + return resources.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset); + } + + /** */ + @Provides + @Named(BURN_IN_PROTECTION_UPDATE_INTERVAL) + static long providesBurnInProtectionUpdateInterval(@Main Resources resources) { + return resources.getInteger( + R.integer.config_dreamOverlayBurnInProtectionUpdateIntervalMillis); + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index 89623f4566bd..f3b721c02635 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -88,7 +88,8 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { filter.addAction(ACTION_GET_FLAGS); flagManager.setRestartAction(this::restartSystemUI); flagManager.setClearCacheAction(this::removeFromCache); - context.registerReceiver(mReceiver, filter, null, null); + context.registerReceiver(mReceiver, filter, null, null, + Context.RECEIVER_EXPORTED_UNAUDITED); dumpManager.registerDumpable(TAG, this); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 4be819a49772..5d6c2a247df3 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -74,6 +74,9 @@ public class Flags { public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER = new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher); + public static final ResourceBooleanFlag ACTIVE_UNLOCK = + new ResourceBooleanFlag(205, R.bool.flag_active_unlock); + /***************************************/ // 300 - power menu public static final BooleanFlag POWER_MENU_LITE = diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index c46ffa077746..b24d08d3f8bb 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcelable; +import android.os.Trace; import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; @@ -224,10 +225,12 @@ public class FragmentHostManager { } public void reloadFragments() { + Trace.beginSection("FrargmentHostManager#reloadFragments"); // Save the old state. Parcelable p = destroyFragmentHost(); // Generate a new fragment host and restore its state. createFragmentHost(p); + Trace.endSection(); } class HostCallbacks extends FragmentHostCallback<FragmentHostManager> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index d73d9cdb7d40..0ad2807bed55 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -119,14 +119,13 @@ public class KeyguardIndicationRotateTextViewController extends return; } long minShowDuration = getMinVisibilityMillis(mIndicationMessages.get(mCurrIndicationType)); - final boolean hasPreviousIndication = mIndicationMessages.get(type) != null - && !TextUtils.isEmpty(mIndicationMessages.get(type).getMessage()); - final boolean hasNewIndication = newIndication != null; + final boolean hasNewIndication = newIndication != null + && !TextUtils.isEmpty(newIndication.getMessage()); if (!hasNewIndication) { mIndicationMessages.remove(type); mIndicationQueue.removeIf(x -> x == type); } else { - if (!hasPreviousIndication) { + if (!mIndicationQueue.contains(type)) { mIndicationQueue.add(type); } @@ -230,6 +229,7 @@ public class KeyguardIndicationRotateTextViewController extends public void clearMessages() { mCurrIndicationType = INDICATION_TYPE_NONE; mIndicationQueue.clear(); + mIndicationMessages.clear(); mView.clearMessages(); } @@ -310,7 +310,7 @@ public class KeyguardIndicationRotateTextViewController extends if (mIsDozing) { showIndication(INDICATION_TYPE_NONE); } else if (mIndicationQueue.size() > 0) { - showIndication(mIndicationQueue.remove(0)); + showIndication(mIndicationQueue.get(0)); } } }; @@ -327,7 +327,7 @@ public class KeyguardIndicationRotateTextViewController extends ShowNextIndication(long delay) { mShowIndicationRunnable = () -> { int type = mIndicationQueue.size() == 0 - ? INDICATION_TYPE_NONE : mIndicationQueue.remove(0); + ? INDICATION_TYPE_NONE : mIndicationQueue.get(0); showIndication(type); }; mCancelDelayedRunnable = mExecutor.executeDelayed(mShowIndicationRunnable, delay); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 094b1927480d..8376681e7bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -894,7 +894,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, delayedActionFilter.addAction(DELAYED_KEYGUARD_ACTION); delayedActionFilter.addAction(DELAYED_LOCK_PROFILE_ACTION); mContext.registerReceiver(mDelayedLockBroadcastReceiver, delayedActionFilter, - SYSTEMUI_PERMISSION, null /* scheduler */); + SYSTEMUI_PERMISSION, null /* scheduler */, + Context.RECEIVER_EXPORTED_UNAUDITED); mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); @@ -1688,6 +1689,21 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, || mUpdateMonitor.isSimPinSecure(); } + /** + * Whether any of the SIMs on the device are secured with a PIN. If so, the keyguard should not + * be dismissable until the PIN is entered, even if the device itself has no lock set. + */ + public boolean isAnySimPinSecure() { + for (int i = 0; i < mLastSimStates.size(); i++) { + final int key = mLastSimStates.keyAt(i); + if (KeyguardUpdateMonitor.isSimPinSecure(mLastSimStates.get(key))) { + return true; + } + } + + return false; + } + public void setSwitchingUser(boolean switching) { mUpdateMonitor.setSwitchingUser(switching); } @@ -1745,7 +1761,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } }; - public void keyguardDone() { + private void keyguardDone() { Trace.beginSection("KeyguardViewMediator#keyguardDone"); if (DEBUG) Log.d(TAG, "keyguardDone()"); userActivity(); @@ -1879,9 +1895,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, resetKeyguardDonePendingLocked(); } - if (mGoingToSleep) { - mUpdateMonitor.clearBiometricRecognized(); + mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser); Log.i(TAG, "Device is going to sleep, aborting keyguardDone"); return; } @@ -1902,7 +1917,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } handleHide(); - mUpdateMonitor.clearBiometricRecognized(); + mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java index 2e1c9faf8848..474a81b9ce44 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java @@ -138,6 +138,7 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe } setWakefulness(WAKEFULNESS_AWAKE); dispatch(Observer::onFinishedWakingUp); + dispatch(Observer::onPostFinishedWakingUp); } public void dispatchStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) { @@ -236,6 +237,12 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe public interface Observer { default void onStartedWakingUp() {} default void onFinishedWakingUp() {} + + /** + * Called after the finished waking up call, ensuring it's after all the other listeners, + * reacting to {@link #onFinishedWakingUp()} + */ + default void onPostFinishedWakingUp() {} default void onStartedGoingToSleep() {} default void onFinishedGoingToSleep() {} } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index 66c51d278dab..e2716e992c48 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -159,7 +159,9 @@ public class MediaProjectionPermissionActivity extends Activity mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); final Window w = mDialog.getWindow(); - w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + // QS is not closed when pressing CastTile. Match the type of the dialog shown from the + // tile. + w.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); mDialog.show(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 558f0e634255..d1fe7d449bdd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -16,6 +16,7 @@ package com.android.systemui.media.dagger; +import android.app.Service; import android.content.Context; import android.view.WindowManager; @@ -30,6 +31,7 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.MediaTttFlags; import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver; import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; +import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -38,8 +40,11 @@ import java.util.concurrent.Executor; import javax.inject.Named; +import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; /** Dagger module for the media package. */ @Module @@ -128,4 +133,10 @@ public interface MediaModule { mediaTttChipControllerReceiver, mainExecutor)); } + + /** Inject into MediaTttSenderService. */ + @Binds + @IntoMap + @ClassKey(MediaTttSenderService.class) + Service bindMediaTttSenderService(MediaTttSenderService service); } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 5a86723677b1..460d38f45b4d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -16,9 +16,14 @@ package com.android.systemui.media.taptotransfer +import android.content.ComponentName import android.content.Context +import android.content.Intent +import android.content.ServiceConnection import android.graphics.Color import android.graphics.drawable.Icon +import android.media.MediaRoute2Info +import android.os.IBinder import android.util.Log import androidx.annotation.VisibleForTesting import com.android.systemui.R @@ -27,9 +32,12 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender -import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer +import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService +import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast import com.android.systemui.media.taptotransfer.sender.TransferInitiated import com.android.systemui.media.taptotransfer.sender.TransferSucceeded +import com.android.systemui.shared.mediattt.DeviceInfo +import com.android.systemui.shared.mediattt.IDeviceSenderCallback import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.concurrency.DelayableExecutor @@ -44,11 +52,14 @@ import javax.inject.Inject @SysUISingleton class MediaTttCommandLineHelper @Inject constructor( commandRegistry: CommandRegistry, - context: Context, + private val context: Context, private val mediaTttChipControllerSender: MediaTttChipControllerSender, private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver, @Main private val mainExecutor: DelayableExecutor, ) { + private var senderCallback: IDeviceSenderCallback? = null + private val senderServiceConnection = SenderServiceConnection() + private val appIconDrawable = Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { it.setTint(Color.YELLOW) @@ -68,14 +79,20 @@ class MediaTttCommandLineHelper @Inject constructor( inner class AddChipCommandSender : Command { override fun execute(pw: PrintWriter, args: List<String>) { val otherDeviceName = args[0] + val mediaInfo = MediaRoute2Info.Builder("id", "Test Name") + .addFeature("feature") + .build() + val otherDeviceInfo = DeviceInfo(otherDeviceName) + when (args[1]) { - MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> { - mediaTttChipControllerSender.displayChip( - MoveCloserToTransfer( - appIconDrawable, APP_ICON_CONTENT_DESCRIPTION, otherDeviceName - ) - ) + MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> { + runOnService { senderCallback -> + senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) + } } + + // TODO(b/203800643): Migrate other commands to invoke the service instead of the + // controller. TRANSFER_INITIATED_COMMAND_NAME -> { val futureTask = FutureTask { fakeUndoRunnable } mediaTttChipControllerSender.displayChip( @@ -101,7 +118,7 @@ class MediaTttCommandLineHelper @Inject constructor( } else -> { pw.println("Chip type must be one of " + - "$MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME, " + + "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " + "$TRANSFER_INITIATED_COMMAND_NAME, " + TRANSFER_SUCCEEDED_COMMAND_NAME ) @@ -114,19 +131,40 @@ class MediaTttCommandLineHelper @Inject constructor( "$ADD_CHIP_COMMAND_SENDER_TAG <deviceName> <chipStatus>" ) } + + private fun runOnService(command: SenderCallbackCommand) { + val currentServiceCallback = senderCallback + if (currentServiceCallback != null) { + command.run(currentServiceCallback) + } else { + bindService(command) + } + } + + private fun bindService(command: SenderCallbackCommand) { + senderServiceConnection.pendingCommand = command + val binding = context.bindService( + Intent(context, MediaTttSenderService::class.java), + senderServiceConnection, + Context.BIND_AUTO_CREATE + ) + Log.i(TAG, "Starting service binding? $binding") + } } /** A command to REMOVE the media ttt chip on the SENDER device. */ inner class RemoveChipCommandSender : Command { override fun execute(pw: PrintWriter, args: List<String>) { mediaTttChipControllerSender.removeChip() + if (senderCallback != null) { + context.unbindService(senderServiceConnection) + } } override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG") } } - /** A command to DISPLAY the media ttt chip on the RECEIVER device. */ inner class AddChipCommandReceiver : Command { override fun execute(pw: PrintWriter, args: List<String>) { @@ -149,6 +187,29 @@ class MediaTttCommandLineHelper @Inject constructor( } } + /** A service connection for [IDeviceSenderCallback]. */ + private inner class SenderServiceConnection : ServiceConnection { + // A command that should be run when the service gets connected. + var pendingCommand: SenderCallbackCommand? = null + + override fun onServiceConnected(className: ComponentName, service: IBinder) { + val newCallback = IDeviceSenderCallback.Stub.asInterface(service) + senderCallback = newCallback + pendingCommand?.run(newCallback) + pendingCommand = null + } + + override fun onServiceDisconnected(className: ComponentName) { + senderCallback = null + } + } + + /** An interface defining a command that should be run on the sender callback. */ + private fun interface SenderCallbackCommand { + /** Runs the command on the provided [senderCallback]. */ + fun run(senderCallback: IDeviceSenderCallback) + } + private val fakeUndoRunnable = Runnable { Log.i(TAG, "Undo runnable triggered") } @@ -163,7 +224,7 @@ const val ADD_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-add-receiver" @VisibleForTesting const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver" @VisibleForTesting -val MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME = MoveCloserToTransfer::class.simpleName!! +val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!! @VisibleForTesting val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!! @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index b1f6faaba924..dd434e7756fb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -40,17 +40,18 @@ sealed class ChipStateSender( ) : MediaTttChipState(appIconDrawable, appIconContentDescription) /** - * A state representing that the two devices are close but not close enough to initiate a transfer. - * The chip will instruct the user to move closer in order to initiate the transfer. + * A state representing that the two devices are close but not close enough to *start* a cast to + * the receiver device. The chip will instruct the user to move closer in order to initiate the + * transfer to the receiver. */ -class MoveCloserToTransfer( +class MoveCloserToStartCast( appIconDrawable: Drawable, appIconContentDescription: String, otherDeviceName: String, ) : ChipStateSender( appIconDrawable, appIconContentDescription, - R.string.media_move_closer_to_transfer, + R.string.media_move_closer_to_start_cast, otherDeviceName ) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt new file mode 100644 index 000000000000..b56a69903ea4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt @@ -0,0 +1,66 @@ +/* + * 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.systemui.media.taptotransfer.sender + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.Icon +import android.media.MediaRoute2Info +import android.os.IBinder +import com.android.systemui.R +import com.android.systemui.shared.mediattt.DeviceInfo +import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import javax.inject.Inject + +/** + * Service that allows external handlers to trigger the media chip on the sender device. + */ +class MediaTttSenderService @Inject constructor( + context: Context, + val controller: MediaTttChipControllerSender +) : Service() { + + // TODO(b/203800643): Add logging when callbacks trigger. + private val binder: IBinder = object : IDeviceSenderCallback.Stub() { + override fun closeToReceiverToStartCast( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) + } + } + + // TODO(b/203800643): Use the app icon from the media info instead of a fake one. + private val fakeAppIconDrawable = + Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { + it.setTint(Color.YELLOW) + } + + override fun onBind(intent: Intent?): IBinder = binder + + private fun closeToReceiverToStartCast( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + val chipState = MoveCloserToStartCast( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString(), + otherDeviceName = otherDeviceInfo.name + ) + controller.displayChip(chipState) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 5fbdd88b9f66..ac816ba9e8d5 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -16,6 +16,7 @@ package com.android.systemui.navigationbar; +import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; @@ -185,6 +186,17 @@ public class NavigationBarView extends FrameLayout implements @Nullable private Rect mOrientedHandleSamplingRegion; + /** + * {@code true} if the IME can render the back button and the IME switcher button. + * + * <p>The value must be used when and only when + * {@link com.android.systemui.shared.system.QuickStepContract#isGesturalMode(int)} returns + * {@code true}</p> + * + * <p>Cache the value here for better performance.</p> + */ + private final boolean mImeCanRenderGesturalNavButtons = canImeRenderGesturalNavButtons(); + private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; private boolean mHomeAppearing; @@ -760,9 +772,14 @@ public class NavigationBarView extends FrameLayout implements updateRecentsIcon(); + boolean isImeRenderingNavButtons = isGesturalMode(mNavBarMode) + && mImeCanRenderGesturalNavButtons; + // Update IME button visibility, a11y and rotate button always overrides the appearance - mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0); + boolean disableImeSwitcher = + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0 + || isImeRenderingNavButtons; + mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher); mBarTransitions.reapplyDarkIntensity(); @@ -777,7 +794,8 @@ public class NavigationBarView extends FrameLayout implements && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures() - || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)); + || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)) + || isImeRenderingNavButtons; // When screen pinning, don't hide back and home when connected service or back and // recents buttons when disconnected from launcher service in screen pinning mode, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index e06b768dbe6f..1dba536d52fd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -28,6 +28,7 @@ import android.view.View.OnLayoutChangeListener; import androidx.annotation.Nullable; +import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSTile; @@ -41,7 +42,6 @@ import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.tileimpl.HeightOverrideable; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import com.android.wm.shell.animation.Interpolators; import java.util.ArrayList; import java.util.Collection; @@ -301,6 +301,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha TouchAnimator.Builder qqsTranslationYBuilder = new Builder(); TouchAnimator.Builder translationXBuilder = new Builder(); TouchAnimator.Builder nonFirstPageAlphaBuilder = new Builder(); + TouchAnimator.Builder quadraticInterpolatorBuilder = new Builder() + .setInterpolator(Interpolators.ACCELERATE); Collection<QSTile> tiles = mHost.getTiles(); int count = 0; @@ -413,7 +415,13 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha qqsTranslationYBuilder ); - firstPageBuilder.addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1); + // Secondary labels on tiles not in QQS have two alpha animation applied: + // * on the tile themselves + // * on TileLayout + // Therefore, we use a quadratic interpolator animator to animate the alpha + // for tiles in QQS to match. + quadraticInterpolatorBuilder + .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1); nonFirstPageAlphaBuilder .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 0); @@ -461,6 +469,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mFirstPageAnimator = firstPageBuilder // Fade in the tiles/labels as we reach the final position. .addFloat(tileLayout, "alpha", 0, 1) + .addFloat(quadraticInterpolatorBuilder.build(), "position", 0, 1) .setListener(this) .build(); @@ -535,7 +544,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha builder.addFloat(tileView.getSecondaryIcon(), "translationY", -centerDiff, 0); // The labels have different apparent size in QQS vs QS (no secondary label), so the // translation needs to account for that. - int labelDiff = centerDiff - tileView.getSecondaryLabel().getMeasuredHeight() / 2; + int secondaryLabelOffset = 0; + if (tileView.getSecondaryLabel().getVisibility() == View.VISIBLE) { + secondaryLabelOffset = tileView.getSecondaryLabel().getMeasuredHeight() / 2; + } + int labelDiff = centerDiff - secondaryLabelOffset; builder.addFloat(tileView.getLabelContainer(), "translationY", -labelDiff, 0); builder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 0.3f, 1); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 41dced6bffeb..259b786dafb7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; +import android.os.Trace; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -172,9 +173,14 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), - R.style.Theme_SystemUI_QuickSettings)); - return inflater.inflate(R.layout.qs_panel, container, false); + try { + Trace.beginSection("QSFragment#onCreateView"); + inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), + R.style.Theme_SystemUI_QuickSettings)); + return inflater.inflate(R.layout.qs_panel, container, false); + } finally { + Trace.endSection(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 866b1b8cabb2..d3f8db38fd59 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -247,10 +247,9 @@ public class QuickStatusBarHeader extends FrameLayout { boolean shouldUseSplitShade = resources.getBoolean(R.bool.config_use_split_notification_shade); - mStatusIconsView.setVisibility( - shouldUseSplitShade || mUseCombinedQSHeader ? View.GONE : View.VISIBLE); - mDatePrivacyView.setVisibility( - shouldUseSplitShade || mUseCombinedQSHeader ? View.GONE : View.VISIBLE); + boolean gone = shouldUseSplitShade || mUseCombinedQSHeader || mQsDisabled; + mStatusIconsView.setVisibility(gone ? View.GONE : View.VISIBLE); + mDatePrivacyView.setVisibility(gone ? View.GONE : View.VISIBLE); mConfigShowBatteryEstimate = resources.getBoolean(R.bool.config_showBatteryEstimateQSBH); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 9acd3eb4afc3..d3bad167b660 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -414,9 +414,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { holder.mTileView.removeOnLayoutChangeListener(this); - holder.mTileView.requestFocus(); + holder.mTileView.requestAccessibilityFocus(); if (mAccessibilityAction == ACTION_NONE) { - holder.mTileView.clearFocus(); + holder.mTileView.clearAccessibilityFocus(); } } }); @@ -449,12 +449,13 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta // Update the tile divider position mTileDividerIndex++; mFocusIndex = mEditIndex - 1; + final int focus = mFocusIndex; mNeedsFocus = true; if (mRecyclerView != null) { mRecyclerView.post(() -> { final RecyclerView recyclerView = mRecyclerView; if (recyclerView != null) { - recyclerView.smoothScrollToPosition(mFocusIndex); + recyclerView.smoothScrollToPosition(focus); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java b/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java new file mode 100644 index 000000000000..3dec38734874 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java @@ -0,0 +1,75 @@ +/* + * 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.systemui.screenshot; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.util.DisplayMetrics; +import android.view.Window; +import android.view.WindowManager; + +import com.android.internal.policy.PhoneWindow; + +/** + * Utility methods for setting up a floating window + */ +public class FloatingWindowUtil { + + /** + * Convert input dp to pixels given DisplayMetrics + */ + public static float dpToPx(DisplayMetrics metrics, float dp) { + return dp * metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT; + } + + /** + * Sets up window params for a floating window + */ + public static WindowManager.LayoutParams getFloatingWindowParams() { + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT, + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + PixelFormat.TRANSLUCENT); + params.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + params.setFitInsetsTypes(0); + // This is needed to let touches pass through outside the touchable areas + params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; + return params; + } + + /** + * Constructs a transparent floating window + */ + public static PhoneWindow getFloatingWindow(Context context) { + PhoneWindow window = new PhoneWindow(context); + window.requestFeature(Window.FEATURE_NO_TITLE); + window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); + window.setBackgroundDrawableResource(android.R.color.transparent); + return window; + } + + +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java index 4a1aa169b207..6c01f0ea13a7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java @@ -70,19 +70,28 @@ public class ScreenshotActionChip extends FrameLayout { super.setPressed(mIsPending || pressed); } - void setIcon(Icon icon, boolean tint) { + /** + * Set chip icon and whether to tint with theme color + */ + public void setIcon(Icon icon, boolean tint) { mIconView.setImageIcon(icon); if (!tint) { mIconView.setImageTintList(null); } } - void setText(CharSequence text) { + /** + * Set chip text + */ + public void setText(CharSequence text) { mTextView.setText(text); updatePadding(text.length() > 0); } - void setPendingIntent(PendingIntent intent, Runnable finisher) { + /** + * Set PendingIntent to be sent and Runnable to be run, when chip is clicked + */ + public void setPendingIntent(PendingIntent intent, Runnable finisher) { setOnClickListener(v -> { try { intent.send(); @@ -93,7 +102,10 @@ public class ScreenshotActionChip extends FrameLayout { }); } - void setIsPending(boolean isPending) { + /** + * Set pressed state of chip (to be used when chip is clicked before underlying intent is ready) + */ + public void setIsPending(boolean isPending) { mIsPending = isPending; setPressed(mIsPending); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index ce571e54e65c..83d8d19a6d4d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -18,7 +18,6 @@ package com.android.systemui.screenshot; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; @@ -41,23 +40,21 @@ import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.ICompatCameraControlCallback; import android.app.Notification; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Insets; -import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.media.MediaActionSound; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.provider.Settings; import android.util.DisplayMetrics; @@ -76,7 +73,6 @@ import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; -import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -90,6 +86,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.R; +import com.android.systemui.clipboardoverlay.ClipboardOverlayController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; @@ -235,13 +232,11 @@ public class ScreenshotController { static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip"; - - private static final int MESSAGE_CORNER_TIMEOUT = 2; - private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; - // From WizardManagerHelper.java private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; + private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; + private final WindowContext mContext; private final ScreenshotNotificationsController mNotificationsController; private final ScreenshotSmartActions mScreenshotSmartActions; @@ -260,6 +255,7 @@ public class ScreenshotController { private final ScrollCaptureController mScrollCaptureController; private final LongScreenshotData mLongScreenshotHolder; private final boolean mIsLowRamDevice; + private final TimeoutHandler mScreenshotHandler; private ScreenshotView mScreenshotView; private Bitmap mScreenBitmap; @@ -270,24 +266,8 @@ public class ScreenshotController { private Animator mScreenshotAnimation; private RequestCallback mCurrentRequestCallback; private String mPackageName = ""; + private BroadcastReceiver mCopyBroadcastReceiver; - private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_CORNER_TIMEOUT: - if (DEBUG_UI) { - Log.d(TAG, "Corner timeout hit"); - } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0, - mPackageName); - ScreenshotController.this.dismissScreenshot(false); - break; - default: - break; - } - } - }; /** Tracks config changes that require re-creating UI */ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( @@ -309,7 +289,8 @@ public class ScreenshotController { @Main Executor mainExecutor, ScrollCaptureController scrollCaptureController, LongScreenshotData longScreenshotHolder, - ActivityManager activityManager) { + ActivityManager activityManager, + TimeoutHandler timeoutHandler) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; mScrollCaptureClient = scrollCaptureClient; @@ -321,6 +302,17 @@ public class ScreenshotController { mIsLowRamDevice = activityManager.isLowRamDevice(); mBgExecutor = Executors.newSingleThreadExecutor(); + mScreenshotHandler = timeoutHandler; + mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); + mScreenshotHandler.setOnTimeoutRunnable(() -> { + if (DEBUG_UI) { + Log.d(TAG, "Corner timeout hit"); + } + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0, + mPackageName); + ScreenshotController.this.dismissScreenshot(false); + }); + mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); final Context displayContext = context.createDisplayContext(getDefaultDisplay()); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); @@ -329,27 +321,11 @@ public class ScreenshotController { mAccessibilityManager = AccessibilityManager.getInstance(mContext); // Setup the window that we are going to use - mWindowLayoutParams = new WindowManager.LayoutParams( - MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT, - WindowManager.LayoutParams.FLAG_FULLSCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, - PixelFormat.TRANSLUCENT); + mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams(); mWindowLayoutParams.setTitle("ScreenshotAnimation"); - mWindowLayoutParams.layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mWindowLayoutParams.setFitInsetsTypes(0); - // This is needed to let touches pass through outside the touchable areas - mWindowLayoutParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; - mWindow = new PhoneWindow(mContext); + mWindow = FloatingWindowUtil.getFloatingWindow(mContext); mWindow.setWindowManager(mWindowManager, null, null); - mWindow.requestFeature(Window.FEATURE_NO_TITLE); - mWindow.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); - mWindow.setBackgroundDrawableResource(android.R.color.transparent); mConfigChanges.applyNewConfig(context.getResources()); reloadAssets(); @@ -357,6 +333,18 @@ public class ScreenshotController { // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); mCameraSound.load(MediaActionSound.SHUTTER_CLICK); + + mCopyBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) { + dismissScreenshot(false); + } + } + }; + mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( + ClipboardOverlayController.COPY_OVERLAY_ACTION), + ClipboardOverlayController.SELF_PERMISSION, null); } void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, @@ -424,7 +412,7 @@ public class ScreenshotController { } return; } - cancelTimeout(); + mScreenshotHandler.cancelTimeout(); if (immediate) { finishDismiss(); } else { @@ -440,6 +428,7 @@ public class ScreenshotController { * Release the constructed window context. */ void releaseContext() { + mContext.unregisterReceiver(mCopyBroadcastReceiver); mContext.release(); mCameraSound.release(); mBgExecutor.shutdownNow(); @@ -462,7 +451,7 @@ public class ScreenshotController { if (DEBUG_INPUT) { Log.d(TAG, "onUserInteraction"); } - resetTimeout(); + mScreenshotHandler.resetTimeout(); } @Override @@ -517,6 +506,9 @@ public class ScreenshotController { } saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true); + + mContext.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), + ClipboardOverlayController.SELF_PERMISSION); } private Bitmap captureScreenshot(Rect crop) { @@ -651,7 +643,7 @@ public class ScreenshotController { // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); - cancelTimeout(); // restarted after animation + mScreenshotHandler.cancelTimeout(); // restarted after animation } private void requestScrollCapture() { @@ -878,7 +870,7 @@ public class ScreenshotController { } mScreenshotView.reset(); removeWindow(); - cancelTimeout(); + mScreenshotHandler.cancelTimeout(); } /** @@ -905,30 +897,6 @@ public class ScreenshotController { mSaveInBgTask.execute(); } - private void cancelTimeout() { - if (DEBUG_DISMISS) { - Log.d(TAG, "cancel timeout"); - } - mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); - } - - private void resetTimeout() { - cancelTimeout(); - - AccessibilityManager accessibilityManager = (AccessibilityManager) - mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); - long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis( - SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS, - AccessibilityManager.FLAG_CONTENT_CONTROLS); - - mScreenshotHandler.sendMessageDelayed( - mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT), - timeoutMs); - if (DEBUG_DISMISS) { - Log.d(TAG, "dismiss timeout: " + timeoutMs + " ms"); - } - - } /** * Sets up the action shade and its entrance animation, once we get the screenshot URI. @@ -939,7 +907,7 @@ public class ScreenshotController { Log.d(TAG, "Showing UI actions"); } - resetTimeout(); + mScreenshotHandler.resetTimeout(); if (imageData.uri != null) { mScreenshotHandler.post(() -> { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 7bcaf5f0f6bb..e5649a126807 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -222,7 +222,6 @@ public class ScreenshotView extends FrameLayout implements } }); mSwipeDetector.setIsLongpressEnabled(false); - mSwipeDismissHandler = new SwipeDismissHandler(); addOnAttachStateChangeListener(new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -244,7 +243,7 @@ public class ScreenshotView extends FrameLayout implements * Called to display the scroll action chip when support is detected. * * @param packageName the owning package of the window to be captured - * @param onClick the action to take when the chip is clicked. + * @param onClick the action to take when the chip is clicked. */ public void showScrollChip(String packageName, Runnable onClick) { if (DEBUG_SCROLL) { @@ -273,10 +272,12 @@ public class ScreenshotView extends FrameLayout implements final Rect tmpRect = new Rect(); mScreenshotPreview.getBoundsOnScreen(tmpRect); - tmpRect.inset((int) dpToPx(-SWIPE_PADDING_DP), (int) dpToPx(-SWIPE_PADDING_DP)); + tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP), + (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP)); touchRegion.op(tmpRect, Region.Op.UNION); mActionsContainerBackground.getBoundsOnScreen(tmpRect); - tmpRect.inset((int) dpToPx(-SWIPE_PADDING_DP), (int) dpToPx(-SWIPE_PADDING_DP)); + tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP), + (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP)); touchRegion.op(tmpRect, Region.Op.UNION); mDismissButton.getBoundsOnScreen(tmpRect); touchRegion.op(tmpRect, Region.Op.UNION); @@ -365,7 +366,7 @@ public class ScreenshotView extends FrameLayout implements mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip)); mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip)); - int swipePaddingPx = (int) dpToPx(SWIPE_PADDING_DP); + int swipePaddingPx = (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, SWIPE_PADDING_DP); TouchDelegate previewDelegate = new TouchDelegate( new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx), mScreenshotPreview); @@ -390,6 +391,24 @@ public class ScreenshotView extends FrameLayout implements // Get focus so that the key events go to the layout. setFocusableInTouchMode(true); requestFocus(); + + mSwipeDismissHandler = new SwipeDismissHandler(mContext, mScreenshotStatic, + new SwipeDismissHandler.SwipeDismissCallbacks() { + @Override + public void onInteraction() { + mCallbacks.onUserInteraction(); + } + + @Override + public void onDismiss() { + if (DEBUG_DISMISS) { + Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture"); + } + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, + mPackageName); + mCallbacks.onDismiss(); + } + }); } View getScreenshotPreview() { @@ -859,8 +878,8 @@ public class ScreenshotView extends FrameLayout implements @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mCallbacks.onDismiss(); - } + mCallbacks.onDismiss(); + } }); animSet.start(); } @@ -934,38 +953,7 @@ public class ScreenshotView extends FrameLayout implements } void animateDismissal() { - animateDismissal(createScreenshotTranslateDismissAnimation()); - } - - private void animateDismissal(Animator dismissAnimation) { - mDismissAnimation = dismissAnimation; - mDismissAnimation.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled = false; - - @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - if (DEBUG_ANIM) { - Log.d(TAG, "Cancelled dismiss animation"); - } - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (!mCancelled) { - if (DEBUG_ANIM) { - Log.d(TAG, "after dismiss animation, calling onDismissRunnable.run()"); - } - mCallbacks.onDismiss(); - } - } - }); - if (DEBUG_ANIM) { - Log.d(TAG, "Starting dismiss animation"); - } - mDismissAnimation.start(); + mSwipeDismissHandler.dismiss(); } void reset() { @@ -979,6 +967,7 @@ public class ScreenshotView extends FrameLayout implements } mDismissAnimation.cancel(); } + mSwipeDismissHandler.cancel(); if (DEBUG_WINDOW) { Log.d(TAG, "removing OnComputeInternalInsetsListener"); } @@ -1042,8 +1031,8 @@ public class ScreenshotView extends FrameLayout implements xAnim.setInterpolator(mAccelerateInterpolator); xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS); float deltaX = mDirectionLTR - ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth()) - : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX()); + ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth()) + : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX()); xAnim.addUpdateListener(animation -> { float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction()); mScreenshotStatic.setTranslationX(currXDelta); @@ -1100,130 +1089,4 @@ public class ScreenshotView extends FrameLayout implements return insetDrawable; } } - - private float dpToPx(float dp) { - return dp * mDisplayMetrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT; - } - - class SwipeDismissHandler implements OnTouchListener { - // distance needed to register a dismissal - private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20; - - private final GestureDetector mGestureDetector; - - private float mStartX; - // Keeps track of the most recent direction (between the last two move events). - // -1 for left; +1 for right. - private int mDirectionX; - private float mPreviousX; - - SwipeDismissHandler() { - GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener(); - mGestureDetector = new GestureDetector(mContext, gestureListener); - } - - @Override - public boolean onTouch(View view, MotionEvent event) { - boolean gestureResult = mGestureDetector.onTouchEvent(event); - mCallbacks.onUserInteraction(); - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mStartX = event.getRawX(); - mPreviousX = mStartX; - return true; - } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { - if (mDismissAnimation != null && mDismissAnimation.isRunning()) { - return true; - } - if (isPastDismissThreshold()) { - if (DEBUG_DISMISS) { - Log.d(TAG, "dismiss triggered via swipe gesture"); - } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, mPackageName); - animateDismissal(createSwipeDismissAnimation()); - } else { - // if we've moved, but not past the threshold, start the return animation - if (DEBUG_DISMISS) { - Log.d(TAG, "swipe gesture abandoned"); - } - createSwipeReturnAnimation().start(); - } - return true; - } - return gestureResult; - } - - class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onScroll( - MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { - mScreenshotStatic.setTranslationX(ev2.getRawX() - mStartX); - mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1; - mPreviousX = ev2.getRawX(); - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - if (mScreenshotStatic.getTranslationX() * velocityX > 0 - && (mDismissAnimation == null || !mDismissAnimation.isRunning())) { - animateDismissal(createSwipeDismissAnimation(velocityX / (float) 1000)); - return true; - } - return false; - } - } - - private boolean isPastDismissThreshold() { - float translationX = mScreenshotStatic.getTranslationX(); - // Determines whether the absolute translation from the start is in the same direction - // as the current movement. For example, if the user moves most of the way to the right, - // but then starts dragging back left, we do not dismiss even though the absolute - // distance is greater than the threshold. - if (translationX * mDirectionX > 0) { - return Math.abs(translationX) >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP); - } - return false; - } - - private ValueAnimator createSwipeDismissAnimation() { - return createSwipeDismissAnimation(1); - } - - private ValueAnimator createSwipeDismissAnimation(float velocity) { - // velocity is measured in pixels per millisecond - velocity = Math.min(3, Math.max(1, velocity)); - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - float startX = mScreenshotStatic.getTranslationX(); - // make sure the UI gets all the way off the screen in the direction of movement - // (the actions container background is guaranteed to be both the leftmost and - // rightmost UI element in LTR and RTL) - float finalX = startX < 0 - ? -1 * mActionsContainerBackground.getRight() - : mDisplayMetrics.widthPixels; - float distance = Math.abs(finalX - startX); - - anim.addUpdateListener(animation -> { - float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction()); - mScreenshotStatic.setTranslationX(translation); - setAlpha(1 - animation.getAnimatedFraction()); - }); - anim.setDuration((long) (distance / Math.abs(velocity))); - return anim; - } - - private ValueAnimator createSwipeReturnAnimation() { - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - float startX = mScreenshotStatic.getTranslationX(); - float finalX = 0; - - anim.addUpdateListener(animation -> { - float translation = MathUtils.lerp( - startX, finalX, animation.getAnimatedFraction()); - mScreenshotStatic.setTranslationX(translation); - }); - - return anim; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java new file mode 100644 index 000000000000..4e960037bb7b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java @@ -0,0 +1,210 @@ +/* + * 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.systemui.screenshot; + +import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.MathUtils; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +/** + * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not met + */ +public class SwipeDismissHandler implements View.OnTouchListener { + private static final String TAG = "SwipeDismissHandler"; + + // distance needed to register a dismissal + private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20; + + /** + * Stores the callbacks when the view is interacted with or dismissed. + */ + public interface SwipeDismissCallbacks { + /** + * Run when the view is interacted with (touched) + */ + void onInteraction(); + + /** + * Run when the view is dismissed (the distance threshold is met), post-dismissal animation + */ + void onDismiss(); + } + + private final View mView; + private final SwipeDismissCallbacks mCallbacks; + private final GestureDetector mGestureDetector; + private DisplayMetrics mDisplayMetrics; + private ValueAnimator mDismissAnimation; + + + private float mStartX; + // Keeps track of the most recent direction (between the last two move events). + // -1 for left; +1 for right. + private int mDirectionX; + private float mPreviousX; + + public SwipeDismissHandler(Context context, View view, SwipeDismissCallbacks callbacks) { + mView = view; + mCallbacks = callbacks; + GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener(); + mGestureDetector = new GestureDetector(context, gestureListener); + mDisplayMetrics = new DisplayMetrics(); + context.getDisplay().getRealMetrics(mDisplayMetrics); + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + boolean gestureResult = mGestureDetector.onTouchEvent(event); + mCallbacks.onInteraction(); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mStartX = event.getRawX(); + mPreviousX = mStartX; + return true; + } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { + if (mDismissAnimation != null && mDismissAnimation.isRunning()) { + return true; + } + if (isPastDismissThreshold()) { + dismiss(); + } else { + // if we've moved, but not past the threshold, start the return animation + if (DEBUG_DISMISS) { + Log.d(TAG, "swipe gesture abandoned"); + } + createSwipeReturnAnimation().start(); + } + return true; + } + return gestureResult; + } + + class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onScroll( + MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { + mView.setTranslationX(ev2.getRawX() - mStartX); + mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1; + mPreviousX = ev2.getRawX(); + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (mView.getTranslationX() * velocityX > 0 + && (mDismissAnimation == null || !mDismissAnimation.isRunning())) { + dismiss(velocityX / (float) 1000); + return true; + } + return false; + } + } + + private boolean isPastDismissThreshold() { + float translationX = mView.getTranslationX(); + // Determines whether the absolute translation from the start is in the same direction + // as the current movement. For example, if the user moves most of the way to the right, + // but then starts dragging back left, we do not dismiss even though the absolute + // distance is greater than the threshold. + if (translationX * mDirectionX > 0) { + return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics, + DISMISS_DISTANCE_THRESHOLD_DP); + } + return false; + } + + /** + * Cancel the currently-running dismissal animation, if any. + */ + public void cancel() { + if (mDismissAnimation != null && mDismissAnimation.isRunning()) { + mDismissAnimation.cancel(); + } + } + + /** + * Start dismissal animation (will run onDismiss callback when animation complete) + */ + public void dismiss() { + dismiss(1); + } + + private void dismiss(float velocity) { + mDismissAnimation = createSwipeDismissAnimation(velocity); + mDismissAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (!mCancelled) { + mCallbacks.onDismiss(); + } + } + }); + mDismissAnimation.start(); + } + + private ValueAnimator createSwipeDismissAnimation(float velocity) { + // velocity is measured in pixels per millisecond + velocity = Math.min(3, Math.max(1, velocity)); + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + float startX = mView.getTranslationX(); + // make sure the UI gets all the way off the screen in the direction of movement + // (the actions container background is guaranteed to be both the leftmost and + // rightmost UI element in LTR and RTL) + float finalX = startX <= 0 ? -1 * mView.getRight() : mDisplayMetrics.widthPixels; + float distance = Math.abs(finalX - startX); + + anim.addUpdateListener(animation -> { + float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction()); + mView.setTranslationX(translation); + mView.setAlpha(1 - animation.getAnimatedFraction()); + }); + anim.setDuration((long) (distance / Math.abs(velocity))); + return anim; + } + + private ValueAnimator createSwipeReturnAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + float startX = mView.getTranslationX(); + float finalX = 0; + + anim.addUpdateListener(animation -> { + float translation = MathUtils.lerp( + startX, finalX, animation.getAnimatedFraction()); + mView.setTranslationX(translation); + }); + + return anim; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 84e21e4126cc..98e6bd141b65 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -83,7 +83,7 @@ public class TakeScreenshotService extends Service { /** Informs about coarse grained state of the Controller. */ interface RequestCallback { - /** Respond to the current request indicating the screenshot request failed.*/ + /** Respond to the current request indicating the screenshot request failed. */ void reportError(); /** The controller has completed handling this request UI has been removed */ @@ -230,7 +230,7 @@ public class TakeScreenshotService extends Service { return false; } return true; - }; + } private static void sendComplete(Messenger target) { try { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java new file mode 100644 index 000000000000..9156601e6638 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java @@ -0,0 +1,102 @@ +/* + * 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.systemui.screenshot; + +import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.accessibility.AccessibilityManager; + +import javax.inject.Inject; + +/** + * Starts a configurable runnable on timeout. Can be cancelled. Used for automatically dismissing + * floating overlays. + */ +public class TimeoutHandler extends Handler { + private static final String TAG = "TimeoutHandler"; + + private static final int MESSAGE_CORNER_TIMEOUT = 2; + private static final int DEFAULT_TIMEOUT_MILLIS = 6000; + + private final Context mContext; + + private Runnable mOnTimeout; + private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS; + + @Inject + public TimeoutHandler(Context context) { + super(Looper.getMainLooper()); + mContext = context; + mOnTimeout = () -> { + }; + } + + public void setOnTimeoutRunnable(Runnable onTimeout) { + mOnTimeout = onTimeout; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_CORNER_TIMEOUT: + mOnTimeout.run(); + break; + default: + break; + } + } + + /** + * Set the default timeout (if not overridden by accessibility) + */ + public void setDefaultTimeoutMillis(int timeout) { + mDefaultTimeout = timeout; + } + + /** + * Cancel the current timeout, if any. To reset the delayed runnable use resetTimeout instead. + */ + public void cancelTimeout() { + if (DEBUG_DISMISS) { + Log.d(TAG, "cancel timeout"); + } + removeMessages(MESSAGE_CORNER_TIMEOUT); + } + + /** + * Reset the timeout. + */ + public void resetTimeout() { + cancelTimeout(); + + AccessibilityManager accessibilityManager = (AccessibilityManager) + mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); + long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis( + mDefaultTimeout, + AccessibilityManager.FLAG_CONTENT_CONTROLS); + + sendMessageDelayed(obtainMessage(MESSAGE_CORNER_TIMEOUT), timeoutMs); + if (DEBUG_DISMISS) { + Log.d(TAG, "dismiss timeout: " + timeoutMs + " ms"); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index cd4b74514937..963a0d709dac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -34,8 +34,6 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -72,7 +70,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -149,6 +146,7 @@ public class KeyguardIndicationController { private CharSequence mBiometricMessage; protected ColorStateList mInitialTextColorState; private boolean mVisible; + private boolean mOrganizationOwnedDevice; private boolean mPowerPluggedIn; private boolean mPowerPluggedInWired; @@ -256,13 +254,13 @@ public class KeyguardIndicationController { mExecutor, mStatusBarStateController); updateIndication(false /* animate */); - updateDisclosure(); + updateOrganizedOwnedDevice(); if (mBroadcastReceiver == null) { // Update the disclosure proactively to avoid IPC on the critical path. mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - updateDisclosure(); + updateOrganizedOwnedDevice(); } }; IntentFilter intentFilter = new IntentFilter(); @@ -305,12 +303,11 @@ public class KeyguardIndicationController { } /** - * Doesn't include disclosure (also a persistent indication) which gets triggered separately. - * * This method also doesn't update transient messages like biometrics since those messages * are also updated separately. */ private void updatePersistentIndications(boolean animate, int userId) { + updateDisclosure(); updateOwnerInfo(); updateBattery(animate); updateUserLocked(userId); @@ -320,9 +317,14 @@ public class KeyguardIndicationController { updateResting(); } - private void updateDisclosure() { + private void updateOrganizedOwnedDevice() { // avoid calling this method since it has an IPC - if (whitelistIpcs(this::isOrganizationOwnedDevice)) { + mOrganizationOwnedDevice = whitelistIpcs(this::isOrganizationOwnedDevice); + updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser()); + } + + private void updateDisclosure() { + if (mOrganizationOwnedDevice) { final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName(); final CharSequence disclosure = getDisclosureText(organizationName); mRotateTextViewController.updateIndication( @@ -335,8 +337,6 @@ public class KeyguardIndicationController { } else { mRotateTextViewController.hideIndication(INDICATION_TYPE_DISCLOSURE); } - - updateResting(); } private CharSequence getDisclosureText(@Nullable CharSequence organizationName) { @@ -753,60 +753,6 @@ public class KeyguardIndicationController { updatePersistentIndications(animate, KeyguardUpdateMonitor.getCurrentUser()); } - // animates textView - textView moves up and bounces down - private void animateText(KeyguardIndicationTextView textView, String indication) { - int yTranslation = mContext.getResources().getInteger( - R.integer.wired_charging_keyguard_text_animation_distance); - int animateUpDuration = mContext.getResources().getInteger( - R.integer.wired_charging_keyguard_text_animation_duration_up); - int animateDownDuration = mContext.getResources().getInteger( - R.integer.wired_charging_keyguard_text_animation_duration_down); - textView.animate().cancel(); - ViewClippingUtil.setClippingDeactivated(textView, true, mClippingParams); - textView.animate() - .translationYBy(yTranslation) - .setInterpolator(Interpolators.LINEAR) - .setDuration(animateUpDuration) - .setListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationStart(Animator animation) { - textView.switchIndication(indication, null); - } - - @Override - public void onAnimationCancel(Animator animation) { - textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y); - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mCancelled) { - ViewClippingUtil.setClippingDeactivated(textView, false, - mClippingParams); - return; - } - textView.animate() - .setDuration(animateDownDuration) - .setInterpolator(Interpolators.BOUNCE) - .translationY(BOUNCE_ANIMATION_FINAL_Y) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y); - ViewClippingUtil.setClippingDeactivated(textView, false, - mClippingParams); - // Unset the listener, otherwise this may persist for - // another view property animation - textView.animate().setListener(null); - } - }); - } - }); - } - protected String computePowerIndication() { int chargingId; if (mBatteryOverheated) { @@ -1182,9 +1128,12 @@ public class KeyguardIndicationController { @Override public void onKeyguardShowingChanged() { + // All transient messages are gone the next time keyguard is shown if (!mKeyguardStateController.isShowing()) { mTopIndicationView.clearMessages(); mRotateTextViewController.clearMessages(); + } else { + updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser()); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 648e14cddff6..c136d9cc7272 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -7,7 +7,6 @@ import android.animation.ValueAnimator import android.content.Context import android.content.res.Configuration import android.os.SystemClock -import android.util.DisplayMetrics import android.util.IndentingPrintWriter import android.util.MathUtils import android.view.MotionEvent @@ -24,6 +23,7 @@ import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.media.MediaHierarchyManager import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager @@ -64,6 +64,7 @@ class LockscreenShadeTransitionController @Inject constructor( private val scrimController: ScrimController, private val depthController: NotificationShadeDepthController, private val context: Context, + wakefulnessLifecycle: WakefulnessLifecycle, configurationController: ConfigurationController, falsingManager: FalsingManager, dumpManager: DumpManager, @@ -120,6 +121,12 @@ class LockscreenShadeTransitionController @Inject constructor( private var nextHideKeyguardNeedsNoAnimation = false /** + * Are we currently waking up to the shade locked + */ + var isWakingToShadeLocked: Boolean = false + private set + + /** * The distance until we're showing the notifications when pulsing */ val distanceUntilShowingPulsingNotifications @@ -160,6 +167,13 @@ class LockscreenShadeTransitionController @Inject constructor( } } }) + wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer { + override fun onPostFinishedWakingUp() { + // when finishing waking up, the UnlockedScreenOffAnimation has another attempt + // to reset keyguard. Let's do it in post + isWakingToShadeLocked = false + } + }) } private fun updateResources() { @@ -494,6 +508,10 @@ class LockscreenShadeTransitionController @Inject constructor( draggedDownEntry = entry } else { logger.logGoingToLockedShade(animationHandler != null) + if (statusBarStateController.isDozing) { + // Make sure we don't go back to keyguard immediately again after waking up + isWakingToShadeLocked = true + } statusBarStateController.setState(StatusBarState.SHADE_LOCKED) // This call needs to be after updating the shade state since otherwise // the scrimstate resets too early @@ -605,6 +623,7 @@ class LockscreenShadeTransitionController @Inject constructor( it.println("qSDragProgress: $qSDragProgress") it.println("isDragDownAnywhereEnabled: $isDragDownAnywhereEnabled") it.println("isFalsingCheckNeeded: $isFalsingCheckNeeded") + it.println("isWakingToShadeLocked: $isWakingToShadeLocked") it.println("hasPendingHandlerOnKeyguardDismiss: " + "${animationHandlerOnKeyguardDismiss != null}") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 9dc823b63290..d785059e3de7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -293,7 +293,8 @@ public class NotificationLockscreenUserManagerImpl implements IntentFilter internalFilter = new IntentFilter(); internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); - mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null); + mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null, + Context.RECEIVER_EXPORTED_UNAUDITED); mCurrentUserId = ActivityManager.getCurrentUser(); // in case we reg'd receiver too late updateCurrentProfilesCache(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index ea51bd89df42..3fe108f2c951 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -107,8 +107,6 @@ constructor( private var mDraggedFarEnough: Boolean = false private var mStartingChild: ExpandableView? = null private var mPulsing: Boolean = false - var isWakingToShadeLocked: Boolean = false - private set private var velocityTracker: VelocityTracker? = null @@ -235,7 +233,6 @@ constructor( mStartingChild = null } if (statusBarStateController.isDozing) { - isWakingToShadeLocked = true wakeUpCoordinator.willWakeUp = true mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE, "com.android.systemui:PULSEDRAG") @@ -333,10 +330,6 @@ constructor( mPulsing = pulsing } - fun onStartedWakingUp() { - isWakingToShadeLocked = false - } - override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { IndentingPrintWriter(pw, " ").let { it.println("PulseExpansionHandler:") @@ -344,7 +337,6 @@ constructor( it.println("isExpanding: $isExpanding") it.println("leavingLockscreen: $leavingLockscreen") it.println("mPulsing: $mPulsing") - it.println("isWakingToShadeLocked: $isWakingToShadeLocked") it.println("qsExpanded: $qsExpanded") it.println("bouncerShowing: $bouncerShowing") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index ea90bdd940a7..6c3a9093fa98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -17,14 +17,10 @@ package com.android.systemui.statusbar; import android.content.Context; -import android.database.ContentObserver; -import android.media.AudioAttributes; import android.os.AsyncTask; -import android.os.Handler; -import android.os.UserHandle; +import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; -import android.provider.Settings; import com.android.systemui.dagger.SysUISingleton; @@ -37,19 +33,8 @@ public class VibratorHelper { private final Vibrator mVibrator; private final Context mContext; - private boolean mHapticFeedbackEnabled; - private static final AudioAttributes STATUS_BAR_VIBRATION_ATTRIBUTES = - new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); - - final private ContentObserver mVibrationObserver = new ContentObserver(Handler.getMain()) { - @Override - public void onChange(boolean selfChange) { - updateHapticFeedBackEnabled(); - } - }; + private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); /** */ @@ -57,23 +42,11 @@ public class VibratorHelper { public VibratorHelper(Context context) { mContext = context; mVibrator = context.getSystemService(Vibrator.class); - - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED), true, - mVibrationObserver); - mVibrationObserver.onChange(false /* selfChange */); } public void vibrate(final int effectId) { - if (mHapticFeedbackEnabled) { - AsyncTask.execute(() -> - mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */), - STATUS_BAR_VIBRATION_ATTRIBUTES)); - } - } - - private void updateHapticFeedBackEnabled() { - mHapticFeedbackEnabled = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + AsyncTask.execute(() -> + mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */), + TOUCH_VIBRATION_ATTRIBUTES)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index 5272a16a4b72..300c3a220599 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -334,6 +334,8 @@ public class NetworkControllerImpl extends BroadcastReceiver setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup()); } }); + // Get initial user setup state + setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup()); WifiManager.ScanResultsCallback scanResultsCallback = new WifiManager.ScanResultsCallback() { @@ -994,6 +996,11 @@ public class NetworkControllerImpl extends BroadcastReceiver } @VisibleForTesting + boolean isUserSetup() { + return mUserSetup; + } + + @VisibleForTesting boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) { if (allSubscriptions.size() != mMobileSignalControllers.size()) { return false; @@ -1137,6 +1144,7 @@ public class NetworkControllerImpl extends BroadcastReceiver /** */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NetworkController state:"); + pw.println(" mUserSetup=" + mUserSetup); pw.println(" - telephony ------"); pw.print(" hasVoiceCallingFeature()="); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index b0d41f155713..3449bd8e2686 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -32,6 +32,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationContentView import com.android.systemui.statusbar.notification.stack.StackStateAnimator @@ -132,12 +134,15 @@ class AnimatedImageNotificationManager @Inject constructor( /** * Tracks state related to conversation notifications, and updates the UI of existing notifications * when necessary. + * TODO(b/214083332) Refactor this class to use the right coordinators and controllers */ @SysUISingleton class ConversationNotificationManager @Inject constructor( private val notificationEntryManager: NotificationEntryManager, private val notificationGroupManager: NotificationGroupManagerLegacy, private val context: Context, + private val notifCollection: CommonNotifCollection, + private val featureFlags: NotifPipelineFlags, @Main private val mainHandler: Handler ) { // Need this state to be thread safe, since it's accessed from the ui thread @@ -146,76 +151,93 @@ class ConversationNotificationManager @Inject constructor( private var notifPanelCollapsed = true - init { - notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener { - override fun onNotificationRankingUpdated(rankingMap: RankingMap) { - fun getLayouts(view: NotificationContentView) = - sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) - val ranking = Ranking() - val activeConversationEntries = states.keys.asSequence() - .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) } - for (entry in activeConversationEntries) { - if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { - val important = ranking.channel.isImportantConversation - var changed = false - entry.row?.layouts?.asSequence() - ?.flatMap(::getLayouts) - ?.mapNotNull { it as? ConversationLayout } - ?.filterNot { it.isImportantConversation == important } - ?.forEach { layout -> - changed = true - if (important && entry.isMarkedForUserTriggeredMovement) { - // delay this so that it doesn't animate in until after - // the notif has been moved in the shade - mainHandler.postDelayed( - { - layout.setIsImportantConversation( - important, - true) - }, - IMPORTANCE_ANIMATION_DELAY.toLong()) - } else { - layout.setIsImportantConversation(important, false) - } - } - if (changed) { - notificationGroupManager.updateIsolation(entry) + private val entryManagerListener = object : NotificationEntryListener { + override fun onNotificationRankingUpdated(rankingMap: RankingMap) = + updateNotificationRanking(rankingMap) + override fun onEntryInflated(entry: NotificationEntry) = + onEntryViewBound(entry) + override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry) + override fun onEntryRemoved( + entry: NotificationEntry, + visibility: NotificationVisibility?, + removedByUser: Boolean, + reason: Int + ) = removeTrackedEntry(entry) + } + + private val notifCollectionListener = object : NotifCollectionListener { + override fun onRankingUpdate(ranking: RankingMap) = + updateNotificationRanking(ranking) + + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + removeTrackedEntry(entry) + } + } + + private fun updateNotificationRanking(rankingMap: RankingMap) { + fun getLayouts(view: NotificationContentView) = + sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) + val ranking = Ranking() + val activeConversationEntries = states.keys.asSequence() + .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) } + for (entry in activeConversationEntries) { + if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { + val important = ranking.channel.isImportantConversation + var changed = false + entry.row?.layouts?.asSequence() + ?.flatMap(::getLayouts) + ?.mapNotNull { it as? ConversationLayout } + ?.filterNot { it.isImportantConversation == important } + ?.forEach { layout -> + changed = true + if (important && entry.isMarkedForUserTriggeredMovement) { + // delay this so that it doesn't animate in until after + // the notif has been moved in the shade + mainHandler.postDelayed( + { + layout.setIsImportantConversation( + important, + true) + }, + IMPORTANCE_ANIMATION_DELAY.toLong()) + } else { + layout.setIsImportantConversation(important, false) + } } - } + if (changed) { + notificationGroupManager.updateIsolation(entry) } } - - override fun onEntryInflated(entry: NotificationEntry) { - if (!entry.ranking.isConversation) { - return - } - fun updateCount(isExpanded: Boolean) { - if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) { - resetCount(entry.key) - entry.row?.let(::resetBadgeUi) - } - } - entry.row?.setOnExpansionChangedListener { isExpanded -> - if (entry.row?.isShown == true && isExpanded) { - entry.row.performOnIntrinsicHeightReached { - updateCount(isExpanded) - } - } else { - updateCount(isExpanded) - } + } + } + fun onEntryViewBound(entry: NotificationEntry) { + if (!entry.ranking.isConversation) { + return + } + fun updateCount(isExpanded: Boolean) { + if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) { + resetCount(entry.key) + entry.row?.let(::resetBadgeUi) + } + } + entry.row?.setOnExpansionChangedListener { isExpanded -> + if (entry.row?.isShown == true && isExpanded) { + entry.row.performOnIntrinsicHeightReached { + updateCount(isExpanded) } - updateCount(entry.row?.isExpanded == true) + } else { + updateCount(isExpanded) } + } + updateCount(entry.row?.isExpanded == true) + } - override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry) - - override fun onEntryRemoved( - entry: NotificationEntry, - visibility: NotificationVisibility?, - removedByUser: Boolean, - reason: Int - ) = removeTrackedEntry(entry) - }) + init { + if (featureFlags.isNewPipelineEnabled()) { + notifCollection.addCollectionListener(notifCollectionListener) + } else { + notificationEntryManager.addNotificationEntryListener(entryManagerListener) + } } private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index 97ae83ef3f6a..643deb7463b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification; import android.annotation.Nullable; +import android.app.NotificationChannel; +import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; @@ -106,4 +108,20 @@ public interface NotificationEntryListener { */ default void onNotificationRankingUpdated(RankingMap rankingMap) { } + + /** + * Called when a notification channel is modified, in response to + * {@link NotificationListenerService#onNotificationChannelModified}. + * + * @param pkgName the package the notification channel belongs to. + * @param user the user the notification channel belongs to. + * @param channel the channel being modified. + * @param modificationType the type of modification that occurred to the channel. + */ + default void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 09c608df6ca8..c33160858d0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -21,8 +21,10 @@ import static com.android.systemui.statusbar.notification.collection.NotifCollec import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import android.app.Notification; +import android.app.NotificationChannel; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; @@ -59,6 +61,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.util.Assert; +import com.android.systemui.util.Compile; import com.android.systemui.util.leak.LeakDetector; import java.io.FileDescriptor; @@ -401,6 +404,15 @@ public class NotificationEntryManager implements @Override public void onNotificationsInitialized() { } + + @Override + public void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + notifyChannelModified(pkgName, user, channel, modificationType); + } }; /** @@ -778,6 +790,19 @@ public class NotificationEntryManager implements } } + void notifyChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onNotificationChannelModified(pkgName, user, channel, modificationType); + } + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onNotificationChannelModified(pkgName, user, channel, modificationType); + } + } + private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) { if (rankingMap == null) { return; @@ -1011,7 +1036,7 @@ public class NotificationEntryManager implements } private static final String TAG = "NotificationEntryMgr"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); /** * Used when a notification is removed and it doesn't have a reason that maps to one of the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java index cbc113ba91bf..c3cc97bc14a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java @@ -24,6 +24,7 @@ import android.widget.ImageView; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * A util class for various reusable functions @@ -73,4 +74,20 @@ public class NotificationUtils { return (int) (dimensionPixelSize * factor); } + /** Get the notification key, reformatted for logging, for the (optional) entry */ + public static String logKey(NotificationEntry entry) { + if (entry == null) { + return "null"; + } + return logKey(entry.getKey()); + } + + /** Removes newlines from the notification key to prettify apps that have these in the tag */ + public static String logKey(String key) { + if (key == null) { + return "null"; + } + return key.replace("\n", ""); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index bf81ea5c264c..2a2cc81c3223 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -46,6 +46,7 @@ import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.UserIdInt; import android.app.Notification; +import android.app.NotificationChannel; import android.os.Handler; import android.os.RemoteException; import android.os.Trace; @@ -56,7 +57,6 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.Pair; -import android.util.Slog; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -73,6 +73,7 @@ import com.android.systemui.statusbar.notification.collection.coalescer.Coalesce import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; import com.android.systemui.statusbar.notification.collection.notifcollection.BindEntryEvent; +import com.android.systemui.statusbar.notification.collection.notifcollection.ChannelChangedEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.CleanUpEntryEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; @@ -425,6 +426,16 @@ public class NotifCollection implements Dumpable { dispatchEventsAndRebuildList(); } + private void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + Assert.isMainThread(); + mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType)); + dispatchEventsAndRebuildList(); + } + private void onNotificationsInitialized() { mInitializedTimestamp = mClock.uptimeMillis(); } @@ -622,7 +633,7 @@ public class NotifCollection implements Dumpable { entry.mLifetimeExtenders.clear(); mAmDispatchingToOtherCode = true; for (NotifLifetimeExtender extender : mLifetimeExtenders) { - if (extender.shouldExtendLifetime(entry, entry.mCancellationReason)) { + if (extender.maybeExtendLifetime(entry, entry.mCancellationReason)) { mLogger.logLifetimeExtended(entry.getKey(), extender); entry.mLifetimeExtenders.add(extender); } @@ -756,6 +767,7 @@ public class NotifCollection implements Dumpable { && !entry.getSbn().getNotification().isGroupSummary() && !hasFlag(entry, Notification.FLAG_ONGOING_EVENT) && !hasFlag(entry, Notification.FLAG_BUBBLE) + && !hasFlag(entry, Notification.FLAG_NO_CLEAR) && entry.getDismissState() != DISMISSED; } @@ -835,6 +847,19 @@ public class NotifCollection implements Dumpable { } @Override + public void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + NotifCollection.this.onNotificationChannelModified( + pkgName, + user, + channel, + modificationType); + } + + @Override public void onNotificationsInitialized() { NotifCollection.this.onNotificationsInitialized(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt index dbecf1cc28d7..ac00581c7c8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt @@ -84,7 +84,7 @@ class GutsCoordinator @Inject constructor( onEndLifetimeExtensionCallback = callback } - override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { val isShowingGuts = isCurrentlyShowingGuts(entry) if (isShowingGuts) { notifsExtendingLifetime.add(entry.key) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java deleted file mode 100644 index f9f0b9da8778..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator; - -import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain; - -import android.util.ArraySet; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.render.NodeController; -import com.android.systemui.statusbar.notification.dagger.IncomingHeader; -import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import javax.inject.Inject; - -/** - * Coordinates heads up notification (HUN) interactions with the notification pipeline based on - * the HUN state reported by the {@link HeadsUpManager}. In this class we only consider one - * notification, in particular the {@link HeadsUpManager#getTopEntry()}, to be HeadsUpping at a - * time even though other notifications may be queued to heads up next. - * - * The current HUN, but not HUNs that are queued to heads up, will be: - * - Lifetime extended until it's no longer heads upping. - * - Promoted out of its group if it's a child of a group. - * - In the HeadsUpCoordinatorSection. Ordering is configured in {@link NotifCoordinators}. - * - Removed from HeadsUpManager if it's removed from the NotificationCollection. - * - * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs. - */ -@CoordinatorScope -public class HeadsUpCoordinator implements Coordinator { - private static final String TAG = "HeadsUpCoordinator"; - - private final HeadsUpManager mHeadsUpManager; - private final HeadsUpViewBinder mHeadsUpViewBinder; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; - private final NotificationRemoteInputManager mRemoteInputManager; - private final NodeController mIncomingHeaderController; - private final DelayableExecutor mExecutor; - - private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; - // notifs we've extended the lifetime for - private final ArraySet<NotificationEntry> mNotifsExtendingLifetime = new ArraySet<>(); - - @Inject - public HeadsUpCoordinator( - HeadsUpManager headsUpManager, - HeadsUpViewBinder headsUpViewBinder, - NotificationInterruptStateProvider notificationInterruptStateProvider, - NotificationRemoteInputManager remoteInputManager, - @IncomingHeader NodeController incomingHeaderController, - @Main DelayableExecutor executor) { - mHeadsUpManager = headsUpManager; - mHeadsUpViewBinder = headsUpViewBinder; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; - mRemoteInputManager = remoteInputManager; - mIncomingHeaderController = incomingHeaderController; - mExecutor = executor; - } - - @Override - public void attach(NotifPipeline pipeline) { - mHeadsUpManager.addListener(mOnHeadsUpChangedListener); - pipeline.addCollectionListener(mNotifCollectionListener); - pipeline.addPromoter(mNotifPromoter); - pipeline.addNotificationLifetimeExtender(mLifetimeExtender); - } - - public NotifSectioner getSectioner() { - return mNotifSectioner; - } - - private void onHeadsUpViewBound(NotificationEntry entry) { - mHeadsUpManager.showNotification(entry); - } - - private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { - /** - * Notification was just added and if it should heads up, bind the view and then show it. - */ - @Override - public void onEntryAdded(NotificationEntry entry) { - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpViewBinder.bindHeadsUpView( - entry, - HeadsUpCoordinator.this::onHeadsUpViewBound); - } - } - - /** - * Notification could've updated to be heads up or not heads up. Even if it did update to - * heads up, if the notification specified that it only wants to alert once, don't heads - * up again. - */ - @Override - public void onEntryUpdated(NotificationEntry entry) { - boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification()); - // includes check for whether this notification should be filtered: - boolean shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry); - final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey()); - if (wasHeadsUp) { - if (shouldHeadsUp) { - mHeadsUpManager.updateNotification(entry.getKey(), hunAgain); - } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) { - // We don't want this to be interrupting anymore, let's remove it - mHeadsUpManager.removeNotification( - entry.getKey(), false /* removeImmediately */); - } - } else if (shouldHeadsUp && hunAgain) { - // This notification was updated to be heads up, show it! - mHeadsUpViewBinder.bindHeadsUpView( - entry, - HeadsUpCoordinator.this::onHeadsUpViewBound); - } - } - - /** - * Stop alerting HUNs that are removed from the notification collection - */ - @Override - public void onEntryRemoved(NotificationEntry entry, int reason) { - final String entryKey = entry.getKey(); - if (mHeadsUpManager.isAlerting(entryKey)) { - boolean removeImmediatelyForRemoteInput = - mRemoteInputManager.isSpinning(entryKey) - && !FORCE_REMOTE_INPUT_HISTORY; - mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput); - } - } - - @Override - public void onEntryCleanUp(NotificationEntry entry) { - mHeadsUpViewBinder.abortBindCallback(entry); - } - }; - - private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() { - @Override - public @NonNull String getName() { - return TAG; - } - - @Override - public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) { - mEndLifetimeExtension = callback; - } - - @Override - public boolean shouldExtendLifetime(@NonNull NotificationEntry entry, int reason) { - boolean extend = !mHeadsUpManager.canRemoveImmediately(entry.getKey()); - if (extend) { - if (isSticky(entry)) { - long removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.getKey()); - mExecutor.executeDelayed(() -> { - if (mNotifsExtendingLifetime.contains(entry) - && mHeadsUpManager.canRemoveImmediately(entry.getKey())) { - mHeadsUpManager.removeNotification( - entry.getKey(), /* releaseImmediately */ true); - } - }, removeAfterMillis); - } else { - // remove as early as possible - mExecutor.execute( - () -> mHeadsUpManager.removeNotification( - entry.getKey(), /* releaseImmediately */ false)); - } - mNotifsExtendingLifetime.add(entry); - } - return extend; - } - - @Override - public void cancelLifetimeExtension(@NonNull NotificationEntry entry) { - mNotifsExtendingLifetime.remove(entry); - } - }; - - private final NotifPromoter mNotifPromoter = new NotifPromoter(TAG) { - @Override - public boolean shouldPromoteToTopLevel(NotificationEntry entry) { - return isCurrentlyShowingHun(entry); - } - }; - - private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp", - NotificationPriorityBucketKt.BUCKET_HEADS_UP) { - @Override - public boolean isInSection(ListEntry entry) { - return isCurrentlyShowingHun(entry); - } - - @Nullable - @Override - public NodeController getHeaderNodeController() { - // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController - if (RankingCoordinator.SHOW_ALL_SECTIONS) { - return mIncomingHeaderController; - } - return null; - } - }; - - private final OnHeadsUpChangedListener mOnHeadsUpChangedListener = - new OnHeadsUpChangedListener() { - @Override - public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - if (!isHeadsUp) { - mHeadsUpViewBinder.unbindHeadsUpView(entry); - endNotifLifetimeExtensionIfExtended(entry); - } - } - }; - - private boolean isSticky(NotificationEntry entry) { - return mHeadsUpManager.isSticky(entry.getKey()); - } - - private boolean isCurrentlyShowingHun(ListEntry entry) { - return mHeadsUpManager.isAlerting(entry.getKey()); - } - - private void endNotifLifetimeExtensionIfExtended(NotificationEntry entry) { - if (mNotifsExtendingLifetime.remove(entry)) { - mEndLifetimeExtension.onEndLifetimeExtension(mLifetimeExtender, entry); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt new file mode 100644 index 000000000000..b84b38233073 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.util.ArraySet +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.dagger.IncomingHeader +import com.android.systemui.statusbar.notification.interruption.HeadsUpController +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider +import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.concurrency.DelayableExecutor +import javax.inject.Inject + +/** + * Coordinates heads up notification (HUN) interactions with the notification pipeline based on + * the HUN state reported by the [HeadsUpManager]. In this class we only consider one + * notification, in particular the [HeadsUpManager.getTopEntry], to be HeadsUpping at a + * time even though other notifications may be queued to heads up next. + * + * The current HUN, but not HUNs that are queued to heads up, will be: + * - Lifetime extended until it's no longer heads upping. + * - Promoted out of its group if it's a child of a group. + * - In the HeadsUpCoordinatorSection. Ordering is configured in [NotifCoordinators]. + * - Removed from HeadsUpManager if it's removed from the NotificationCollection. + * + * Note: The inflation callback in [PreparationCoordinator] handles showing HUNs. + */ +@CoordinatorScope +class HeadsUpCoordinator @Inject constructor( + private val mHeadsUpManager: HeadsUpManager, + private val mHeadsUpViewBinder: HeadsUpViewBinder, + private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider, + private val mRemoteInputManager: NotificationRemoteInputManager, + @IncomingHeader private val mIncomingHeaderController: NodeController, + @Main private val mExecutor: DelayableExecutor +) : Coordinator { + private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null + + // notifs we've extended the lifetime for + private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>() + + override fun attach(pipeline: NotifPipeline) { + mHeadsUpManager.addListener(mOnHeadsUpChangedListener) + pipeline.addCollectionListener(mNotifCollectionListener) + pipeline.addPromoter(mNotifPromoter) + pipeline.addNotificationLifetimeExtender(mLifetimeExtender) + } + + private fun onHeadsUpViewBound(entry: NotificationEntry) { + mHeadsUpManager.showNotification(entry) + } + + private val mNotifCollectionListener = object : NotifCollectionListener { + /** + * Notification was just added and if it should heads up, bind the view and then show it. + */ + override fun onEntryAdded(entry: NotificationEntry) { + if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) } + } + } + + /** + * Notification could've updated to be heads up or not heads up. Even if it did update to + * heads up, if the notification specified that it only wants to alert once, don't heads + * up again. + */ + override fun onEntryUpdated(entry: NotificationEntry) { + val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification) + // includes check for whether this notification should be filtered: + val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry) + val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key) + if (wasHeadsUp) { + if (shouldHeadsUp) { + mHeadsUpManager.updateNotification(entry.key, hunAgain) + } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.key)) { + // We don't want this to be interrupting anymore, let's remove it + mHeadsUpManager.removeNotification( + entry.key, false /* removeImmediately */ + ) + } + } else if (shouldHeadsUp && hunAgain) { + // This notification was updated to be heads up, show it! + mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) } + } + } + + /** + * Stop alerting HUNs that are removed from the notification collection + */ + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + val entryKey = entry.key + if (mHeadsUpManager.isAlerting(entryKey)) { + val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) && + !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY) + mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput) + } + } + + override fun onEntryCleanUp(entry: NotificationEntry) { + mHeadsUpViewBinder.abortBindCallback(entry) + } + } + + private val mLifetimeExtender = object : NotifLifetimeExtender { + override fun getName() = TAG + + override fun setCallback(callback: OnEndLifetimeExtensionCallback) { + mEndLifetimeExtension = callback + } + + override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + if (mHeadsUpManager.canRemoveImmediately(entry.key)) { + return false + } + if (isSticky(entry)) { + val removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key) + mExecutor.executeDelayed({ + val canStillRemove = mHeadsUpManager.canRemoveImmediately(entry.key) + if (mNotifsExtendingLifetime.contains(entry) && canStillRemove) { + mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ true) + } + }, removeAfterMillis) + } else { + mExecutor.execute { + mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ false) + } + } + mNotifsExtendingLifetime.add(entry) + return true + } + + override fun cancelLifetimeExtension(entry: NotificationEntry) { + mNotifsExtendingLifetime.remove(entry) + } + } + + private val mNotifPromoter = object : NotifPromoter(TAG) { + override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean = + isCurrentlyShowingHun(entry) + } + + val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) { + override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry) + + override fun getHeaderNodeController(): NodeController? = + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController + if (RankingCoordinator.SHOW_ALL_SECTIONS) mIncomingHeaderController else null + } + + private val mOnHeadsUpChangedListener = object : OnHeadsUpChangedListener { + override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { + if (!isHeadsUp) { + mHeadsUpViewBinder.unbindHeadsUpView(entry) + endNotifLifetimeExtensionIfExtended(entry) + } + } + } + + private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key) + + private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key) + + private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) { + if (mNotifsExtendingLifetime.remove(entry)) { + mEndLifetimeExtension?.onEndLifetimeExtension(mLifetimeExtender, entry) + } + } + + companion object { + private const val TAG = "HeadsUpCoordinator" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index ec4e0391c171..195f3672dc56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -31,6 +31,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -98,6 +99,7 @@ public class PreparationCoordinator implements Coordinator { /** How long we can delay a group while waiting for all children to inflate */ private final long mMaxGroupInflationDelay; + private final ConversationNotificationManager mConversationManager; @Inject public PreparationCoordinator( @@ -106,7 +108,8 @@ public class PreparationCoordinator implements Coordinator { NotifInflationErrorManager errorManager, NotifViewBarn viewBarn, NotifUiAdjustmentProvider adjustmentProvider, - IStatusBarService service) { + IStatusBarService service, + ConversationNotificationManager conversationManager) { this( logger, notifInflater, @@ -114,6 +117,7 @@ public class PreparationCoordinator implements Coordinator { viewBarn, adjustmentProvider, service, + conversationManager, CHILD_BIND_CUTOFF, MAX_GROUP_INFLATION_DELAY); } @@ -126,6 +130,7 @@ public class PreparationCoordinator implements Coordinator { NotifViewBarn viewBarn, NotifUiAdjustmentProvider adjustmentProvider, IStatusBarService service, + ConversationNotificationManager conversationManager, int childBindCutoff, long maxGroupInflationDelay) { mLogger = logger; @@ -136,6 +141,7 @@ public class PreparationCoordinator implements Coordinator { mStatusBarService = service; mChildBindCutoff = childBindCutoff; mMaxGroupInflationDelay = maxGroupInflationDelay; + mConversationManager = conversationManager; } @Override @@ -363,6 +369,9 @@ public class PreparationCoordinator implements Coordinator { mInflatingNotifs.remove(entry); mViewBarn.registerViewForEntry(entry, controller); mInflationStates.put(entry, STATE_INFLATED); + // NOTE: under the new pipeline there's no way to register for an inflation callback, + // so this one method is called by the PreparationCoordinator directly. + mConversationManager.onEntryViewBound(entry); mNotifInflatingFilter.invalidateList(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java index 5993f1dee3a7..cd2affead92a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java @@ -16,6 +16,10 @@ package com.android.systemui.statusbar.notification.collection.legacy; +import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; + +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -34,9 +38,9 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.util.Compile; import com.android.wm.shell.bubbles.Bubbles; import java.io.FileDescriptor; @@ -49,6 +53,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.TreeSet; +import java.util.function.Function; import javax.inject.Inject; @@ -69,8 +74,8 @@ public class NotificationGroupManagerLegacy implements Dumpable { private static final String TAG = "NotifGroupManager"; - private static final boolean DEBUG = StatusBar.DEBUG; - private static final boolean SPEW = StatusBar.SPEW; + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); + private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); /** * The maximum amount of time (in ms) between the posting of notifications that can be * considered part of the same update batch. @@ -79,10 +84,9 @@ public class NotificationGroupManagerLegacy implements private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>(); private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners = new ArraySet<>(); - private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>(); private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier; private final Optional<Bubbles> mBubblesOptional; - private final EventBuffer mEventBuffer = new EventBuffer(); + private final GroupEventDispatcher mEventDispatcher = new GroupEventDispatcher(mGroupMap::get); private int mBarState = -1; private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>(); private HeadsUpManager mHeadsUpManager; @@ -105,7 +109,7 @@ public class NotificationGroupManagerLegacy implements * Add a listener for changes to groups. */ public void registerGroupChangeListener(OnGroupChangeListener listener) { - mGroupChangeListeners.add(listener); + mEventDispatcher.registerGroupChangeListener(listener); } @Override @@ -156,13 +160,15 @@ public class NotificationGroupManagerLegacy implements */ public void onEntryRemoved(NotificationEntry removed) { if (SPEW) { - Log.d(TAG, "onEntryRemoved: entry=" + removed); + Log.d(TAG, "onEntryRemoved: entry=" + logKey(removed)); } + mEventDispatcher.openBufferScope(); onEntryRemovedInternal(removed, removed.getSbn()); StatusBarNotification oldSbn = mIsolatedEntries.remove(removed.getKey()); if (oldSbn != null) { updateSuppression(mGroupMap.get(oldSbn.getGroupKey())); } + mEventDispatcher.closeBufferScope(); } /** @@ -190,7 +196,8 @@ public class NotificationGroupManagerLegacy implements return; } if (SPEW) { - Log.d(TAG, "onEntryRemovedInternal: entry=" + removed + " group=" + group.groupKey); + Log.d(TAG, "onEntryRemovedInternal: entry=" + logKey(removed) + + " group=" + logGroupKey(group)); } if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) { group.children.remove(removed.getKey()); @@ -201,9 +208,7 @@ public class NotificationGroupManagerLegacy implements if (group.children.isEmpty()) { if (group.summary == null) { mGroupMap.remove(groupKey); - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupRemoved(group, groupKey); - } + mEventDispatcher.notifyGroupRemoved(group); } } } @@ -213,10 +218,12 @@ public class NotificationGroupManagerLegacy implements */ public void onEntryAdded(final NotificationEntry added) { if (SPEW) { - Log.d(TAG, "onEntryAdded: entry=" + added); + Log.d(TAG, "onEntryAdded: entry=" + logKey(added)); } + mEventDispatcher.openBufferScope(); updateIsolation(added); onEntryAddedInternal(added); + mEventDispatcher.closeBufferScope(); } private void onEntryAddedInternal(final NotificationEntry added) { @@ -230,19 +237,17 @@ public class NotificationGroupManagerLegacy implements if (group == null) { group = new NotificationGroup(groupKey); mGroupMap.put(groupKey, group); - - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupCreated(group, groupKey); - } + mEventDispatcher.notifyGroupCreated(group); } if (SPEW) { - Log.d(TAG, "onEntryAddedInternal: entry=" + added + " group=" + group.groupKey); + Log.d(TAG, "onEntryAddedInternal: entry=" + logKey(added) + + " group=" + logGroupKey(group)); } if (isGroupChild) { NotificationEntry existing = group.children.get(added.getKey()); if (existing != null && existing != added) { Throwable existingThrowable = existing.getDebugThrowable(); - Log.wtf(TAG, "Inconsistent entries found with the same key " + added.getKey() + Log.wtf(TAG, "Inconsistent entries found with the same key " + logKey(added) + "existing removed: " + existing.isRowRemoved() + (existingThrowable != null ? Log.getStackTraceString(existingThrowable) + "\n" : "") @@ -262,9 +267,7 @@ public class NotificationGroupManagerLegacy implements for (NotificationEntry child : childrenCopy) { onEntryBecomingChild(child); } - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupCreatedFromChildren(group); - } + mEventDispatcher.notifyGroupsChanged(); } } } @@ -323,30 +326,27 @@ public class NotificationGroupManagerLegacy implements boolean alertOverrideChanged = prevAlertOverride != group.alertOverride; boolean suppressionChanged = prevSuppressed != group.suppressed; if (alertOverrideChanged || suppressionChanged) { - if (DEBUG && alertOverrideChanged) { - Log.d(TAG, "updateSuppression: alertOverride was=" + prevAlertOverride - + " now=" + group.alertOverride + " group:\n" + group); - } - if (DEBUG && suppressionChanged) { - Log.d(TAG, - "updateSuppression: suppressed changed to " + group.suppressed - + " group:\n" + group); - } - if (!mIsUpdatingUnchangedGroup) { + if (DEBUG) { + Log.d(TAG, "updateSuppression:" + + " willNotifyListeners=" + !mIsUpdatingUnchangedGroup + + " changes for group:\n" + group); if (alertOverrideChanged) { - mEventBuffer.notifyAlertOverrideChanged(group, prevAlertOverride); + Log.d(TAG, "updateSuppression: alertOverride was=" + logKey(prevAlertOverride) + + " now=" + logKey(group.alertOverride)); } if (suppressionChanged) { - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupSuppressionChanged(group, group.suppressed); - } - } - mEventBuffer.notifyGroupsChanged(); - } else { - if (DEBUG) { - Log.d(TAG, group + " did not notify listeners of above change(s)"); + Log.d(TAG, "updateSuppression: suppressed changed to " + group.suppressed); } } + if (alertOverrideChanged) { + mEventDispatcher.notifyAlertOverrideChanged(group, prevAlertOverride); + } + if (suppressionChanged) { + mEventDispatcher.notifySuppressedChanged(group); + } + if (!mIsUpdatingUnchangedGroup) { + mEventDispatcher.notifyGroupsChanged(); + } } } @@ -369,13 +369,15 @@ public class NotificationGroupManagerLegacy implements // but which should be alerting (because priority conversations are isolated), find it. if (group == null || group.summary == null) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary"); + Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary" + + " group=" + logGroupKey(group)); } return null; } if (isIsolated(group.summary.getKey())) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: isolated group"); + Log.d(TAG, "getPriorityConversationAlertOverride: isolated group" + + " group=" + logGroupKey(group)); } return null; } @@ -384,9 +386,10 @@ public class NotificationGroupManagerLegacy implements // * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY // * Only necessary when at least one notification in the group is on a priority channel if (group.summary.getSbn().getNotification().getGroupAlertBehavior() - != Notification.GROUP_ALERT_SUMMARY) { + == Notification.GROUP_ALERT_CHILDREN) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: summary != GROUP_ALERT_SUMMARY"); + Log.d(TAG, "getPriorityConversationAlertOverride: summary == GROUP_ALERT_CHILDREN" + + " group=" + logGroupKey(group)); } return null; } @@ -396,7 +399,8 @@ public class NotificationGroupManagerLegacy implements HashMap<String, NotificationEntry> children = getImportantConversations(group); if (children == null || children.isEmpty()) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations"); + Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations" + + " group=" + logGroupKey(group)); } return null; } @@ -408,8 +412,8 @@ public class NotificationGroupManagerLegacy implements if (child.getSbn().getNotification().getGroupAlertBehavior() != Notification.GROUP_ALERT_SUMMARY) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: " - + "child != GROUP_ALERT_SUMMARY"); + Log.d(TAG, "getPriorityConversationAlertOverride: child != GROUP_ALERT_SUMMARY" + + " group=" + logGroupKey(group)); } return null; } @@ -450,13 +454,16 @@ public class NotificationGroupManagerLegacy implements } if (newestChild != null && importantChildKeys.contains(newestChild.getKey())) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: result=" + newestChild); + Log.d(TAG, "getPriorityConversationAlertOverride:" + + " result=" + logKey(newestChild) + + " group=" + logGroupKey(group)); } return newestChild; } if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: result=null, newestChild=" - + newestChild); + Log.d(TAG, "getPriorityConversationAlertOverride:" + + " result=null newestChild=" + logKey(newestChild) + + " group=" + logGroupKey(group)); } return null; } @@ -500,7 +507,7 @@ public class NotificationGroupManagerLegacy implements */ public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) { if (SPEW) { - Log.d(TAG, "onEntryUpdated: entry=" + entry); + Log.d(TAG, "onEntryUpdated: entry=" + logKey(entry)); } onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(), oldNotification.getNotification().isGroupSummary()); @@ -519,6 +526,7 @@ public class NotificationGroupManagerLegacy implements boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey); boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary); boolean isGroupChild = isGroupChild(entry.getSbn()); + mEventDispatcher.openBufferScope(); mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild; if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) { onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary); @@ -529,11 +537,14 @@ public class NotificationGroupManagerLegacy implements mIsolatedEntries.put(entry.getKey(), entry.getSbn()); if (groupKeysChanged) { updateSuppression(mGroupMap.get(oldGroupKey)); - updateSuppression(mGroupMap.get(newGroupKey)); } + // Always update the suppression of the group from which you're isolated, in case + // this entry was or now is the alertOverride for that group. + updateSuppression(mGroupMap.get(newGroupKey)); } else if (!wasGroupChild && isGroupChild) { onEntryBecomingChild(entry); } + mEventDispatcher.closeBufferScope(); } /** @@ -796,7 +807,7 @@ public class NotificationGroupManagerLegacy implements */ private void isolateNotification(NotificationEntry entry) { if (SPEW) { - Log.d(TAG, "isolateNotification: entry=" + entry); + Log.d(TAG, "isolateNotification: entry=" + logKey(entry)); } // We will be isolated now, so lets update the groups onEntryRemovedInternal(entry, entry.getSbn()); @@ -809,9 +820,7 @@ public class NotificationGroupManagerLegacy implements // When the notification gets added afterwards it is already isolated and therefore // it doesn't lead to an update. updateSuppression(mGroupMap.get(entry.getSbn().getGroupKey())); - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupsChanged(); - } + mEventDispatcher.notifyGroupsChanged(); } /** @@ -825,7 +834,7 @@ public class NotificationGroupManagerLegacy implements // listener may be unable to correctly determine the true state of the group. By delaying // the alertOverride change until after the add phase, we can ensure that listeners only // have to handle a consistent state. - mEventBuffer.startBuffering(); + mEventDispatcher.openBufferScope(); boolean isIsolated = isIsolated(entry.getSbn().getKey()); if (shouldIsolate(entry)) { if (!isIsolated) { @@ -834,7 +843,7 @@ public class NotificationGroupManagerLegacy implements } else if (isIsolated) { stopIsolatingNotification(entry); } - mEventBuffer.flushAndStopBuffering(); + mEventDispatcher.closeBufferScope(); } /** @@ -844,15 +853,13 @@ public class NotificationGroupManagerLegacy implements */ private void stopIsolatingNotification(NotificationEntry entry) { if (SPEW) { - Log.d(TAG, "stopIsolatingNotification: entry=" + entry); + Log.d(TAG, "stopIsolatingNotification: entry=" + logKey(entry)); } // not isolated anymore, we need to update the groups onEntryRemovedInternal(entry, entry.getSbn()); mIsolatedEntries.remove(entry.getKey()); onEntryAddedInternal(entry); - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupsChanged(); - } + mEventDispatcher.notifyGroupsChanged(); } private boolean isGroupNotFullyVisible(NotificationGroup notificationGroup) { @@ -872,11 +879,11 @@ public class NotificationGroupManagerLegacy implements pw.println("GroupManagerLegacy state:"); pw.println(" number of groups: " + mGroupMap.size()); for (Map.Entry<String, NotificationGroup> entry : mGroupMap.entrySet()) { - pw.println("\n key: " + entry.getKey()); pw.println(entry.getValue()); + pw.println("\n key: " + logKey(entry.getKey())); pw.println(entry.getValue()); } pw.println("\n isolated entries: " + mIsolatedEntries.size()); for (Map.Entry<String, StatusBarNotification> entry : mIsolatedEntries.entrySet()) { - pw.print(" "); pw.print(entry.getKey()); + pw.print(" "); pw.print(logKey(entry.getKey())); pw.print(", "); pw.println(entry.getValue()); } } @@ -886,6 +893,14 @@ public class NotificationGroupManagerLegacy implements setStatusBarState(newState); } + /** Get the group key, reformatted for logging, for the (optional) group */ + public static String logGroupKey(NotificationGroup group) { + if (group == null) { + return "null"; + } + return logKey(group.groupKey); + } + /** * A record of a notification being posted, containing the time of the post and the key of the * notification entry. These are stored in a TreeSet by the NotificationGroup and used to @@ -975,18 +990,35 @@ public class NotificationGroupManagerLegacy implements * When buffering, instead of notifying the listeners it will set internal state that will allow * it to notify listeners of those events later */ - private class EventBuffer { + static class GroupEventDispatcher { + private final Function<String, NotificationGroup> mGroupMapGetter; + private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>(); private final HashMap<String, NotificationEntry> mOldAlertOverrideByGroup = new HashMap<>(); - private boolean mIsBuffering = false; + private final HashMap<String, Boolean> mOldSuppressedByGroup = new HashMap<>(); + private int mBufferScopeDepth = 0; private boolean mDidGroupsChange = false; + GroupEventDispatcher(Function<String, NotificationGroup> groupMapGetter) { + mGroupMapGetter = requireNonNull(groupMapGetter); + } + + void registerGroupChangeListener(OnGroupChangeListener listener) { + mGroupChangeListeners.add(listener); + } + + private boolean isBuffering() { + return mBufferScopeDepth > 0; + } + void notifyAlertOverrideChanged(NotificationGroup group, NotificationEntry oldAlertOverride) { - if (mIsBuffering) { + if (isBuffering()) { // The value in this map is the override before the event. If there is an entry // already in the map, then we are effectively coalescing two events, which means // we need to preserve the original initial value. - mOldAlertOverrideByGroup.putIfAbsent(group.groupKey, oldAlertOverride); + if (!mOldAlertOverrideByGroup.containsKey(group.groupKey)) { + mOldAlertOverrideByGroup.put(group.groupKey, oldAlertOverride); + } } else { for (OnGroupChangeListener listener : mGroupChangeListeners) { listener.onGroupAlertOverrideChanged(group, oldAlertOverride, @@ -995,8 +1027,21 @@ public class NotificationGroupManagerLegacy implements } } + void notifySuppressedChanged(NotificationGroup group) { + if (isBuffering()) { + // The value in this map is the 'suppressed' before the event. If there is a value + // already in the map, then we are effectively coalescing two events, which means + // we need to preserve the original initial value. + mOldSuppressedByGroup.putIfAbsent(group.groupKey, !group.suppressed); + } else { + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupSuppressionChanged(group, group.suppressed); + } + } + } + void notifyGroupsChanged() { - if (mIsBuffering) { + if (isBuffering()) { mDidGroupsChange = true; } else { for (OnGroupChangeListener listener : mGroupChangeListeners) { @@ -1005,26 +1050,94 @@ public class NotificationGroupManagerLegacy implements } } - void startBuffering() { - mIsBuffering = true; + void notifyGroupCreated(NotificationGroup group) { + // NOTE: given how this event is used, it doesn't need to be buffered right now + final String groupKey = group.groupKey; + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupCreated(group, groupKey); + } + } + + void notifyGroupRemoved(NotificationGroup group) { + // NOTE: given how this event is used, it doesn't need to be buffered right now + final String groupKey = group.groupKey; + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupRemoved(group, groupKey); + } + } + + void openBufferScope() { + mBufferScopeDepth++; + if (SPEW) { + Log.d(TAG, "openBufferScope: scopeDepth=" + mBufferScopeDepth); + } + } + + void closeBufferScope() { + mBufferScopeDepth--; + if (SPEW) { + Log.d(TAG, "closeBufferScope: scopeDepth=" + mBufferScopeDepth); + } + // Flush buffered events if the last buffer scope has closed + if (!isBuffering()) { + flushBuffer(); + } } - void flushAndStopBuffering() { - // stop buffering so that we can call our own helpers - mIsBuffering = false; + private void flushBuffer() { + if (SPEW) { + Log.d(TAG, "flushBuffer: " + + " suppressed.size=" + mOldSuppressedByGroup.size() + + " alertOverride.size=" + mOldAlertOverrideByGroup.size() + + " mDidGroupsChange=" + mDidGroupsChange); + } + // alert all group suppressed changes for groups that were not removed + for (Map.Entry<String, Boolean> entry : mOldSuppressedByGroup.entrySet()) { + NotificationGroup group = mGroupMapGetter.apply(entry.getKey()); + if (group == null) { + // The group can be null if this suppressed changed before the group was + // permanently removed, meaning that there's no guarantee that listeners will + // that field clear. + if (SPEW) { + Log.d(TAG, "flushBuffer: suppressed:" + + " cannot report for removed group: " + logKey(entry.getKey())); + } + continue; + } + boolean oldSuppressed = entry.getValue(); + if (group.suppressed == oldSuppressed) { + // If the final suppressed equals the initial, it means we coalesced two + // events which undid the change, so we can drop it entirely. + if (SPEW) { + Log.d(TAG, "flushBuffer: suppressed:" + + " did not change for group: " + logKey(entry.getKey())); + } + continue; + } + notifySuppressedChanged(group); + } + mOldSuppressedByGroup.clear(); // alert all group alert override changes for groups that were not removed for (Map.Entry<String, NotificationEntry> entry : mOldAlertOverrideByGroup.entrySet()) { - NotificationGroup group = mGroupMap.get(entry.getKey()); + NotificationGroup group = mGroupMapGetter.apply(entry.getKey()); if (group == null) { // The group can be null if this alertOverride changed before the group was // permanently removed, meaning that there's no guarantee that listeners will // that field clear. + if (SPEW) { + Log.d(TAG, "flushBuffer: alertOverride:" + + " cannot report for removed group: " + entry.getKey()); + } continue; } NotificationEntry oldAlertOverride = entry.getValue(); if (group.alertOverride == oldAlertOverride) { // If the final alertOverride equals the initial, it means we coalesced two // events which undid the change, so we can drop it entirely. + if (SPEW) { + Log.d(TAG, "flushBuffer: alertOverride:" + + " did not change for group: " + logKey(entry.getKey())); + } continue; } notifyAlertOverrideChanged(group, oldAlertOverride); @@ -1086,14 +1199,6 @@ public class NotificationGroupManagerLegacy implements @Nullable NotificationEntry newAlertOverride) {} /** - * A group of children just received a summary notification and should therefore become - * children of it. - * - * @param group the group created - */ - default void onGroupCreatedFromChildren(NotificationGroup group) {} - - /** * The groups have changed. This can happen if the isolation of a child has changes or if a * group became suppressed / unsuppressed */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 68a346f817e1..9d56a8ede1cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; import android.annotation.NonNull; +import android.app.NotificationChannel; +import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -115,4 +117,20 @@ public interface NotifCollectionListener { */ default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) { } + + /** + * Called when a notification channel is modified, in response to + * {@link NotificationListenerService#onNotificationChannelModified}. + * + * @param pkgName the package the notification channel belongs to. + * @param user the user the notification channel belongs to. + * @param channel the channel being modified. + * @param modificationType the type of modification that occurred to the channel. + */ + default void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt index 179e95328442..e20f0e50af6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection +import android.app.NotificationChannel +import android.os.UserHandle import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.notification.collection.NotifCollection @@ -102,3 +104,14 @@ class RankingAppliedEvent() : NotifEvent() { listener.onRankingApplied() } } + +data class ChannelChangedEvent( + val pkgName: String, + val user: UserHandle, + val channel: NotificationChannel, + val modificationType: Int +) : NotifEvent() { + override fun dispatchToListener(listener: NotifCollectionListener) { + listener.onNotificationChannelModified(pkgName, user, channel, modificationType) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java index 2fe3bd63c2e6..70ad8a552b9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java @@ -45,7 +45,7 @@ public interface NotifLifetimeExtender { * called on all lifetime extenders even if earlier ones return true (in other words, multiple * lifetime extenders can be extending a notification at the same time). */ - boolean shouldExtendLifetime(@NonNull NotificationEntry entry, @CancellationReason int reason); + boolean maybeExtendLifetime(@NonNull NotificationEntry entry, @CancellationReason int reason); /** * Called by the NotifCollection to inform a lifetime extender that its extension of a notif diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt index 145c1e54d732..51dab7246b28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt @@ -73,7 +73,7 @@ abstract class SelfTrackingLifetimeExtender( final override fun getName(): String = name - final override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + final override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { val shouldExtend = queryShouldExtendLifetime(entry) if (debug) { Log.d(tag, "$name.shouldExtendLifetime(key=${entry.key}, reason=$reason)" + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt index d16d76ad2f9a..ab777de21e34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt @@ -77,7 +77,7 @@ class DebugModeFilterProvider @Inject constructor( if (needsInitialization) { val filter = IntentFilter().apply { addAction(ACTION_SET_NOTIF_DEBUG_MODE) } val permission = NOTIF_DEBUG_MODE_PERMISSION - context.registerReceiver(mReceiver, filter, permission, null) + context.registerReceiver(mReceiver, filter, permission, null, Context.RECEIVER_EXPORTED) Log.d(TAG, "Registered: $mReceiver") } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 433d5e19ed6b..2b4bc918e66e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.Compile; import java.util.ArrayList; import java.util.List; @@ -52,8 +53,8 @@ import javax.inject.Inject; @SysUISingleton public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider { private static final String TAG = "InterruptionStateProvider"; - private static final boolean DEBUG = true; //false; - private static final boolean DEBUG_HEADS_UP = true; + private static final boolean DEBUG = Compile.IS_DEBUG; + private static final boolean DEBUG_HEADS_UP = Compile.IS_DEBUG; private static final boolean ENABLE_HEADS_UP = true; private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 9e8200b063fe..dc3941332f0b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -50,6 +50,7 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.util.Compile; import java.util.Collection; import java.util.Collections; @@ -65,7 +66,7 @@ import javax.inject.Inject; */ public class NotificationLogger implements StateListener { private static final String TAG = "NotificationLogger"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); /** The minimum delay in ms between reports of notification visibility. */ private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 08a230b18eab..dbd22db333ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -115,6 +115,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; +import com.android.systemui.util.Compile; import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.wmshell.BubblesManager; @@ -136,11 +137,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView implements PluginListener<NotificationMenuRowPlugin>, SwipeableView, NotificationFadeAware.FadeOptimizedNotification { - private static final boolean DEBUG = false; + private static final String TAG = "ExpandableNotifRow"; + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); private static final int DEFAULT_DIVIDER_ALPHA = 0x29; private static final int COLORED_DIVIDER_ALPHA = 0x7B; private static final int MENU_VIEW_INDEX = 0; - private static final String TAG = "ExpandableNotifRow"; public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java index c0bafb7a0d16..4893490b3e5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java @@ -48,11 +48,12 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.util.Compile; public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsContent { private static final String TAG = "FeedbackInfo"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); private NotificationGuts mGutsContainer; private NotificationListenerService.Ranking mRanking; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java index ab78d197da0b..6abfee9818bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.logging.NotificationCounters; +import com.android.systemui.util.Compile; import java.util.Collections; import java.util.HashSet; @@ -43,8 +44,9 @@ import java.util.Set; */ public class NotificationBlockingHelperManager { /** Enables debug logging and always makes the blocking helper show up after a dismiss. */ - private static final boolean DEBUG = false; private static final String TAG = "BlockingHelper"; + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG_ALWAYS_SHOW = false; private final Context mContext; private final NotificationGutsManager mNotificationGutsManager; @@ -98,7 +100,7 @@ public class NotificationBlockingHelperManager { // - The dismissed row is a valid group (>1 or 0 children from the same channel) // or the only child in the group final NotificationEntry entry = row.getEntry(); - if ((entry.getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG) + if ((entry.getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG_ALWAYS_SHOW) && mIsShadeExpanded && !row.getIsNonblockable() && ((!row.isChildInGroup() || mGroupMembershipManager.isOnlyChildInGroup(entry)) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 4dec1f1f975c..9cb5dc5d4aa4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -61,6 +61,7 @@ import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt; import com.android.systemui.statusbar.policy.SmartReplyView; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; +import com.android.systemui.util.Compile; import com.android.systemui.wmshell.BubblesManager; import java.io.FileDescriptor; @@ -77,7 +78,7 @@ import java.util.List; public class NotificationContentView extends FrameLayout implements NotificationFadeAware { private static final String TAG = "NotificationContentView"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); public static final int VISIBLE_TYPE_CONTRACTED = 0; public static final int VISIBLE_TYPE_EXPANDED = 1; public static final int VISIBLE_TYPE_HEADSUP = 2; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 9d599cbe1caf..d0fb416bf96a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -57,9 +57,6 @@ import java.util.Map; public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener, ExpandableNotificationRow.LayoutListener { - private static final boolean DEBUG = false; - private static final String TAG = "swipe"; - // Notification must be swiped at least this fraction of a single menu item to show menu private static final float SWIPED_FAR_ENOUGH_MENU_FRACTION = 0.25f; private static final float SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION = 0.15f; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 915a85df679c..90f5179bc81d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -132,6 +132,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; + private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE); // Delay in milli-seconds before shade closes for clear all. private final int DELAY_BEFORE_SHADE_CLOSE = 200; @@ -3143,6 +3144,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable AnimationEvent event = new AnimationEvent(row, type); event.headsUpFromBottom = onBottom; mAnimationEvents.add(event); + if (SPEW) { + Log.v(TAG, "Generating HUN animation event: " + + " isHeadsUp=" + isHeadsUp + + " type=" + type + + " onBottom=" + onBottom + + " row=" + row.getEntry().getKey()); + } } mHeadsUpChangeAnimations.clear(); mAddedHeadsUpChildren.clear(); @@ -4677,7 +4685,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { - if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) { + final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed); + if (SPEW) { + Log.v(TAG, "generateHeadsUpAnimation:" + + " willAdd=" + add + + " isHeadsUp=" + isHeadsUp + + " row=" + row.getEntry().getKey()); + } + if (add) { + // If we're hiding a HUN we just started showing THIS FRAME, then remove that event, + // and do not add the disappear event either. + if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) { + if (SPEW) { + Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled"); + } + return; + } mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); mNeedsAnimation = true; if (!mIsExpanded && !mWillExpand && !isHeadsUp) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index ff75eef80ac8..5833ec286983 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -93,7 +93,6 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; -import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; @@ -127,6 +126,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; +import com.android.systemui.util.Compile; import java.util.ArrayList; import java.util.List; @@ -144,7 +144,7 @@ import kotlin.Unit; @StatusBarComponent.StatusBarScope public class NotificationStackScrollLayoutController { private static final String TAG = "StackScrollerController"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); private final boolean mAllowLongPress; private final NotificationGutsManager mNotificationGutsManager; @@ -688,11 +688,6 @@ public class NotificationStackScrollLayoutController { (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded)); legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() { @Override - public void onGroupCreatedFromChildren(NotificationGroup group) { - mStatusBar.requestNotificationUpdate("onGroupCreatedFromChildren"); - } - - @Override public void onGroupsChanged() { mStatusBar.requestNotificationUpdate("onGroupsChanged"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index bfa4a245070c..dee1b334182a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.systemui.statusbar.phone; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index ec2d0364a3a6..571c10b3800f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -54,6 +54,7 @@ class KeyguardLiftController constructor( isListening = false updateListeningState() keyguardUpdateMonitor.requestFaceAuth(true) + keyguardUpdateMonitor.requestActiveUnlock() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt index 4de78f5d6190..868efa027f40 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt @@ -34,7 +34,7 @@ class LSShadeTransitionLogger @Inject constructor( private val displayMetrics: DisplayMetrics ) { fun logUnSuccessfulDragDown(startingChild: View?) { - val entry = (startingChild as ExpandableNotificationRow?)?.entry + val entry = (startingChild as? ExpandableNotificationRow)?.entry buffer.log(TAG, LogLevel.INFO, { str1 = entry?.key ?: "no entry" }, { @@ -49,7 +49,7 @@ class LSShadeTransitionLogger @Inject constructor( } fun logDragDownStarted(startingChild: ExpandableView?) { - val entry = (startingChild as ExpandableNotificationRow?)?.entry + val entry = (startingChild as? ExpandableNotificationRow)?.entry buffer.log(TAG, LogLevel.INFO, { str1 = entry?.key ?: "no entry" }, { @@ -58,7 +58,7 @@ class LSShadeTransitionLogger @Inject constructor( } fun logDraggedDownLockDownShade(startingChild: View?) { - val entry = (startingChild as ExpandableNotificationRow?)?.entry + val entry = (startingChild as? ExpandableNotificationRow)?.entry buffer.log(TAG, LogLevel.INFO, { str1 = entry?.key ?: "no entry" }, { @@ -67,7 +67,7 @@ class LSShadeTransitionLogger @Inject constructor( } fun logDraggedDown(startingChild: View?, dragLengthY: Int) { - val entry = (startingChild as ExpandableNotificationRow?)?.entry + val entry = (startingChild as? ExpandableNotificationRow)?.entry buffer.log(TAG, LogLevel.INFO, { str1 = entry?.key ?: "no entry" }, { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 9787a9446019..8bababfb9d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; +import static com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.logGroupKey; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -39,6 +42,7 @@ import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.util.Compile; import java.util.ArrayList; import java.util.List; @@ -54,8 +58,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private static final long ALERT_TRANSFER_TIMEOUT = 300; private static final String TAG = "NotifGroupAlertTransfer"; - private static final boolean DEBUG = StatusBar.DEBUG; - private static final boolean SPEW = StatusBar.SPEW; + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); + private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); /** * The list of entries containing group alert metadata for each group. Keyed by group key. @@ -147,7 +151,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Override public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) { if (DEBUG) { - Log.d(TAG, "!! onGroupSuppressionChanged: group.summary=" + group.summary + Log.d(TAG, "!! onGroupSuppressionChanged:" + + " group=" + logGroupKey(group) + + " group.summary=" + logKey(group.summary) + " suppressed=" + suppressed); } NotificationEntry oldAlertOverride = group.alertOverride; @@ -159,9 +165,11 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Nullable NotificationEntry oldAlertOverride, @Nullable NotificationEntry newAlertOverride) { if (DEBUG) { - Log.d(TAG, "!! onGroupAlertOverrideChanged: group.summary=" + group.summary - + " oldAlertOverride=" + oldAlertOverride - + " newAlertOverride=" + newAlertOverride); + Log.d(TAG, "!! onGroupAlertOverrideChanged:" + + " group=" + logGroupKey(group) + + " group.summary=" + logKey(group.summary) + + " oldAlertOverride=" + logKey(oldAlertOverride) + + " newAlertOverride=" + logKey(newAlertOverride)); } onGroupChanged(group, oldAlertOverride); } @@ -207,7 +215,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { if (DEBUG) { - Log.d(TAG, "!! onHeadsUpStateChanged: entry=" + entry + " isHeadsUp=" + isHeadsUp); + Log.d(TAG, "!! onHeadsUpStateChanged:" + + " entry=" + logKey(entry) + + " isHeadsUp=" + isHeadsUp); } if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) { // a group summary is alerting; trigger the forward transfer checks @@ -239,6 +249,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) { handleSuppressedSummaryAlerted(summary, oldAlertOverride); } + if (DEBUG) { + Log.d(TAG, "checkForForwardAlertTransfer: done"); + } } private final NotificationEntryListener mNotificationEntryListener = @@ -248,7 +261,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Override public void onPendingEntryAdded(NotificationEntry entry) { if (DEBUG) { - Log.d(TAG, "!! onPendingEntryAdded: entry=" + entry); + Log.d(TAG, "!! onPendingEntryAdded: entry=" + logKey(entry)); } String groupKey = mGroupManager.getGroupKey(entry.getSbn()); GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey); @@ -344,7 +357,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary, NotificationEntry oldAlertOverride) { if (DEBUG) { - Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + summary); + Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + logKey(summary)); } GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn())); @@ -406,7 +419,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis */ private void handleOverriddenSummaryAlerted(NotificationEntry summary) { if (DEBUG) { - Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + summary); + Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + logKey(summary)); } GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn())); @@ -480,7 +493,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime(); } if (DEBUG) { - Log.d(TAG, "transferAlertState: fromEntry=" + fromEntry + " toEntry=" + toEntry); + Log.d(TAG, "transferAlertState:" + + " fromEntry=" + logKey(fromEntry) + + " toEntry=" + logKey(toEntry)); } transferAlertState(fromEntry, toEntry); } @@ -582,7 +597,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis final RowContentBindParams params = mRowContentBindStage.getStageParams(entry); if ((params.getContentViews() & contentFlag) == 0) { if (DEBUG) { - Log.d(TAG, "alertNotificationWhenPossible: async requestRebind entry=" + entry); + Log.d(TAG, "alertNotificationWhenPossible:" + + " async requestRebind entry=" + logKey(entry)); } mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry)); params.requireContentViews(contentFlag); @@ -592,6 +608,10 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis if (alertInfo.isStillValid()) { alertNotificationWhenPossible(entry); } else { + if (DEBUG) { + Log.d(TAG, "alertNotificationWhenPossible:" + + " markContentViewsFreeable entry=" + logKey(entry)); + } // The transfer is no longer valid. Free the content. mRowContentBindStage.getStageParams(entry).markContentViewsFreeable( contentFlag); @@ -603,12 +623,14 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } if (mHeadsUpManager.isAlerting(entry.getKey())) { if (DEBUG) { - Log.d(TAG, "alertNotificationWhenPossible: continue alerting entry=" + entry); + Log.d(TAG, "alertNotificationWhenPossible:" + + " continue alerting entry=" + logKey(entry)); } mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */); } else { if (DEBUG) { - Log.d(TAG, "alertNotificationWhenPossible: start alerting entry=" + entry); + Log.d(TAG, "alertNotificationWhenPossible:" + + " start alerting entry=" + logKey(entry)); } mHeadsUpManager.showNotification(entry); } @@ -656,7 +678,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis // Notification is aborted due to the transfer being explicitly cancelled return false; } - if (mEntry.getSbn().getGroupKey() != mOriginalNotification.getGroupKey()) { + if (!mEntry.getSbn().getGroupKey().equals(mOriginalNotification.getGroupKey())) { // Groups have changed return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 4d625cfbfbf5..81e5b58aacea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -69,6 +69,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; +import android.os.Trace; import android.os.UserManager; import android.os.VibrationEffect; import android.provider.Settings; @@ -2438,7 +2439,7 @@ public class NotificationPanelViewController extends PanelViewController { mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction); mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); mSplitShadeHeaderController.setShadeExpanded(mQsVisible); - + mKeyguardStatusBarViewController.updateViewState(); if (mCommunalViewController != null) { mCommunalViewController.updateQsExpansion(qsExpansionFraction); @@ -4590,11 +4591,20 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onSmallestScreenWidthChanged() { + Trace.beginSection("onSmallestScreenWidthChanged"); if (DEBUG) Log.d(TAG, "onSmallestScreenWidthChanged"); // Can affect multi-user switcher visibility as it depends on screen size by default: // it is enabled only for devices with large screens (see config_keyguardUserSwitcher) - reInflateViews(); + boolean prevKeyguardUserSwitcherEnabled = mKeyguardUserSwitcherEnabled; + boolean prevKeyguardQsUserSwitchEnabled = mKeyguardQsUserSwitchEnabled; + updateUserSwitcherFlags(); + if (prevKeyguardUserSwitcherEnabled != mKeyguardUserSwitcherEnabled + || prevKeyguardQsUserSwitchEnabled != mKeyguardQsUserSwitchEnabled) { + reInflateViews(); + } + + Trace.endSection(); } @Override @@ -4733,8 +4743,6 @@ public class NotificationPanelViewController extends PanelViewController { public interface NotificationPanelViewStateProvider { /** Returns the expanded height of the panel view. */ float getPanelViewExpandedHeight(); - /** Returns the fraction of QS that's expanded. */ - float getQsExpansionFraction(); /** * Returns true if heads up should be visible. * @@ -4755,18 +4763,15 @@ public class NotificationPanelViewController extends PanelViewController { } @Override - public float getQsExpansionFraction() { - return computeQsExpansionFraction(); - } - - @Override public boolean shouldHeadsUpBeVisible() { return mHeadsUpAppearanceController.shouldBeVisible(); } @Override public float getLockscreenShadeDragProgress() { - return mLockscreenShadeTransitionController.getQSDragProgress(); + return mTransitioningToFullShadeProgress > 0 + ? mLockscreenShadeTransitionController.getQSDragProgress() + : computeQsExpansionFraction(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index a23e726e0b6b..4249d767a72c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -182,6 +182,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private GradientColors mColors; private boolean mNeedsDrawableColorUpdate; + private float mAdditionalScrimBehindAlphaKeyguard = 0f; + // Combined scrim behind keyguard alpha of default scrim + additional scrim + // (if wallpaper dimming is applied). private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA; private final float mDefaultScrimAlpha; @@ -437,7 +440,35 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump return mState; } - protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) { + /** + * Sets the additional scrim behind alpha keyguard that would be blended with the default scrim + * by applying alpha composition on both values. + * + * @param additionalScrimAlpha alpha value of additional scrim behind alpha keyguard. + */ + protected void setAdditionalScrimBehindAlphaKeyguard(float additionalScrimAlpha) { + mAdditionalScrimBehindAlphaKeyguard = additionalScrimAlpha; + } + + /** + * Applies alpha composition to the default scrim behind alpha keyguard and the additional + * scrim alpha, and sets this value to the scrim behind alpha keyguard. + * This is used to apply additional keyguard dimming on top of the default scrim alpha value. + */ + protected void applyCompositeAlphaOnScrimBehindKeyguard() { + int compositeAlpha = ColorUtils.compositeAlpha( + (int) (255 * mAdditionalScrimBehindAlphaKeyguard), + (int) (255 * KEYGUARD_SCRIM_ALPHA)); + float keyguardScrimAlpha = (float) compositeAlpha / 255; + setScrimBehindValues(keyguardScrimAlpha); + } + + /** + * Sets the scrim behind alpha keyguard values. This is how much the keyguard will be dimmed. + * + * @param scrimBehindAlphaKeyguard alpha value of the scrim behind + */ + private void setScrimBehindValues(float scrimBehindAlphaKeyguard) { mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; ScrimState[] states = ScrimState.values(); for (int i = 0; i < states.length; i++) { @@ -732,7 +763,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) { // We're unoccluding the keyguard and don't want to have a bright flash. - mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA; + mNotificationsAlpha = mScrimBehindAlphaKeyguard; mNotificationsTint = ScrimState.KEYGUARD.getNotifTint(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index a9b3927acc88..fdced64bf1c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1003,7 +1003,7 @@ public class StatusBar extends CoreStartable implements internalFilter.addAction(BANNER_ACTION_CANCEL); internalFilter.addAction(BANNER_ACTION_SETUP); mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF, - null); + null, Context.RECEIVER_EXPORTED_UNAUDITED); if (mWallpaperSupported) { IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface( @@ -1332,7 +1332,8 @@ public class StatusBar extends CoreStartable implements demoFilter.addAction(ACTION_FAKE_ARTWORK); } mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter, - android.Manifest.permission.DUMP, null); + android.Manifest.permission.DUMP, null, + Context.RECEIVER_EXPORTED_UNAUDITED); // listen for USER_SETUP_COMPLETE setting (per-user) mDeviceProvisionedController.addCallback(mUserSetupObserver); @@ -1359,10 +1360,14 @@ public class StatusBar extends CoreStartable implements // Things that mean we're not dismissing the keyguard, and should ignore this expansion: // - Keyguard isn't even visible. // - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt). + // - The SIM is locked, you can't swipe to unlock. If the SIM is locked but there is no + // device lock set, canDismissLockScreen returns true even though you should not be able + // to dismiss the lock screen until entering the SIM PIN. // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the // keyguard. if (!isKeyguardShowing() || !mKeyguardStateController.canDismissLockScreen() + || mKeyguardViewMediator.isAnySimPinSecure() || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) { return; } @@ -2959,7 +2964,7 @@ public class StatusBar extends CoreStartable implements mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) { mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER); - } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) { + } else if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) { mStatusBarStateController.setState(StatusBarState.KEYGUARD); } updatePanelExpansionForKeyguard(); @@ -3163,16 +3168,29 @@ public class StatusBar extends CoreStartable implements * Switches theme from light to dark and vice-versa. */ protected void updateTheme() { + // Set additional scrim only if the lock and system wallpaper are different to prevent + // applying the dimming effect twice. + mUiBgExecutor.execute(() -> { + float dimAmount = 0f; + if (mWallpaperManager.lockScreenWallpaperExists()) { + dimAmount = mWallpaperManager.getWallpaperDimAmount(); + } + final float scrimDimAmount = dimAmount; + mMainExecutor.execute(() -> { + mScrimController.setAdditionalScrimBehindAlphaKeyguard(scrimDimAmount); + mScrimController.applyCompositeAlphaOnScrimBehindKeyguard(); + }); + }); + // Lock wallpaper defines the color of the majority of the views, hence we'll use it // to set our default theme. final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText(); final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper : R.style.Theme_SystemUI; - if (mContext.getThemeResId() == themeResId) { - return; + if (mContext.getThemeResId() != themeResId) { + mContext.setTheme(themeResId); + mConfigurationController.notifyThemeChanged(); } - mContext.setTheme(themeResId); - mConfigurationController.notifyThemeChanged(); } private void updateDozingState() { @@ -3565,7 +3583,6 @@ public class StatusBar extends CoreStartable implements // once we fully woke up. updateRevealEffect(true /* wakingUp */); updateNotificationPanelTouchState(); - mPulseExpansionHandler.onStartedWakingUp(); // If we are waking up during the screen off animation, we should undo making the // expanded visible (we did that so the LightRevealScrim would be visible). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 0ba713464b80..ea0dd72d673f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -99,6 +99,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( override fun onAnimationEnd(animation: Animator?) { lightRevealAnimationPlaying = false + interactionJankMonitor.end(CUJ_SCREEN_OFF) } override fun onAnimationStart(animation: Animator?) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index 41cacf5142fd..1030bfdb40fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -16,110 +16,53 @@ package com.android.systemui.statusbar.policy; - import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED; import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; -import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; - -import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS; import android.annotation.Nullable; import android.hardware.devicestate.DeviceStateManager; -import android.os.UserHandle; -import android.provider.Settings; -import android.text.TextUtils; import android.util.Log; -import android.util.SparseIntArray; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.wrapper.RotationPolicyWrapper; import java.util.concurrent.Executor; import javax.inject.Inject; -import javax.inject.Named; /** - * Handles reading and writing of rotation lock settings per device state, as well as setting - * the rotation lock when device state changes. - **/ + * Handles reading and writing of rotation lock settings per device state, as well as setting the + * rotation lock when device state changes. + */ @SysUISingleton -public final class DeviceStateRotationLockSettingController implements Listenable, - RotationLockController.RotationLockControllerCallback { +public final class DeviceStateRotationLockSettingController + implements Listenable, RotationLockController.RotationLockControllerCallback { private static final String TAG = "DSRotateLockSettingCon"; - private static final String SEPARATOR_REGEX = ":"; - - private final SecureSettings mSecureSettings; private final RotationPolicyWrapper mRotationPolicyWrapper; private final DeviceStateManager mDeviceStateManager; private final Executor mMainExecutor; - private final String[] mDeviceStateRotationLockDefaults; + private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager; - private SparseIntArray mDeviceStateRotationLockSettings; - // TODO(b/183001527): Add API to query current device state and initialize this. + // On registration for DeviceStateCallback, we will receive a callback with the current state + // and this will be initialized. private int mDeviceState = -1; - @Nullable - private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; - + @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; + private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener + mDeviceStateRotationLockSettingsListener; @Inject public DeviceStateRotationLockSettingController( - SecureSettings secureSettings, RotationPolicyWrapper rotationPolicyWrapper, DeviceStateManager deviceStateManager, @Main Executor executor, - @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults - ) { - mSecureSettings = secureSettings; + DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) { mRotationPolicyWrapper = rotationPolicyWrapper; mDeviceStateManager = deviceStateManager; mMainExecutor = executor; - mDeviceStateRotationLockDefaults = deviceStateRotationLockDefaults; - } - - /** - * Loads the settings from storage. - */ - public void initialize() { - String serializedSetting = - mSecureSettings.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - UserHandle.USER_CURRENT); - if (TextUtils.isEmpty(serializedSetting)) { - // No settings saved, we should load the defaults and persist them. - fallbackOnDefaults(); - return; - } - String[] values = serializedSetting.split(SEPARATOR_REGEX); - if (values.length % 2 != 0) { - // Each entry should be a key/value pair, so this is corrupt. - Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults"); - fallbackOnDefaults(); - return; - } - mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2); - int key; - int value; - - for (int i = 0; i < values.length - 1; ) { - try { - key = Integer.parseInt(values[i++]); - value = Integer.parseInt(values[i++]); - mDeviceStateRotationLockSettings.put(key, value); - } catch (NumberFormatException e) { - Log.wtf(TAG, "Error deserializing one of the saved settings", e); - fallbackOnDefaults(); - return; - } - } - } - - private void fallbackOnDefaults() { - loadDefaults(); - persistSettings(); + mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager; } @Override @@ -129,10 +72,17 @@ public final class DeviceStateRotationLockSettingController implements Listenabl // is no user action. mDeviceStateCallback = this::updateDeviceState; mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback); + mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState); + mDeviceStateRotationLockSettingsManager.registerListener( + mDeviceStateRotationLockSettingsListener); } else { if (mDeviceStateCallback != null) { mDeviceStateManager.unregisterCallback(mDeviceStateCallback); } + if (mDeviceStateRotationLockSettingsListener != null) { + mDeviceStateRotationLockSettingsManager.unregisterListener( + mDeviceStateRotationLockSettingsListener); + } } } @@ -143,7 +93,8 @@ public final class DeviceStateRotationLockSettingController implements Listenabl return; } - if (rotationLocked == isRotationLockedForCurrentState()) { + if (rotationLocked + == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) { Log.v(TAG, "Rotation lock same as the current setting, no need to update."); return; } @@ -152,19 +103,15 @@ public final class DeviceStateRotationLockSettingController implements Listenabl } private void saveNewRotationLockSetting(boolean isRotationLocked) { - Log.v(TAG, "saveNewRotationLockSetting [state=" + mDeviceState + "] [isRotationLocked=" - + isRotationLocked + "]"); - - mDeviceStateRotationLockSettings.put(mDeviceState, - isRotationLocked - ? DEVICE_STATE_ROTATION_LOCK_LOCKED - : DEVICE_STATE_ROTATION_LOCK_UNLOCKED); - persistSettings(); - } - - private boolean isRotationLockedForCurrentState() { - return mDeviceStateRotationLockSettings.get(mDeviceState, - DEVICE_STATE_ROTATION_LOCK_IGNORED) == DEVICE_STATE_ROTATION_LOCK_LOCKED; + Log.v( + TAG, + "saveNewRotationLockSetting [state=" + + mDeviceState + + "] [isRotationLocked=" + + isRotationLocked + + "]"); + + mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked); } private void updateDeviceState(int state) { @@ -173,8 +120,12 @@ public final class DeviceStateRotationLockSettingController implements Listenabl return; } + readPersistedSetting(state); + } + + private void readPersistedSetting(int state) { int rotationLockSetting = - mDeviceStateRotationLockSettings.get(state, DEVICE_STATE_ROTATION_LOCK_IGNORED); + mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state); if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { // We won't handle this device state. The same rotation lock setting as before should // apply and any changes to the rotation lock setting will be written for the previous @@ -186,54 +137,10 @@ public final class DeviceStateRotationLockSettingController implements Listenabl // Accept the new state mDeviceState = state; - // Update the rotation lock setting if needed for this new device state + // Update the rotation policy, if needed, for this new device state boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED; if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) { mRotationPolicyWrapper.setRotationLock(newRotationLockSetting); } } - - private void persistSettings() { - if (mDeviceStateRotationLockSettings.size() == 0) { - mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"", UserHandle.USER_CURRENT); - return; - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(mDeviceStateRotationLockSettings.keyAt(0)) - .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.valueAt(0)); - - for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) { - stringBuilder - .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.keyAt(i)) - .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.valueAt(i)); - } - mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - stringBuilder.toString(), UserHandle.USER_CURRENT); - } - - private void loadDefaults() { - if (mDeviceStateRotationLockDefaults.length == 0) { - Log.w(TAG, "Empty default settings"); - mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */0); - return; - } - mDeviceStateRotationLockSettings = - new SparseIntArray(mDeviceStateRotationLockDefaults.length); - for (String serializedDefault : mDeviceStateRotationLockDefaults) { - String[] entry = serializedDefault.split(SEPARATOR_REGEX); - try { - int key = Integer.parseInt(entry[0]); - int value = Integer.parseInt(entry[1]); - mDeviceStateRotationLockSettings.put(key, value); - } catch (NumberFormatException e) { - Log.wtf(TAG, "Error deserializing default settings", e); - } - } - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java new file mode 100644 index 000000000000..a418c74848a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java @@ -0,0 +1,266 @@ +/* + * 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.systemui.statusbar.policy; + +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseIntArray; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.HashSet; +import java.util.Set; + +/** + * Manages device-state based rotation lock settings. Handles reading, writing, and listening for + * changes. + */ +public final class DeviceStateRotationLockSettingsManager { + + private static final String TAG = "DSRotLockSettingsMngr"; + private static final String SEPARATOR_REGEX = ":"; + + private static DeviceStateRotationLockSettingsManager sSingleton; + + private final ContentResolver mContentResolver; + private final String[] mDeviceStateRotationLockDefaults; + private final Handler mMainHandler = Handler.getMain(); + private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>(); + private SparseIntArray mDeviceStateRotationLockSettings; + + private DeviceStateRotationLockSettingsManager(Context context) { + mContentResolver = context.getContentResolver(); + mDeviceStateRotationLockDefaults = + context.getResources() + .getStringArray(R.array.config_perDeviceStateRotationLockDefaults); + initializeInMemoryMap(); + listenForSettingsChange(context); + } + + /** Returns a singleton instance of this class */ + public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) { + if (sSingleton == null) { + sSingleton = + new DeviceStateRotationLockSettingsManager(context.getApplicationContext()); + } + return sSingleton; + } + + /** Returns true if device-state based rotation lock settings are enabled. */ + public static boolean isDeviceStateRotationLockEnabled(Context context) { + return context.getResources() + .getStringArray(R.array.config_perDeviceStateRotationLockDefaults) + .length + > 0; + } + + private void listenForSettingsChange(Context context) { + context.getContentResolver() + .registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK), + /* notifyForDescendents= */ false, //NOTYPO + new ContentObserver(mMainHandler) { + @Override + public void onChange(boolean selfChange) { + onPersistedSettingsChanged(); + } + }, + UserHandle.USER_CURRENT); + } + + /** + * Registers a {@link DeviceStateRotationLockSettingsListener} to be notified when the settings + * change. Can be called multiple times with different listeners. + */ + public void registerListener(DeviceStateRotationLockSettingsListener runnable) { + mListeners.add(runnable); + } + + /** + * Unregisters a {@link DeviceStateRotationLockSettingsListener}. No-op if the given instance + * was never registered. + */ + public void unregisterListener( + DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) { + if (!mListeners.remove(deviceStateRotationLockSettingsListener)) { + Log.w(TAG, "Attempting to unregister a listener hadn't been registered"); + } + } + + /** Updates the rotation lock setting for a specified device state. */ + public void updateSetting(int deviceState, boolean rotationLocked) { + mDeviceStateRotationLockSettings.put( + deviceState, + rotationLocked + ? DEVICE_STATE_ROTATION_LOCK_LOCKED + : DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + persistSettings(); + } + + /** + * Returns the {@link DeviceStateRotationLockSetting} for the given device state. If no setting + * is specified for this device state, it will return {@link + * DEVICE_STATE_ROTATION_LOCK_IGNORED}. + */ + @Settings.Secure.DeviceStateRotationLockSetting + public int getRotationLockSetting(int deviceState) { + return mDeviceStateRotationLockSettings.get( + deviceState, DEVICE_STATE_ROTATION_LOCK_IGNORED); + } + + /** Returns true if the rotation is locked for the current device state */ + public boolean isRotationLocked(int deviceState) { + return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED; + } + + /** + * Returns true if there is no device state for which the current setting is {@link + * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}. + */ + public boolean isRotationLockedForAllStates() { + for (int i = 0; i < mDeviceStateRotationLockSettings.size(); i++) { + if (mDeviceStateRotationLockSettings.valueAt(i) + == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) { + return false; + } + } + return true; + } + + private void initializeInMemoryMap() { + String serializedSetting = + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT); + if (TextUtils.isEmpty(serializedSetting)) { + // No settings saved, we should load the defaults and persist them. + fallbackOnDefaults(); + return; + } + String[] values = serializedSetting.split(SEPARATOR_REGEX); + if (values.length % 2 != 0) { + // Each entry should be a key/value pair, so this is corrupt. + Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults"); + fallbackOnDefaults(); + return; + } + mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2); + int key; + int value; + + for (int i = 0; i < values.length - 1; ) { + try { + key = Integer.parseInt(values[i++]); + value = Integer.parseInt(values[i++]); + mDeviceStateRotationLockSettings.put(key, value); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Error deserializing one of the saved settings", e); + fallbackOnDefaults(); + return; + } + } + } + + private void fallbackOnDefaults() { + loadDefaults(); + persistSettings(); + } + + private void persistSettings() { + if (mDeviceStateRotationLockSettings.size() == 0) { + Settings.Secure.putStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + /* value= */ "", + UserHandle.USER_CURRENT); + return; + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder + .append(mDeviceStateRotationLockSettings.keyAt(0)) + .append(SEPARATOR_REGEX) + .append(mDeviceStateRotationLockSettings.valueAt(0)); + + for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) { + stringBuilder + .append(SEPARATOR_REGEX) + .append(mDeviceStateRotationLockSettings.keyAt(i)) + .append(SEPARATOR_REGEX) + .append(mDeviceStateRotationLockSettings.valueAt(i)); + } + Settings.Secure.putStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + stringBuilder.toString(), + UserHandle.USER_CURRENT); + } + + private void loadDefaults() { + if (mDeviceStateRotationLockDefaults.length == 0) { + Log.w(TAG, "Empty default settings"); + mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */ 0); + return; + } + mDeviceStateRotationLockSettings = + new SparseIntArray(mDeviceStateRotationLockDefaults.length); + for (String serializedDefault : mDeviceStateRotationLockDefaults) { + String[] entry = serializedDefault.split(SEPARATOR_REGEX); + try { + int key = Integer.parseInt(entry[0]); + int value = Integer.parseInt(entry[1]); + mDeviceStateRotationLockSettings.put(key, value); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Error deserializing default settings", e); + } + } + } + + /** + * Called when the persisted settings have changed, requiring a reinitialization of the + * in-memory map. + */ + @VisibleForTesting + public void onPersistedSettingsChanged() { + initializeInMemoryMap(); + notifyListeners(); + } + + private void notifyListeners() { + for (DeviceStateRotationLockSettingsListener r : mListeners) { + r.onSettingsChanged(); + } + } + + /** Listener for changes in device-state based rotation lock settings */ + public interface DeviceStateRotationLockSettingsListener { + /** Called whenever the settings have changed. */ + void onSettingsChanged(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index c1f63b80dbf8..05a586b1cdc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -128,7 +128,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum update(true /* updateAlways */); } } - }, filter, null, null); + }, filter, null, null, Context.RECEIVER_EXPORTED_UNAUDITED); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 3831857c5c8d..59969c0447b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -22,10 +22,14 @@ import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static com.android.settingslib.Utils.updateLocationEnabled; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Handler; import android.os.Looper; @@ -68,31 +72,37 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio private final BootCompleteCache mBootCompleteCache; private final UserTracker mUserTracker; private final H mHandler; - + private final Handler mBackgroundHandler; + private final PackageManager mPackageManager; private boolean mAreActiveLocationRequests; private boolean mShouldDisplayAllAccesses; + private boolean mShowSystemAccesses; @Inject public LocationControllerImpl(Context context, AppOpsController appOpsController, DeviceConfigProxy deviceConfigProxy, @Main Looper mainLooper, @Background Handler backgroundHandler, BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache, - UserTracker userTracker) { + UserTracker userTracker, PackageManager packageManager) { mContext = context; mAppOpsController = appOpsController; mDeviceConfigProxy = deviceConfigProxy; mBootCompleteCache = bootCompleteCache; mHandler = new H(mainLooper); mUserTracker = userTracker; - mShouldDisplayAllAccesses = getDeviceConfigSetting(); + mBackgroundHandler = backgroundHandler; + mPackageManager = packageManager; + mShouldDisplayAllAccesses = getAllAccessesSetting(); + mShowSystemAccesses = getShowSystemSetting(); // Register to listen for changes in DeviceConfig settings. mDeviceConfigProxy.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_PRIVACY, backgroundHandler::post, properties -> { - mShouldDisplayAllAccesses = getDeviceConfigSetting(); + mShouldDisplayAllAccesses = getAllAccessesSetting(); + mShowSystemAccesses = getShowSystemSetting(); updateActiveLocationRequests(); }); @@ -176,11 +186,15 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio UserHandle.of(userId)); } - private boolean getDeviceConfigSetting() { + private boolean getAllAccessesSetting() { return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false); } + private boolean getShowSystemSetting() { + return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, false); + } /** * Returns true if there currently exist active high power location requests. */ @@ -202,35 +216,74 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio * Returns true if there currently exist active location requests. */ @VisibleForTesting - protected boolean areActiveLocationRequests() { + protected void areActiveLocationRequests() { if (!mShouldDisplayAllAccesses) { - return false; + return; } - List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); + boolean hadActiveLocationRequests = mAreActiveLocationRequests; + boolean shouldDisplay = false; + List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); + final List<UserInfo> profiles = mUserTracker.getUserProfiles(); final int numItems = appOpsItems.size(); for (int i = 0; i < numItems; i++) { if (appOpsItems.get(i).getCode() == OP_FINE_LOCATION || appOpsItems.get(i).getCode() == OP_COARSE_LOCATION) { - return true; + if (mShowSystemAccesses) { + shouldDisplay = true; + } else { + shouldDisplay |= !isSystemApp(profiles, appOpsItems.get(i)); + } } } - return false; + mAreActiveLocationRequests = areActiveHighPowerLocationRequests() || shouldDisplay; + if (mAreActiveLocationRequests != hadActiveLocationRequests) { + mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + } + } + + private boolean isSystemApp(List<UserInfo> profiles, AppOpItem item) { + final String permission = AppOpsManager.opToPermission(item.getCode()); + UserHandle user = UserHandle.getUserHandleForUid(item.getUid()); + + // Don't show apps belonging to background users except managed users. + boolean foundUser = false; + final int numProfiles = profiles.size(); + for (int i = 0; i < numProfiles; i++) { + if (profiles.get(i).getUserHandle().equals(user)) { + foundUser = true; + } + } + if (!foundUser) { + return true; + } + + final int permissionFlags = mPackageManager.getPermissionFlags( + permission, item.getPackageName(), user); + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, item.getUid(), item.getPackageName()) + == PermissionChecker.PERMISSION_GRANTED) { + return (permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) + == 0; + } else { + return (permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0; + } } // Reads the active location requests from either OP_MONITOR_HIGH_POWER_LOCATION, // OP_FINE_LOCATION, or OP_COARSE_LOCATION and updates the status view if necessary. private void updateActiveLocationRequests() { - boolean hadActiveLocationRequests = mAreActiveLocationRequests; if (mShouldDisplayAllAccesses) { - mAreActiveLocationRequests = - areActiveHighPowerLocationRequests() || areActiveLocationRequests(); + mBackgroundHandler.post(this::areActiveLocationRequests); } else { + boolean hadActiveLocationRequests = mAreActiveLocationRequests; mAreActiveLocationRequests = areActiveHighPowerLocationRequests(); - } - if (mAreActiveLocationRequests != hadActiveLocationRequests) { - mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + if (mAreActiveLocationRequests != hadActiveLocationRequests) { + mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 46fa20d094a0..48949f92413d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -51,6 +51,7 @@ import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; +import android.view.WindowInsetsController; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; @@ -665,8 +666,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } // Hide soft-keyboard when the input view became invisible // (i.e. The notification shade collapsed by pressing the home key) - if (visibility != VISIBLE && !mEditText.isVisibleToUser() - && !mController.isRemoteInputActive()) { + if (visibility != VISIBLE && !mController.isRemoteInputActive()) { mEditText.hideIme(); } } @@ -779,8 +779,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void hideIme() { - if (mInputMethodManager != null) { - mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + final WindowInsetsController insetsController = getWindowInsetsController(); + if (insetsController != null) { + insetsController.hide(WindowInsets.Type.ime()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java index 3143a471649c..1eeb0ac8b3bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java @@ -64,7 +64,6 @@ public final class RotationLockControllerImpl implements RotationLockController mDeviceStateRotationLockSettingController = deviceStateRotationLockSettingController; mIsPerDeviceStateRotationLockEnabled = deviceStateRotationLockDefaults.length > 0; if (mIsPerDeviceStateRotationLockEnabled) { - deviceStateRotationLockSettingController.initialize(); mCallbacks.add(mDeviceStateRotationLockSettingController); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java index f42e3885fe62..29285f886f4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java @@ -74,7 +74,7 @@ public class UserInfoControllerImpl implements UserInfoController { profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED); profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED); mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter, - null, null); + null, null, Context.RECEIVER_EXPORTED_UNAUDITED); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 79ee7468b5fe..9f20bc55ebc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -229,7 +229,8 @@ public class UserSwitcherController implements Dumpable { filter = new IntentFilter(); mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter, - PERMISSION_SELF, null /* scheduler */); + PERMISSION_SELF, null /* scheduler */, + Context.RECEIVER_EXPORTED_UNAUDITED); mSettingsObserver = new ContentObserver(mHandler) { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index b6a96a7e49b9..60938fb2feb4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy.dagger; +import android.content.Context; import android.content.res.Resources; import android.os.UserManager; @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.policy.DeviceControlsController; import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DevicePostureControllerImpl; +import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingsManager; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.ExtensionControllerImpl; import com.android.systemui.statusbar.policy.FlashlightController; @@ -163,6 +165,14 @@ public interface StatusBarPolicyModule { return controller; } + /** Returns a singleton instance of DeviceStateRotationLockSettingsManager */ + @SysUISingleton + @Provides + static DeviceStateRotationLockSettingsManager provideAutoRotateSettingsManager( + Context context) { + return DeviceStateRotationLockSettingsManager.getInstance(context); + } + /** * Default values for per-device state rotation lock settings. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index bd845209a6d6..31c7006c8edd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -47,6 +47,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; +import com.android.systemui.unfold.UnfoldTransitionProgressProvider; +import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener; import java.util.Optional; @@ -81,7 +83,8 @@ public class StatusBarWindowController { WindowManager windowManager, IWindowManager iWindowManager, StatusBarContentInsetsProvider contentInsetsProvider, - @Main Resources resources) { + @Main Resources resources, + Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) { mContext = context; mWindowManager = windowManager; mIWindowManager = iWindowManager; @@ -94,6 +97,10 @@ public class StatusBarWindowController { if (mBarHeight < 0) { mBarHeight = SystemBarUtils.getStatusBarHeight(mContext); } + unfoldTransitionProgressProvider.ifPresent( + unfoldProgressProvider -> unfoldProgressProvider.addCallback( + new JankMonitorTransitionProgressListener( + /* attachedViewProvider=*/ () -> mStatusBarWindowView))); } public int getStatusBarHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index 52c416bad803..4f037a0f1ace 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -30,17 +30,17 @@ import dagger.Lazy import javax.inject.Inject /** - * Controls folding to AOD animation: when AOD is enabled and foldable device is folded - * we play a special AOD animation on the outer screen + * Controls folding to AOD animation: when AOD is enabled and foldable device is folded we play a + * special AOD animation on the outer screen */ @SysUIUnfoldScope -class FoldAodAnimationController @Inject constructor( +class FoldAodAnimationController +@Inject +constructor( private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, private val wakefulnessLifecycle: WakefulnessLifecycle, private val globalSettings: GlobalSettings -) : CallbackController<FoldAodAnimationStatus>, - ScreenOffAnimation, - WakefulnessLifecycle.Observer { +) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer { private var alwaysOnEnabled: Boolean = false private var isScrimOpaque: Boolean = false @@ -58,17 +58,13 @@ class FoldAodAnimationController @Inject constructor( wakefulnessLifecycle.addObserver(this) } - /** - * Returns true if we should run fold to AOD animation - */ - override fun shouldPlayAnimation(): Boolean = - shouldPlayAnimation + /** Returns true if we should run fold to AOD animation */ + override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation override fun startAnimation(): Boolean = if (alwaysOnEnabled && wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD && - globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0" - ) { + globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0") { shouldPlayAnimation = true isAnimationPlaying = true @@ -107,9 +103,7 @@ class FoldAodAnimationController @Inject constructor( } } - /** - * Called when keyguard scrim opaque changed - */ + /** Called when keyguard scrim opaque changed */ override fun onScrimOpaqueChanged(isOpaque: Boolean) { isScrimOpaque = isOpaque @@ -130,27 +124,19 @@ class FoldAodAnimationController @Inject constructor( } } - override fun isAnimationPlaying(): Boolean = - isAnimationPlaying + override fun isAnimationPlaying(): Boolean = isAnimationPlaying - override fun isKeyguardHideDelayed(): Boolean = - isAnimationPlaying() + override fun isKeyguardHideDelayed(): Boolean = isAnimationPlaying() - override fun shouldShowAodIconsWhenShade(): Boolean = - shouldPlayAnimation() + override fun shouldShowAodIconsWhenShade(): Boolean = shouldPlayAnimation() - override fun shouldAnimateAodIcons(): Boolean = - !shouldPlayAnimation() + override fun shouldAnimateAodIcons(): Boolean = !shouldPlayAnimation() - override fun shouldAnimateDozingChange(): Boolean = - !shouldPlayAnimation() + override fun shouldAnimateDozingChange(): Boolean = !shouldPlayAnimation() - override fun shouldAnimateClockChange(): Boolean = - !isAnimationPlaying() + override fun shouldAnimateClockChange(): Boolean = !isAnimationPlaying() - /** - * Called when AOD status is changed - */ + /** Called when AOD status is changed */ override fun onAlwaysOnChanged(alwaysOn: Boolean) { alwaysOnEnabled = alwaysOn } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt new file mode 100644 index 000000000000..1451c03fefa0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt @@ -0,0 +1,36 @@ +/* + * 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.systemui.unfold + +import com.android.internal.util.FrameworkStatsLog + +/** Logs fold state changes. */ +class FoldStateLogger(private val foldStateLoggingProvider: FoldStateLoggingProvider) : + FoldStateLoggingProvider.FoldStateLoggingListener { + + fun init() { + foldStateLoggingProvider.addCallback(this) + } + + override fun onFoldUpdate(foldStateUpdate: FoldStateChange) { + FrameworkStatsLog.write( + FrameworkStatsLog.FOLD_STATE_DURATION_REPORTED, + foldStateUpdate.previous, + foldStateUpdate.current, + foldStateUpdate.dtMillis) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt index 7f63d6c5e778..79b42b8daab1 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt @@ -29,12 +29,16 @@ import javax.inject.Inject * Logs performance metrics regarding time to turn the inner screen on. * * This class assumes that [onFoldEvent] is always called before [onScreenTurnedOn]. + * * This should be used from only one process. + * * For now, the focus is on the time the inner display is visible, but in the future, it is easily * possible to monitor the time to go from the inner screen to the outer. */ @SysUISingleton -class UnfoldLatencyTracker @Inject constructor( +class UnfoldLatencyTracker +@Inject +constructor( private val latencyTracker: LatencyTracker, private val deviceStateManager: DeviceStateManager, @UiBackground private val uiBgExecutor: Executor, @@ -45,8 +49,11 @@ class UnfoldLatencyTracker @Inject constructor( private var folded: Boolean? = null private val foldStateListener = FoldStateListener(context) private val isFoldable: Boolean - get() = context.resources.getIntArray( - com.android.internal.R.array.config_foldedDeviceStates).isNotEmpty() + get() = + context + .resources + .getIntArray(com.android.internal.R.array.config_foldedDeviceStates) + .isNotEmpty() /** Registers for relevant events only if the device is foldable. */ fun init() { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 0b89ef28d227..4b09a583645c 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -20,8 +20,8 @@ import android.content.Context import android.graphics.PixelFormat import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener -import android.hardware.input.InputManager import android.hardware.display.DisplayManager +import android.hardware.input.InputManager import android.os.Handler import android.os.Trace import android.view.Choreographer @@ -46,7 +46,9 @@ import java.util.function.Consumer import javax.inject.Inject @SysUIUnfoldScope -class UnfoldLightRevealOverlayAnimation @Inject constructor( +class UnfoldLightRevealOverlayAnimation +@Inject +constructor( private val context: Context, private val deviceStateManager: DeviceStateManager, private val displayManager: DisplayManager, @@ -75,12 +77,13 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( deviceStateManager.registerCallback(executor, FoldListener()) unfoldTransitionProgressProvider.addCallback(transitionListener) - val containerBuilder = SurfaceControl.Builder(SurfaceSession()) - .setContainerLayer() - .setName("unfold-overlay-container") + val containerBuilder = + SurfaceControl.Builder(SurfaceSession()) + .setContainerLayer() + .setName("unfold-overlay-container") - displayAreaHelper.get().attachToRootDisplayArea(Display.DEFAULT_DISPLAY, - containerBuilder) { builder -> + displayAreaHelper.get().attachToRootDisplayArea( + Display.DEFAULT_DISPLAY, containerBuilder) { builder -> executor.execute { overlayContainer = builder.build() @@ -89,13 +92,13 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( .show(overlayContainer) .apply() - wwm = WindowlessWindowManager(context.resources.configuration, - overlayContainer, null) + wwm = + WindowlessWindowManager(context.resources.configuration, overlayContainer, null) } } - displayManager.registerDisplayListener(displayListener, handler, - DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) + displayManager.registerDisplayListener( + displayListener, handler, DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) // Get unfolded display size immediately as 'current display info' might be // not up-to-date during unfolding @@ -136,8 +139,8 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( ensureOverlayRemoved() val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false) - val newView = LightRevealScrim(context, null) - .apply { + val newView = + LightRevealScrim(context, null).apply { revealEffect = createLightRevealEffect() isScrimOpaqueChangedListener = Consumer {} revealAmount = 0f @@ -147,8 +150,7 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( newRoot.setView(newView, params) onOverlayReady?.let { callback -> - Trace.beginAsyncSection( - "UnfoldLightRevealOverlayAnimation#relayout", 0) + Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0) newRoot.relayout(params) { transaction -> val vsyncId = Choreographer.getSfInstance().vsyncId @@ -161,15 +163,11 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( // (turn on the brightness) only when the content is actually visible as it // might be presented only in the next frame. // See b/197538198 - transaction.setFrameTimelineVsync(vsyncId) - .apply(/* sync */true) + transaction.setFrameTimelineVsync(vsyncId).apply(/* sync */ true) - transaction - .setFrameTimelineVsync(vsyncId + 1) - .apply(/* sync */ true) + transaction.setFrameTimelineVsync(vsyncId + 1).apply(/* sync */ true) - Trace.endAsyncSection( - "UnfoldLightRevealOverlayAnimation#relayout", 0) + Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0) callback.run() } } @@ -185,10 +183,10 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( val rotation = context.display!!.rotation val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 - params.height = if (isNatural) - unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth - params.width = if (isNatural) - unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight + params.height = + if (isNatural) unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth + params.width = + if (isNatural) unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight params.format = PixelFormat.TRANSLUCENT params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY @@ -206,8 +204,8 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( } private fun createLightRevealEffect(): LightRevealEffect { - val isVerticalFold = currentRotation == Surface.ROTATION_0 || - currentRotation == Surface.ROTATION_180 + val isVerticalFold = + currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180 return LinearLightRevealEffect(isVertical = isVerticalFold) } @@ -218,7 +216,8 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( } private fun getUnfoldedDisplayInfo(): DisplayInfo = - displayManager.displays + displayManager + .displays .asSequence() .map { DisplayInfo().apply { it.getDisplayInfo(this) } } .filter { it.type == Display.TYPE_INTERNAL } @@ -255,18 +254,19 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( } } - override fun onDisplayAdded(displayId: Int) { - } + override fun onDisplayAdded(displayId: Int) {} - override fun onDisplayRemoved(displayId: Int) { - } + override fun onDisplayRemoved(displayId: Int) {} } - private inner class FoldListener : FoldStateListener(context, Consumer { isFolded -> - if (isFolded) { - ensureOverlayRemoved() - isUnfoldHandled = false - } - this.isFolded = isFolded - }) + private inner class FoldListener : + FoldStateListener( + context, + Consumer { isFolded -> + if (isFolded) { + ensureOverlayRemoved() + isUnfoldHandled = false + } + this.isFolded = isFolded + }) } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt index bd04ad8385b2..2325acfdcd48 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt @@ -21,29 +21,23 @@ import com.android.wm.shell.unfold.ShellUnfoldProgressProvider import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener import java.util.concurrent.Executor -class UnfoldProgressProvider( - private val unfoldProgressProvider: UnfoldTransitionProgressProvider -) : ShellUnfoldProgressProvider { +class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider) : + ShellUnfoldProgressProvider { override fun addListener(executor: Executor, listener: UnfoldListener) { - unfoldProgressProvider.addCallback(object : TransitionProgressListener { - override fun onTransitionStarted() { - executor.execute { - listener.onStateChangeStarted() + unfoldProgressProvider.addCallback( + object : TransitionProgressListener { + override fun onTransitionStarted() { + executor.execute { listener.onStateChangeStarted() } } - } - override fun onTransitionProgress(progress: Float) { - executor.execute { - listener.onStateChangeProgress(progress) + override fun onTransitionProgress(progress: Float) { + executor.execute { listener.onStateChangeProgress(progress) } } - } - override fun onTransitionFinished() { - executor.execute { - listener.onStateChangeFinished() + override fun onTransitionFinished() { + executor.execute { listener.onStateChangeFinished() } } - } - }) + }) } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index f2c156108ac6..d2d2361d613d 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -17,87 +17,47 @@ package com.android.systemui.unfold import android.content.Context -import android.hardware.SensorManager -import android.hardware.devicestate.DeviceStateManager -import android.os.Handler import android.view.IWindowManager -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.updates.FoldStateProvider +import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider +import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix import com.android.systemui.util.time.SystemClockImpl import com.android.wm.shell.unfold.ShellUnfoldProgressProvider import dagger.Lazy import dagger.Module import dagger.Provides import java.util.Optional -import java.util.concurrent.Executor import javax.inject.Named import javax.inject.Singleton -@Module +@Module(includes = [UnfoldSharedModule::class]) class UnfoldTransitionModule { - @Provides - @Singleton - fun provideUnfoldTransitionProgressProvider( - context: Context, - config: UnfoldTransitionConfig, - screenStatusProvider: Lazy<LifecycleScreenStatusProvider>, - deviceStateManager: DeviceStateManager, - sensorManager: SensorManager, - @Main executor: Executor, - @Main handler: Handler - ): Optional<UnfoldTransitionProgressProvider> = - if (config.isEnabled) { - Optional.of( - createUnfoldTransitionProgressProvider( - context, - config, - screenStatusProvider.get(), - deviceStateManager, - sensorManager, - handler, - executor, - tracingTagPrefix = "systemui")) - } else { - Optional.empty() - } + @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui" @Provides @Singleton - fun provideFoldStateProvider( - context: Context, + fun providesFoldStateLoggingProvider( config: UnfoldTransitionConfig, - screenStatusProvider: Lazy<LifecycleScreenStatusProvider>, - deviceStateManager: DeviceStateManager, - sensorManager: SensorManager, - @Main executor: Executor, - @Main handler: Handler - ): Optional<FoldStateProvider> = - if (!config.isHingeAngleEnabled) { - Optional.empty() + foldStateProvider: Lazy<FoldStateProvider> + ): Optional<FoldStateLoggingProvider> = + if (config.isHingeAngleEnabled) { + Optional.of(FoldStateLoggingProviderImpl(foldStateProvider.get(), SystemClockImpl())) } else { - Optional.of( - createFoldStateProvider( - context, - config, - screenStatusProvider.get(), - deviceStateManager, - sensorManager, - handler, - executor)) + Optional.empty() } @Provides @Singleton - fun providesFoldStateLoggingProvider( - optionalFoldStateProvider: Optional<FoldStateProvider> - ): Optional<FoldStateLoggingProvider> = - optionalFoldStateProvider.map { foldStateProvider -> - FoldStateLoggingProviderImpl(foldStateProvider, SystemClockImpl()) + fun providesFoldStateLogger( + optionalFoldStateLoggingProvider: Optional<FoldStateLoggingProvider> + ): Optional<FoldStateLogger> = + optionalFoldStateLoggingProvider.map { FoldStateLoggingProvider -> + FoldStateLogger(FoldStateLoggingProvider) } @Provides @@ -130,11 +90,14 @@ class UnfoldTransitionModule { config: UnfoldTransitionConfig, provider: Optional<UnfoldTransitionProgressProvider> ): ShellUnfoldProgressProvider = - if (config.isEnabled && provider.isPresent()) { + if (config.isEnabled && provider.isPresent) { UnfoldProgressProvider(provider.get()) } else { ShellUnfoldProgressProvider.NO_PROVIDER } + + @Provides + fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl } const val UNFOLD_STATUS_BAR = "unfold_status_bar" diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt index a184315ab75c..d723760fa510 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt @@ -21,7 +21,9 @@ import com.android.systemui.util.WallpaperController import javax.inject.Inject @SysUIUnfoldScope -class UnfoldTransitionWallpaperController @Inject constructor( +class UnfoldTransitionWallpaperController +@Inject +constructor( private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, private val wallpaperController: WallpaperController ) { diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 90c7f1f7ac72..cf361ec304e5 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -167,9 +167,11 @@ public class StorageNotification extends CoreStartable { mStorageManager.registerListener(mListener); mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME), - android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null, + Context.RECEIVER_EXPORTED_UNAUDITED); mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD), - android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null, + Context.RECEIVER_EXPORTED_UNAUDITED); // Kick current state into place final List<DiskInfo> disks = mStorageManager.getDisks(); diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java index aec14be9c39e..d10e890f3c02 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java @@ -70,6 +70,10 @@ public class UsbContaminantActivity extends Activity implements View.OnClickList mEnableUsb.setText(getString(R.string.usb_disable_contaminant_detection)); mGotIt.setText(getString(R.string.got_it)); mLearnMore.setText(getString(R.string.learn_more)); + if (getResources().getBoolean( + com.android.internal.R.bool.config_settingsHelpLinksEnabled)) { + mLearnMore.setVisibility(View.VISIBLE); + } mEnableUsb.setOnClickListener(this); mGotIt.setOnClickListener(this); diff --git a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java index a40cf4f37cc3..5b188b24d7dd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java @@ -63,7 +63,8 @@ abstract public class SafetyWarningDialog extends SystemUIDialog setOnDismissListener(this); final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.registerReceiver(mReceiver, filter); + context.registerReceiver(mReceiver, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); } abstract protected void cleanUp(); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 20c8bf38692b..69ebfe8012d1 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -427,9 +427,11 @@ public class BubblesManager implements Dumpable { } @Override - public void onEntryRemoved(NotificationEntry entry, + public void onEntryRemoved( + NotificationEntry entry, @Nullable NotificationVisibility visibility, - boolean removedByUser, int reason) { + boolean removedByUser, + int reason) { BubblesManager.this.onEntryRemoved(entry); } @@ -437,6 +439,18 @@ public class BubblesManager implements Dumpable { public void onNotificationRankingUpdated(RankingMap rankingMap) { BubblesManager.this.onRankingUpdate(rankingMap); } + + @Override + public void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + BubblesManager.this.onNotificationChannelModified(pkgName, + user, + channel, + modificationType); + } }); // The new pipeline takes care of this as a NotifDismissInterceptor BubbleCoordinator @@ -556,6 +570,19 @@ public class BubblesManager implements Dumpable { public void onRankingUpdate(RankingMap rankingMap) { BubblesManager.this.onRankingUpdate(rankingMap); } + + @Override + public void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + BubblesManager.this.onNotificationChannelModified( + pkgName, + user, + channel, + modificationType); + } }); } @@ -592,6 +619,14 @@ public class BubblesManager implements Dumpable { mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif); } + void onNotificationChannelModified( + String pkg, + UserHandle user, + NotificationChannel channel, + int modificationType) { + mBubbles.onNotificationChannelModified(pkg, user, channel, modificationType); + } + /** * Gets the DismissedByUserStats used by {@link NotificationEntryManager}. * Will not be necessary when using the new notification pipeline's {@link NotifCollection}. diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt new file mode 100644 index 000000000000..6bc65054d830 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt @@ -0,0 +1,194 @@ +/* + * 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.keyguard + +import android.hardware.biometrics.BiometricSourceType +import org.mockito.Mockito.verify +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { + @Mock + lateinit var uiEventLogger: UiEventLogger + @Mock + lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock + lateinit var dumpManager: DumpManager + @Mock + lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker + + @Captor + lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback> + lateinit var updateMonitorCallback: KeyguardUpdateMonitorCallback + + lateinit var keyguardBiometricLockoutLogger: KeyguardBiometricLockoutLogger + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker) + keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger( + mContext, + uiEventLogger, + keyguardUpdateMonitor, + dumpManager) + } + + @Test + fun test_logsOnStart() { + // GIVEN is encrypted / lockdown before start + whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(anyInt())) + .thenReturn(true) + + // WHEN start + keyguardBiometricLockoutLogger.start() + + // THEN encrypted / lockdown state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN) + } + + @Test + fun test_logTimeoutChange() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c timeout + whenever(strongAuthTracker.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) + + // WHEN primary auth requirement changes + updateMonitorCallback.onStrongAuthStateChanged(0) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_TIMEOUT) + } + + @Test + fun test_logUnattendedUpdate() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c unattended update + whenever(strongAuthTracker.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) + + // WHEN primary auth requirement changes + updateMonitorCallback.onStrongAuthStateChanged(0) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + } + + @Test + fun test_logMultipleChanges() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c timeout + whenever(strongAuthTracker.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT + or STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) + + // WHEN primary auth requirement changes + updateMonitorCallback.onStrongAuthStateChanged(0) + + // THEN primary auth required state is logged with all the reasons + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_TIMEOUT) + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + + // WHEN onStrongAuthStateChanged is called again + updateMonitorCallback.onStrongAuthStateChanged(0) + + // THEN no more events are sent since there haven't been any changes + verifyNoMoreInteractions(uiEventLogger) + } + + @Test + fun test_logFaceLockout() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c face lock + whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(true) + + // WHEN lockout state changes + updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT) + + // WHEN face lockout is reset + whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false) + updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET) + } + + @Test + fun test_logFingerprintLockout() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c fingerprint lock + whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true) + + // WHEN lockout state changes + updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT) + + // WHEN fingerprint lockout is reset + whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) + updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET) + } + + fun captureUpdateMonitorCallback() { + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture()) + updateMonitorCallback = updateMonitorCallbackCaptor.value + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 70792cfee301..7266e41ad7ca 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -88,6 +88,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -173,6 +174,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private InteractionJankMonitor mInteractionJankMonitor; @Mock private LatencyTracker mLatencyTracker; + @Mock + private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; // Direct executor @@ -1105,7 +1108,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mRingerModeTracker, mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, - mInteractionJankMonitor, mLatencyTracker); + mInteractionJankMonitor, mLatencyTracker, mFeatureFlags); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java index 7d556236b518..7c121e1754bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java @@ -15,6 +15,7 @@ package com.android.systemui; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -79,7 +80,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { intent.putExtra(SliceBroadcastRelay.EXTRA_URI, testUri); mRelayHandler.handleIntent(intent); - verify(mSpyContext).registerReceiver(any(), eq(value)); + verify(mSpyContext).registerReceiver(any(), eq(value), anyInt()); } @Test @@ -99,7 +100,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { mRelayHandler.handleIntent(intent); ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mSpyContext).registerReceiver(relay.capture(), eq(value)); + verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt()); intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER); intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0)); @@ -138,7 +139,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { mRelayHandler.handleIntent(intent); ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mSpyContext).registerReceiver(relay.capture(), eq(value)); + verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt()); relay.getValue().onReceive(mSpyContext, new Intent(TEST_ACTION)); verify(Receiver.sReceiver, timeout(2000)).onReceive(any(), any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java index 3d679deaa426..0674ea855d7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java @@ -83,6 +83,16 @@ public class SysuiTestableContext extends TestableContext { } @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { + if (receiver != null) { + synchronized (mRegisteredReceivers) { + mRegisteredReceivers.add(receiver); + } + } + return super.registerReceiver(receiver, filter, flags); + } + + @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { if (receiver != null) { @@ -94,6 +104,17 @@ public class SysuiTestableContext extends TestableContext { } @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, int flags) { + if (receiver != null) { + synchronized (mRegisteredReceivers) { + mRegisteredReceivers.add(receiver); + } + } + return super.registerReceiver(receiver, filter, broadcastPermission, scheduler, flags); + } + + @Override public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler) { if (receiver != null) { @@ -105,6 +126,18 @@ public class SysuiTestableContext extends TestableContext { } @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) { + if (receiver != null) { + synchronized (mRegisteredReceivers) { + mRegisteredReceivers.add(receiver); + } + } + return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission, scheduler, + flags); + } + + @Override public void unregisterReceiver(BroadcastReceiver receiver) { if (receiver != null) { synchronized (mRegisteredReceivers) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt index 2d510923b942..254fc5945522 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt @@ -25,6 +25,7 @@ import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN +import android.hardware.biometrics.SensorLocationInternal import android.hardware.biometrics.SensorProperties import android.hardware.display.DisplayManager import android.hardware.display.DisplayManagerGlobal @@ -52,6 +53,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.recents.OverviewProxyService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test @@ -75,6 +77,12 @@ import org.mockito.junit.MockitoJUnit private const val DISPLAY_ID = 2 private const val SENSOR_ID = 1 +private const val DISPLAY_SIZE_X = 800 +private const val DISPLAY_SIZE_Y = 900 + +private val X_LOCATION = SensorLocationInternal("", 540, 0, 20) +private val Y_LOCATION = SensorLocationInternal("", 0, 1500, 22) + @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @@ -101,6 +109,8 @@ class SidefpsControllerTest : SysuiTestCase() { lateinit var handler: Handler @Captor lateinit var overlayCaptor: ArgumentCaptor<View> + @Captor + lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private val executor = FakeExecutor(FakeSystemClock()) private lateinit var overlayController: ISidefpsController @@ -125,6 +135,17 @@ class SidefpsControllerTest : SysuiTestCase() { this } } + `when`(windowManager.maximumWindowMetrics).thenReturn( + WindowMetrics(Rect(0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y), WindowInsets.CONSUMED) + ) + } + + private fun testWithDisplay( + initInfo: DisplayInfo.() -> Unit = {}, + locations: List<SensorLocationInternal> = listOf(X_LOCATION), + windowInsets: WindowInsets = insetsForSmallNavbar(), + block: () -> Unit + ) { `when`(fingerprintManager.sensorPropertiesInternal).thenReturn( listOf( FingerprintSensorPropertiesInternal( @@ -133,22 +154,21 @@ class SidefpsControllerTest : SysuiTestCase() { 5 /* maxEnrollmentsPerUser */, listOf() /* componentInfo */, FingerprintSensorProperties.TYPE_POWER_BUTTON, - true /* resetLockoutRequiresHardwareAuthToken */ + true /* resetLockoutRequiresHardwareAuthToken */, + locations ) ) ) - `when`(windowManager.maximumWindowMetrics).thenReturn( - WindowMetrics(Rect(0, 0, 800, 800), WindowInsets.CONSUMED) - ) - } - private fun testWithDisplay(initInfo: DisplayInfo.() -> Unit = {}, block: () -> Unit) { val displayInfo = DisplayInfo() displayInfo.initInfo() val dmGlobal = mock(DisplayManagerGlobal::class.java) val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS) `when`(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo) `when`(windowManager.defaultDisplay).thenReturn(display) + `when`(windowManager.currentWindowMetrics).thenReturn( + WindowMetrics(Rect(0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y), windowInsets) + ) sideFpsController = SidefpsController( context.createDisplayContext(display), layoutInflater, fingerprintManager, @@ -245,28 +265,71 @@ class SidefpsControllerTest : SysuiTestCase() { } @Test + fun showsWithTaskbarOnY() = testWithDisplay( + { rotation = Surface.ROTATION_0 }, + locations = listOf(Y_LOCATION) + ) { + hidesWithTaskbar(visible = true) + } + + @Test fun showsWithTaskbar90() = testWithDisplay({ rotation = Surface.ROTATION_90 }) { hidesWithTaskbar(visible = true) } @Test + fun showsWithTaskbar90OnY() = testWithDisplay( + { rotation = Surface.ROTATION_90 }, + locations = listOf(Y_LOCATION) + ) { + hidesWithTaskbar(visible = true) + } + + @Test fun showsWithTaskbar180() = testWithDisplay({ rotation = Surface.ROTATION_180 }) { hidesWithTaskbar(visible = true) } @Test - fun showsWithTaskbarCollapsedDown() = testWithDisplay({ rotation = Surface.ROTATION_270 }) { - `when`(windowManager.currentWindowMetrics).thenReturn( - WindowMetrics(Rect(0, 0, 800, 800), insetsForSmallNavbar()) - ) + fun showsWithTaskbar270OnY() = testWithDisplay( + { rotation = Surface.ROTATION_270 }, + locations = listOf(Y_LOCATION) + ) { hidesWithTaskbar(visible = true) } @Test - fun hidesWithTaskbarDown() = testWithDisplay({ rotation = Surface.ROTATION_270 }) { - `when`(windowManager.currentWindowMetrics).thenReturn( - WindowMetrics(Rect(0, 0, 800, 800), insetsForLargeNavbar()) - ) + fun showsWithTaskbarCollapsedDown() = testWithDisplay( + { rotation = Surface.ROTATION_270 }, + windowInsets = insetsForSmallNavbar() + ) { + hidesWithTaskbar(visible = true) + } + + @Test + fun showsWithTaskbarCollapsedDownOnY() = testWithDisplay( + { rotation = Surface.ROTATION_180 }, + locations = listOf(Y_LOCATION), + windowInsets = insetsForSmallNavbar() + ) { + hidesWithTaskbar(visible = true) + } + + @Test + fun hidesWithTaskbarDown() = testWithDisplay( + { rotation = Surface.ROTATION_180 }, + locations = listOf(X_LOCATION), + windowInsets = insetsForLargeNavbar() + ) { + hidesWithTaskbar(visible = false) + } + + @Test + fun hidesWithTaskbarDownOnY() = testWithDisplay( + { rotation = Surface.ROTATION_270 }, + locations = listOf(Y_LOCATION), + windowInsets = insetsForLargeNavbar() + ) { hidesWithTaskbar(visible = false) } @@ -281,6 +344,28 @@ class SidefpsControllerTest : SysuiTestCase() { verify(windowManager, never()).removeView(any()) verify(sidefpsView).visibility = if (visible) View.VISIBLE else View.GONE } + + @Test + fun setsXAlign() = testWithDisplay { + overlayController.show(SENSOR_ID, REASON_UNKNOWN) + executor.runAllReady() + + verify(windowManager).addView(any(), overlayViewParamsCaptor.capture()) + + assertThat(overlayViewParamsCaptor.value.x).isEqualTo(X_LOCATION.sensorLocationX) + assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0) + } + + @Test + fun setYAlign() = testWithDisplay(locations = listOf(Y_LOCATION)) { + overlayController.show(SENSOR_ID, REASON_UNKNOWN) + executor.runAllReady() + + verify(windowManager).addView(any(), overlayViewParamsCaptor.capture()) + + assertThat(overlayViewParamsCaptor.value.x).isEqualTo(DISPLAY_SIZE_X) + assertThat(overlayViewParamsCaptor.value.y).isEqualTo(Y_LOCATION.sensorLocationY) + } } private fun insetsForSmallNavbar() = insetsWithBottom(60) diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java deleted file mode 100644 index 4a29ada8a998..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.systemui.communal; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.app.communal.CommunalManager; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.condition.Monitor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class CommunalManagerUpdaterTest extends SysuiTestCase { - private CommunalSourceMonitor mMonitor; - @Mock - private CommunalManager mCommunalManager; - @Mock - private Monitor mCommunalConditionsMonitor; - - private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mContext.addMockSystemService(CommunalManager.class, mCommunalManager); - - doAnswer(invocation -> { - final Monitor.Callback callback = invocation.getArgument(0); - callback.onConditionsChanged(true); - return null; - }).when(mCommunalConditionsMonitor).addCallback(any()); - - mMonitor = new CommunalSourceMonitor(mExecutor, mCommunalConditionsMonitor); - final CommunalManagerUpdater updater = new CommunalManagerUpdater(mContext, mMonitor); - updater.start(); - clearInvocations(mCommunalManager); - } - - @Test - public void testUpdateSystemService_false() { - mMonitor.setSource(null); - mExecutor.runAllReady(); - verify(mCommunalManager).setCommunalViewShowing(false); - } - - @Test - public void testUpdateSystemService_true() { - final CommunalSource source = mock(CommunalSource.class); - mMonitor.setSource(source); - mExecutor.runAllReady(); - verify(mCommunalManager).setCommunalViewShowing(true); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt index 2d3757c29ebf..12096bc06748 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt @@ -17,6 +17,9 @@ package com.android.systemui.controls.controller import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection import android.os.UserHandle import android.service.controls.IControlsActionCallback import android.service.controls.IControlsProvider @@ -43,6 +46,8 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -57,8 +62,6 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { private lateinit var subscriberService: IControlsSubscriber.Stub @Mock private lateinit var service: IControlsProvider.Stub - @Mock - private lateinit var loadCallback: ControlsBindingController.LoadCallback @Captor private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper> @@ -75,7 +78,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - mContext.addMockService(componentName, service) + context.addMockService(componentName, service) executor = FakeExecutor(FakeSystemClock()) `when`(service.asBinder()).thenCallRealMethod() `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service) @@ -98,7 +101,36 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { fun testBindService() { manager.bindService() executor.runAllReady() - assertTrue(mContext.isBound(componentName)) + assertTrue(context.isBound(componentName)) + } + + @Test + fun testNullBinding() { + val mockContext = mock(Context::class.java) + lateinit var serviceConnection: ServiceConnection + `when`(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer { + val component = (it.arguments[0] as Intent).component + if (component == componentName) { + serviceConnection = it.arguments[1] as ServiceConnection + serviceConnection.onNullBinding(component) + true + } else { + false + } + } + + val nullManager = ControlsProviderLifecycleManager( + mockContext, + executor, + actionCallbackService, + UserHandle.of(0), + componentName + ) + + nullManager.bindService() + executor.runAllReady() + + verify(mockContext).unbindService(serviceConnection) } @Test @@ -109,7 +141,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { manager.unbindService() executor.runAllReady() - assertFalse(mContext.isBound(componentName)) + assertFalse(context.isBound(componentName)) } @Test @@ -119,7 +151,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { verify(service).load(subscriberService) - assertTrue(mContext.isBound(componentName)) + assertTrue(context.isBound(componentName)) } @Test @@ -129,7 +161,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { manager.unbindService() executor.runAllReady() - assertFalse(mContext.isBound(componentName)) + assertFalse(context.isBound(componentName)) } @Test @@ -162,7 +194,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { manager.maybeBindAndSubscribe(list, subscriberService) executor.runAllReady() - assertTrue(mContext.isBound(componentName)) + assertTrue(context.isBound(componentName)) verify(service).subscribe(list, subscriberService) } @@ -173,7 +205,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { manager.maybeBindAndSendAction(controlId, action) executor.runAllReady() - assertTrue(mContext.isBound(componentName)) + assertTrue(context.isBound(componentName)) verify(service).action(eq(controlId), capture(wrapperCaptor), eq(actionCallbackService)) assertEquals(action, wrapperCaptor.getValue().getWrappedAction()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt index a328d9e06a74..efb3db700804 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt @@ -32,7 +32,6 @@ import androidx.test.filters.MediumTest import androidx.test.rule.ActivityTestRule import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.util.mockito.capture @@ -70,8 +69,6 @@ class ControlsRequestDialogTest : SysuiTestCase() { @Mock private lateinit var listingController: ControlsListingController @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var iIntentSender: IIntentSender @Captor private lateinit var captor: ArgumentCaptor<ControlInfo> @@ -85,7 +82,7 @@ class ControlsRequestDialogTest : SysuiTestCase() { override fun create(intent: Intent?): TestControlsRequestDialog { return TestControlsRequestDialog( controller, - broadcastDispatcher, + fakeBroadcastDispatcher, listingController ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index cf53ccffcdb0..7c5f57fe0b39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -19,10 +19,13 @@ package com.android.systemui.dreams; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; +import android.os.Handler; import android.testing.AndroidTestingRunner; import android.view.View; import android.view.ViewGroup; @@ -45,6 +48,8 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { private static final int DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT = 100; + private static final int MAX_BURN_IN_OFFSET = 20; + private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10; @Mock Resources mResources; @@ -61,6 +66,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { @Mock ViewGroup mDreamOverlayContentView; + @Mock + Handler mHandler; + DreamOverlayContainerViewController mController; @Before @@ -74,8 +82,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver); mController = new DreamOverlayContainerViewController( - mDreamOverlayContainerView, mDreamOverlayContentView, - mDreamOverlayStatusBarViewController); + mDreamOverlayContainerView, + mDreamOverlayContentView, + mDreamOverlayStatusBarViewController, + mHandler, + MAX_BURN_IN_OFFSET, + BURN_IN_PROTECTION_UPDATE_INTERVAL); } @Test @@ -129,4 +141,37 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { computeInsetsListenerCapture.getValue().onComputeInternalInsets(info); assertNotNull(info.touchableRegion); } + + @Test + public void testBurnInProtectionStartsWhenContentViewAttached() { + mController.onViewAttached(); + verify(mHandler).postDelayed(any(Runnable.class), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); + } + + @Test + public void testBurnInProtectionStopsWhenContentViewDetached() { + mController.onViewDetached(); + verify(mHandler).removeCallbacks(any(Runnable.class)); + } + + @Test + public void testBurnInProtectionUpdatesPeriodically() { + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + mController.onViewAttached(); + verify(mHandler).postDelayed( + runnableCaptor.capture(), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); + runnableCaptor.getValue().run(); + verify(mDreamOverlayContainerView).setTranslationX(anyFloat()); + verify(mDreamOverlayContainerView).setTranslationY(anyFloat()); + } + + @Test + public void testBurnInProtectionReschedulesUpdate() { + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + mController.onViewAttached(); + verify(mHandler).postDelayed( + runnableCaptor.capture(), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); + runnableCaptor.getValue().run(); + verify(mHandler).postDelayed(runnableCaptor.getValue(), BURN_IN_PROTECTION_UPDATE_INTERVAL); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java index 736556871376..f538112edbda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.dreams; +package com.android.systemui.dreams.appwidgets; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.isNull; @@ -33,8 +33,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; -import com.android.systemui.dreams.appwidgets.AppWidgetProvider; -import com.android.systemui.dreams.appwidgets.ComplicationProvider; +import com.android.systemui.dreams.ComplicationHost; +import com.android.systemui.dreams.ComplicationHostView; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.utils.leaks.LeakCheckedTest; diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt index cb16becc48fd..87bc732d2f66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt @@ -76,7 +76,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) verify(mFlagManager).restartAction = any() mBroadcastReceiver = withArgCaptor { - verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable()) + verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable(), + any()) } mClearCacheAction = withArgCaptor { verify(mFlagManager).clearCacheAction = capture() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java index e453ff2dc7bf..fd282ccd2a74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java @@ -80,6 +80,7 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { assertEquals(WakefulnessLifecycle.WAKEFULNESS_AWAKE, mWakefulness.getWakefulness()); verify(mWakefulnessObserver).onFinishedWakingUp(); + verify(mWakefulnessObserver).onPostFinishedWakingUp(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index dec5a100e20d..4839bdea1b70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -16,23 +16,28 @@ package com.android.systemui.media.taptotransfer +import android.content.ComponentName import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver -import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender -import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer -import com.android.systemui.media.taptotransfer.sender.TransferInitiated -import com.android.systemui.media.taptotransfer.sender.TransferSucceeded +import com.android.systemui.media.taptotransfer.sender.* +import com.android.systemui.shared.mediattt.DeviceInfo +import com.android.systemui.shared.mediattt.IDeviceSenderCallback import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.mockito.Mock +import org.mockito.Mockito.anyString import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import java.io.PrintWriter import java.io.StringWriter @@ -51,10 +56,19 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender @Mock private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver + @Mock + private lateinit var mediaSenderService: IDeviceSenderCallback.Stub + private lateinit var mediaSenderServiceComponentName: ComponentName @Before fun setUp() { MockitoAnnotations.initMocks(this) + + mediaSenderServiceComponentName = ComponentName(context, MediaTttSenderService::class.java) + context.addMockService(mediaSenderServiceComponentName, mediaSenderService) + whenever(mediaSenderService.queryLocalInterface(anyString())).thenReturn(mediaSenderService) + whenever(mediaSenderService.asBinder()).thenReturn(mediaSenderService) + mediaTttCommandLineHelper = MediaTttCommandLineHelper( commandRegistry, @@ -102,10 +116,14 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { } @Test - fun sender_moveCloserToTransfer_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand()) + fun sender_moveCloserToStartCast_serviceCallbackCalled() { + commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() - verify(mediaTttChipControllerSender).displayChip(any(MoveCloserToTransfer::class.java)) + val deviceInfoCaptor = argumentCaptor<DeviceInfo>() + verify(mediaSenderService).closeToReceiverToStartCast(any(), capture(deviceInfoCaptor)) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test @@ -143,11 +161,11 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { verify(mediaTttChipControllerReceiver).removeChip() } - private fun getMoveCloserToTransferCommand(): Array<String> = + private fun getMoveCloserToStartCastCommand(): Array<String> = arrayOf( ADD_CHIP_COMMAND_SENDER_TAG, DEVICE_NAME, - MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME + MOVE_CLOSER_TO_START_CAST_COMMAND_NAME ) private fun getTransferInitiatedCommand(): Array<String> = diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index caef5b901e0f..ecc4c46634b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -66,8 +66,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun moveCloserToTransfer_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() { - controllerSender.displayChip(moveCloserToTransfer()) + fun moveCloserToStartCast_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() { + controllerSender.displayChip(moveCloserToStartCast()) val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) @@ -192,8 +192,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() { - controllerSender.displayChip(moveCloserToTransfer()) + fun changeFromCloserToStartToTransferInitiated_loadingIconAppears() { + controllerSender.displayChip(moveCloserToStartCast()) controllerSender.displayChip(transferInitiated()) assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE) @@ -216,9 +216,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() { + fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() { controllerSender.displayChip(transferSucceeded()) - controllerSender.displayChip(moveCloserToTransfer()) + controllerSender.displayChip(moveCloserToStartCast()) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE) } @@ -240,8 +240,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } /** Helper method providing default parameters to not clutter up the tests. */ - private fun moveCloserToTransfer() = - MoveCloserToTransfer(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + private fun moveCloserToStartCast() = + MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferInitiated( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt new file mode 100644 index 000000000000..8f64698a5a6c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt @@ -0,0 +1,48 @@ +package com.android.systemui.media.taptotransfer.sender + +import android.media.MediaRoute2Info +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.mediattt.DeviceInfo +import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +class MediaTttSenderServiceTest : SysuiTestCase() { + + private lateinit var service: MediaTttSenderService + private lateinit var callback: IDeviceSenderCallback + + @Mock + private lateinit var controller: MediaTttChipControllerSender + + private val mediaInfo = MediaRoute2Info.Builder("id", "Test Name") + .addFeature("feature") + .build() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + service = MediaTttSenderService(context, controller) + callback = IDeviceSenderCallback.Stub.asInterface(service.onBind(null)) + } + + @Test + fun closeToReceiverToStartCast_controllerTriggeredWithMoveCloserToStartCastState() { + val name = "Fake name" + callback.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name)) + + val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + assertThat(chipState.otherDeviceName).isEqualTo(name) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt new file mode 100644 index 000000000000..8bc438bce5cc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt @@ -0,0 +1,102 @@ +/* + * 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.systemui.shared.navigationbar +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.SurfaceControl +import android.view.View +import android.view.ViewRootImpl +import androidx.concurrent.futures.DirectExecutor +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@RunWithLooper +class RegionSamplingHelperTest : SysuiTestCase() { + + @Mock + lateinit var sampledView: View + @Mock + lateinit var samplingCallback: RegionSamplingHelper.SamplingCallback + @Mock + lateinit var compositionListener: RegionSamplingHelper.SysuiCompositionSamplingListener + @Mock + lateinit var viewRootImpl: ViewRootImpl + @Mock + lateinit var surfaceControl: SurfaceControl + @Mock + lateinit var wrappedSurfaceControl: SurfaceControl + @JvmField @Rule + var rule = MockitoJUnit.rule() + lateinit var regionSamplingHelper: RegionSamplingHelper + + @Before + fun setup() { + whenever(sampledView.isAttachedToWindow).thenReturn(true) + whenever(sampledView.viewRootImpl).thenReturn(viewRootImpl) + whenever(viewRootImpl.surfaceControl).thenReturn(surfaceControl) + whenever(surfaceControl.isValid).thenReturn(true) + whenever(wrappedSurfaceControl.isValid).thenReturn(true) + whenever(samplingCallback.isSamplingEnabled).thenReturn(true) + regionSamplingHelper = object : RegionSamplingHelper(sampledView, samplingCallback, + DirectExecutor.INSTANCE, DirectExecutor.INSTANCE, compositionListener) { + override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl { + return wrappedSurfaceControl + } + } + regionSamplingHelper.setWindowVisible(true) + } + + @Test + fun testStart_register() { + regionSamplingHelper.start(Rect(0, 0, 100, 100)) + verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl), any()) + } + + @Test + fun testStart_unregister() { + regionSamplingHelper.start(Rect(0, 0, 100, 100)) + regionSamplingHelper.setWindowVisible(false) + verify(compositionListener).unregister(any()) + } + + @Test + fun testStart_hasBlur_neverRegisters() { + regionSamplingHelper.setWindowHasBlurs(true) + regionSamplingHelper.start(Rect(0, 0, 100, 100)) + verify(compositionListener, never()) + .register(any(), anyInt(), eq(wrappedSurfaceControl), any()) + } + + @Test + fun testStart_stopAndDestroy() { + regionSamplingHelper.start(Rect(0, 0, 100, 100)) + regionSamplingHelper.stopAndDestroy() + verify(compositionListener).unregister(any()) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 7f357324bed0..cf1a36af24ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -24,6 +24,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE; +import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT; @@ -86,7 +87,6 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; -import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -132,8 +132,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock - private LockIcon mLockIcon; - @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -738,6 +736,41 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyHideIndication(INDICATION_TYPE_OWNER_INFO); } + @Test + public void testOnKeyguardShowingChanged_notShowing_resetsMessages() { + createController(); + + // GIVEN keyguard isn't showing + when(mKeyguardStateController.isShowing()).thenReturn(false); + + // WHEN keyguard showing changed called + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + // THEN messages are reset + verify(mRotateTextViewController).clearMessages(); + assertThat(mTextView.getText()).isEqualTo(""); + } + + @Test + public void testOnKeyguardShowingChanged_showing_updatesPersistentMessages() { + createController(); + + // GIVEN keyguard is showing + when(mKeyguardStateController.isShowing()).thenReturn(true); + + // WHEN keyguard showing changed called + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + // THEN persistent messages are updated (in this case, most messages are hidden since + // no info is provided) - verify that this happens + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_DISCLOSURE); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_OWNER_INFO); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_BATTERY); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_TRUST); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_ALIGNMENT); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_LOGOUT); + } + private void sendUpdateDisclosureBroadcast() { mBroadcastReceiver.onReceive(mContext, new Intent()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt new file mode 100644 index 000000000000..6971c63ed6d4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt @@ -0,0 +1,44 @@ +package com.android.systemui.statusbar + +import android.testing.AndroidTestingRunner +import android.util.DisplayMetrics +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.LogBuffer +import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.phone.LSShadeTransitionLogger +import com.android.systemui.statusbar.phone.LockscreenGestureLogger +import com.android.systemui.util.mockito.mock +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class LSShadeTransitionLoggerTest : SysuiTestCase() { + lateinit var logger: LSShadeTransitionLogger + @Mock + lateinit var gestureLogger: LockscreenGestureLogger + @Mock + lateinit var displayMetrics: DisplayMetrics + @JvmField @Rule + val mockito = MockitoJUnit.rule() + + @Before + fun setup() { + logger = LSShadeTransitionLogger( + LogBuffer("Test", 10, 10, mock()), + gestureLogger, + displayMetrics) + } + + @Test + fun testLogDragDownStarted() { + val view: ExpandableView = mock() + // log a non-null, non row, ensure no crash + logger.logDragDownStarted(view) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 13b8e81a0711..42647f7b026a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -4,13 +4,12 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper -import android.util.DisplayMetrics import com.android.systemui.ExpandHelper import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dump.DumpManager -import com.android.systemui.log.LogBuffer +import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.media.MediaHierarchyManager import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QS @@ -64,12 +63,11 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Mock lateinit var lockScreenUserManager: NotificationLockscreenUserManager @Mock lateinit var falsingCollector: FalsingCollector @Mock lateinit var ambientState: AmbientState - @Mock lateinit var displayMetrics: DisplayMetrics + @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock lateinit var mediaHierarchyManager: MediaHierarchyManager @Mock lateinit var scrimController: ScrimController @Mock lateinit var configurationController: ConfigurationController @Mock lateinit var falsingManager: FalsingManager - @Mock lateinit var buffer: LogBuffer @Mock lateinit var notificationPanelController: NotificationPanelViewController @Mock lateinit var nsslController: NotificationStackScrollLayoutController @Mock lateinit var depthController: NotificationShadeDepthController @@ -98,6 +96,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { mediaHierarchyManager = mediaHierarchyManager, scrimController = scrimController, depthController = depthController, + wakefulnessLifecycle = wakefulnessLifecycle, context = context, configurationController = configurationController, falsingManager = falsingManager, @@ -148,6 +147,23 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { } @Test + fun testWakingToShadeLockedWhenDozing() { + whenever(statusbarStateController.isDozing).thenReturn(true) + transitionController.goToLockedShade(null) + verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED) + assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked) + } + + @Test + fun testNotWakingToShadeLockedWhenNotDozing() { + whenever(statusbarStateController.isDozing).thenReturn(false) + transitionController.goToLockedShade(null) + verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED) + assertFalse("Waking to shade locked when not dozing", + transitionController.isWakingToShadeLocked) + } + + @Test fun testGoToLockedShadeOnlyOnKeyguard() { whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) transitionController.goToLockedShade(null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index b31dd3c155f4..eef9dd46509f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -23,7 +23,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; @@ -209,8 +209,6 @@ public class NetworkControllerBaseTest extends SysuiTestCase { when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true); doAnswer(invocation -> { mUserCallback = (DeviceProvisionedListener) invocation.getArguments()[0]; - mUserCallback.onUserSetupChanged(); - mUserCallback.onDeviceProvisionedChanged(); TestableLooper.get(this).processAllMessages(); return null; }).when(mMockProvisionController).addCallback(any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java index 73eddd166f88..6262a9b628f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java @@ -35,6 +35,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import com.android.settingslib.graph.SignalDrawable; @@ -60,6 +61,72 @@ import java.util.List; public class NetworkControllerSignalTest extends NetworkControllerBaseTest { @Test + public void testDeviceProvisioned_userNotSetUp() { + // GIVEN - user is not setup + when(mMockProvisionController.isCurrentUserSetup()).thenReturn(false); + + // WHEN - a NetworkController is created + mNetworkController = new NetworkControllerImpl(mContext, + mMockCm, + mMockTm, + mTelephonyListenerManager, + mMockWm, + mMockNsm, + mMockSm, + mConfig, + TestableLooper.get(this).getLooper(), + mFakeExecutor, + mCallbackHandler, + mock(AccessPointControllerImpl.class), + mock(DataUsageController.class), + mMockSubDefaults, + mMockProvisionController, + mMockBd, + mDemoModeController, + mCarrierConfigTracker, + mFeatureFlags, + mock(DumpManager.class) + ); + TestableLooper.get(this).processAllMessages(); + + // THEN - NetworkController claims the user is not setup + assertFalse("User has not been set up", mNetworkController.isUserSetup()); + } + + @Test + public void testDeviceProvisioned_userSetUp() { + // GIVEN - user is not setup + when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true); + + // WHEN - a NetworkController is created + mNetworkController = new NetworkControllerImpl(mContext, + mMockCm, + mMockTm, + mTelephonyListenerManager, + mMockWm, + mMockNsm, + mMockSm, + mConfig, + TestableLooper.get(this).getLooper(), + mFakeExecutor, + mCallbackHandler, + mock(AccessPointControllerImpl.class), + mock(DataUsageController.class), + mMockSubDefaults, + mMockProvisionController, + mMockBd, + mDemoModeController, + mCarrierConfigTracker, + mFeatureFlags, + mock(DumpManager.class) + ); + TestableLooper.get(this).processAllMessages(); + + // THEN - NetworkController claims the user is not setup + assertTrue("User has been set up", mNetworkController.isUserSetup()); + } + + @Test public void testNoIconWithoutMobile() { // Turn off mobile network support. when(mMockTm.isDataCapable()).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index dc83c0d08291..f2b7bf515c45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; @@ -428,6 +429,18 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + public void testNotifyChannelModified_notifiesListeners() { + NotificationChannel channel = mock(NotificationChannel.class); + String pkg = "PKG"; + mEntryManager.notifyChannelModified(pkg, UserHandle.CURRENT, channel, + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + verify(mNotifCollectionListener).onNotificationChannelModified(eq(pkg), + eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + verify(mEntryListener).onNotificationChannelModified(eq(pkg), + eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() { // GIVEN an entry manager with a notification mEntryManager.addActiveNotificationForTest(mEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index a7f8b6e01949..706800940fd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CLICK; @@ -53,6 +54,8 @@ import static java.util.Objects.requireNonNull; import android.annotation.Nullable; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.os.Handler; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; @@ -336,6 +339,37 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testEventDispatchedWhenChannelChanged() { + // GIVEN a collection with one notif that has a channel + NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48); + NotificationChannel channel = new NotificationChannel( + "channelId", + "channelName", + NotificationManager.IMPORTANCE_DEFAULT); + neb.setChannel(channel); + + NotifEvent notif = mNoMan.postNotif(neb); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + clearInvocations(mCollectionListener); + + + // WHEN a notif channel is modified + channel.setAllowBubbles(true); + mNoMan.issueChannelModification( + TEST_PACKAGE, + entry.getSbn().getUser(), + channel, + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + // THEN the listener is notified + mListenerInOrder.verify(mCollectionListener).onNotificationChannelModified( + TEST_PACKAGE, + entry.getSbn().getUser(), + channel, + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + } + + @Test public void testRankingsAreUpdatedForOtherNotifs() { // GIVEN a collection with one notif NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) @@ -459,7 +493,7 @@ public class NotifCollectionTest extends SysuiTestCase { mCollection.dismissNotification(entry1, defaultStats(entry1)); // THEN lifetime extenders are never queried - verify(mExtender1, never()).shouldExtendLifetime(eq(entry1), anyInt()); + verify(mExtender1, never()).maybeExtendLifetime(eq(entry1), anyInt()); } @Test @@ -912,9 +946,9 @@ public class NotifCollectionTest extends SysuiTestCase { mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); // THEN each extender is asked whether to extend, even if earlier ones return true - verify(mExtender1).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender2).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender1).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3).maybeExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry is not removed assertTrue(mCollection.getAllNotifs().contains(entry2)); @@ -948,9 +982,9 @@ public class NotifCollectionTest extends SysuiTestCase { mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); // THEN each extender is re-queried - verify(mExtender1).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender2).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender1).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3).maybeExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry is not removed assertTrue(mCollection.getAllNotifs().contains(entry2)); @@ -986,9 +1020,9 @@ public class NotifCollectionTest extends SysuiTestCase { assertTrue(mCollection.getAllNotifs().contains(entry2)); // THEN we don't re-query the extenders - verify(mExtender1, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender2, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender3, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender1, never()).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2, never()).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3, never()).maybeExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry properly records all extenders that returned true assertEquals(singletonList(mExtender1), entry2.mLifetimeExtenders); @@ -1469,6 +1503,18 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testCannotDismissNoClearNotifications() { + // GIVEN an no-clear notification + final NotificationEntry container = new NotificationEntryBuilder() + .setFlag(mContext, FLAG_NO_CLEAR, true) + .build(); + + // THEN its children are not dismissible + assertFalse(mCollection.shouldAutoDismissChildren( + container, container.getSbn().getGroupKey())); + } + + @Test public void testCanDismissFgsNotificationChildren() { // GIVEN an FGS but not ongoing notification final NotificationEntry container = new NotificationEntryBuilder() @@ -1585,7 +1631,7 @@ public class NotifCollectionTest extends SysuiTestCase { } @Override - public boolean shouldExtendLifetime( + public boolean maybeExtendLifetime( @NonNull NotificationEntry entry, @CancellationReason int reason) { return shouldExtendLifetime; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt index 0f6bd771d352..4143647592e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt @@ -73,43 +73,43 @@ class GutsCoordinatorTest : SysuiTestCase() { @Test fun testSimpleLifetimeExtension() { - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() } @Test fun testDoubleOpenLifetimeExtension() { - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() } @Test fun testTwoEntryLifetimeExtension() { - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry2, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isTrue() notifGutsViewListener.onGutsClose(entry2) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry2) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java deleted file mode 100644 index b3ee5f8373e4..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.render.NodeController; -import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class HeadsUpCoordinatorTest extends SysuiTestCase { - - private HeadsUpCoordinator mCoordinator; - - // captured listeners and pluggables: - private NotifCollectionListener mCollectionListener; - private NotifPromoter mNotifPromoter; - private NotifLifetimeExtender mNotifLifetimeExtender; - private OnHeadsUpChangedListener mOnHeadsUpChangedListener; - private NotifSectioner mNotifSectioner; - - @Mock private NotifPipeline mNotifPipeline; - @Mock private HeadsUpManager mHeadsUpManager; - @Mock private HeadsUpViewBinder mHeadsUpViewBinder; - @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider; - @Mock private NotificationRemoteInputManager mRemoteInputManager; - @Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; - @Mock private NodeController mHeaderController; - - private NotificationEntry mEntry; - private final FakeSystemClock mClock = new FakeSystemClock(); - private final FakeExecutor mExecutor = new FakeExecutor(mClock); - private final ArrayList<NotificationEntry> mHuns = new ArrayList(); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mCoordinator = new HeadsUpCoordinator( - mHeadsUpManager, - mHeadsUpViewBinder, - mNotificationInterruptStateProvider, - mRemoteInputManager, - mHeaderController, - mExecutor); - - mCoordinator.attach(mNotifPipeline); - - // capture arguments: - ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor = - ArgumentCaptor.forClass(NotifCollectionListener.class); - ArgumentCaptor<NotifPromoter> notifPromoterCaptor = - ArgumentCaptor.forClass(NotifPromoter.class); - ArgumentCaptor<NotifLifetimeExtender> notifLifetimeExtenderCaptor = - ArgumentCaptor.forClass(NotifLifetimeExtender.class); - ArgumentCaptor<OnHeadsUpChangedListener> headsUpChangedListenerCaptor = - ArgumentCaptor.forClass(OnHeadsUpChangedListener.class); - - verify(mNotifPipeline).addCollectionListener(notifCollectionCaptor.capture()); - verify(mNotifPipeline).addPromoter(notifPromoterCaptor.capture()); - verify(mNotifPipeline).addNotificationLifetimeExtender( - notifLifetimeExtenderCaptor.capture()); - verify(mHeadsUpManager).addListener(headsUpChangedListenerCaptor.capture()); - - given(mHeadsUpManager.getAllEntries()).willAnswer(i -> mHuns.stream()); - given(mHeadsUpManager.isAlerting(anyString())).willAnswer(i -> { - String key = i.getArgument(0); - for (NotificationEntry entry : mHuns) { - if (entry.getKey().equals(key)) return true; - } - return false; - }); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L); - - mCollectionListener = notifCollectionCaptor.getValue(); - mNotifPromoter = notifPromoterCaptor.getValue(); - mNotifLifetimeExtender = notifLifetimeExtenderCaptor.getValue(); - mOnHeadsUpChangedListener = headsUpChangedListenerCaptor.getValue(); - - mNotifSectioner = mCoordinator.getSectioner(); - mNotifLifetimeExtender.setCallback(mEndLifetimeExtension); - mEntry = new NotificationEntryBuilder().build(); - } - - @Test - public void testCancelStickyNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(true); - addHUN(mEntry); - when(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L); - assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(1)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testCancelUpdatedStickyNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(true); - addHUN(mEntry); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L); - assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testCancelNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(false); - addHUN(mEntry); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L); - assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(1)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testPromotesCurrentHUN() { - // GIVEN the current HUN is set to mEntry - addHUN(mEntry); - - // THEN only promote the current HUN, mEntry - assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)); - assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder() - .setPkg("test-package2") - .build())); - } - - @Test - public void testIncludeInSectionCurrentHUN() { - // GIVEN the current HUN is set to mEntry - addHUN(mEntry); - - // THEN only section the current HUN, mEntry - assertTrue(mNotifSectioner.isInSection(mEntry)); - assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder() - .setPkg("test-package") - .build())); - } - - @Test - public void testLifetimeExtendsCurrentHUN() { - // GIVEN there is a HUN, mEntry - addHUN(mEntry); - - given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer(i -> { - String key = i.getArgument(0); - for (NotificationEntry entry : mHuns) { - if (entry.getKey().equals(key)) return false; - } - return true; - }); - // THEN only the current HUN, mEntry, should be lifetimeExtended - assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, /* cancellationReason */ 0)); - assertFalse(mNotifLifetimeExtender.shouldExtendLifetime( - new NotificationEntryBuilder() - .setPkg("test-package") - .build(), /* cancellationReason */ 0)); - } - - @Test - public void testShowHUNOnInflationFinished() { - // WHEN a notification should HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true); - - ArgumentCaptor<BindCallback> bindCallbackCaptor = - ArgumentCaptor.forClass(BindCallback.class); - mCollectionListener.onEntryAdded(mEntry); - verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), bindCallbackCaptor.capture()); - - bindCallbackCaptor.getValue().onBindFinished(mEntry); - - // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager).showNotification(mEntry); - } - - @Test - public void testNoHUNOnInflationFinished() { - // WHEN a notification shouldn't HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false); - ArgumentCaptor<BindCallback> bindCallbackCaptor = - ArgumentCaptor.forClass(BindCallback.class); - mCollectionListener.onEntryAdded(mEntry); - - // THEN we never bind the heads up view or tell HeadsUpManager to show the notification - verify(mHeadsUpViewBinder, never()).bindHeadsUpView( - eq(mEntry), bindCallbackCaptor.capture()); - verify(mHeadsUpManager, never()).showNotification(mEntry); - } - - @Test - public void testOnEntryRemovedRemovesHeadsUpNotification() { - // GIVEN the current HUN is mEntry - addHUN(mEntry); - - // WHEN mEntry is removed from the notification collection - mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0); - when(mRemoteInputManager.isSpinning(any())).thenReturn(false); - - // THEN heads up manager should remove the entry - verify(mHeadsUpManager).removeNotification(mEntry.getKey(), false); - } - - private void addHUN(NotificationEntry entry) { - mHuns.add(entry); - when(mHeadsUpManager.getTopEntry()).thenReturn(entry); - mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, entry != null); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt new file mode 100644 index 000000000000..c67a2331b023 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.BDDMockito.given +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.ArrayList +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class HeadsUpCoordinatorTest : SysuiTestCase() { + private lateinit var mCoordinator: HeadsUpCoordinator + + // captured listeners and pluggables: + private lateinit var mCollectionListener: NotifCollectionListener + private lateinit var mNotifPromoter: NotifPromoter + private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender + private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener + private lateinit var mNotifSectioner: NotifSectioner + + private val mNotifPipeline: NotifPipeline = mock() + private val mHeadsUpManager: HeadsUpManager = mock() + private val mHeadsUpViewBinder: HeadsUpViewBinder = mock() + private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock() + private val mRemoteInputManager: NotificationRemoteInputManager = mock() + private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock() + private val mHeaderController: NodeController = mock() + + private lateinit var mEntry: NotificationEntry + private val mExecutor = FakeExecutor(FakeSystemClock()) + private val mHuns: ArrayList<NotificationEntry> = ArrayList() + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mCoordinator = HeadsUpCoordinator( + mHeadsUpManager, + mHeadsUpViewBinder, + mNotificationInterruptStateProvider, + mRemoteInputManager, + mHeaderController, + mExecutor) + mCoordinator.attach(mNotifPipeline) + + // capture arguments: + mCollectionListener = withArgCaptor { + verify(mNotifPipeline).addCollectionListener(capture()) + } + mNotifPromoter = withArgCaptor { + verify(mNotifPipeline).addPromoter(capture()) + } + mNotifLifetimeExtender = withArgCaptor { + verify(mNotifPipeline).addNotificationLifetimeExtender(capture()) + } + mOnHeadsUpChangedListener = withArgCaptor { + verify(mHeadsUpManager).addListener(capture()) + } + given(mHeadsUpManager.allEntries).willAnswer { mHuns.stream() } + given(mHeadsUpManager.isAlerting(anyString())).willAnswer { invocation -> + val key = invocation.getArgument<String>(0) + mHuns.any { entry -> entry.key == key } + } + given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer { invocation -> + val key = invocation.getArgument<String>(0) + !mHuns.any { entry -> entry.key == key } + } + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) + mNotifSectioner = mCoordinator.sectioner + mNotifLifetimeExtender.setCallback(mEndLifetimeExtension) + mEntry = NotificationEntryBuilder().build() + } + + @Test + fun testCancelStickyNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(mEntry) + whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testCancelUpdatedStickyNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(mEntry) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testCancelNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(false) + addHUN(mEntry) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testPromotesCurrentHUN() { + // GIVEN the current HUN is set to mEntry + addHUN(mEntry) + + // THEN only promote the current HUN, mEntry + assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)) + assertFalse(mNotifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder() + .setPkg("test-package2") + .build())) + } + + @Test + fun testIncludeInSectionCurrentHUN() { + // GIVEN the current HUN is set to mEntry + addHUN(mEntry) + + // THEN only section the current HUN, mEntry + assertTrue(mNotifSectioner.isInSection(mEntry)) + assertFalse(mNotifSectioner.isInSection(NotificationEntryBuilder() + .setPkg("test-package") + .build())) + } + + @Test + fun testLifetimeExtendsCurrentHUN() { + // GIVEN there is a HUN, mEntry + addHUN(mEntry) + + // THEN only the current HUN, mEntry, should be lifetimeExtended + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0)) + assertFalse(mNotifLifetimeExtender.maybeExtendLifetime( + NotificationEntryBuilder() + .setPkg("test-package") + .build(), /* cancellationReason */ 0)) + } + + @Test + fun testShowHUNOnInflationFinished() { + // WHEN a notification should HUN and its inflation is finished + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) + + mCollectionListener.onEntryAdded(mEntry) + withArgCaptor<BindCallback> { + verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture()) + }.onBindFinished(mEntry) + + // THEN we tell the HeadsUpManager to show the notification + verify(mHeadsUpManager).showNotification(mEntry) + } + + @Test + fun testNoHUNOnInflationFinished() { + // WHEN a notification shouldn't HUN and its inflation is finished + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false) + mCollectionListener.onEntryAdded(mEntry) + + // THEN we never bind the heads up view or tell HeadsUpManager to show the notification + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any()) + verify(mHeadsUpManager, never()).showNotification(mEntry) + } + + @Test + fun testOnEntryRemovedRemovesHeadsUpNotification() { + // GIVEN the current HUN is mEntry + addHUN(mEntry) + + // WHEN mEntry is removed from the notification collection + mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0) + whenever(mRemoteInputManager.isSpinning(any())).thenReturn(false) + + // THEN heads up manager should remove the entry + verify(mHeadsUpManager).removeNotification(mEntry.key, false) + } + + private fun addHUN(entry: NotificationEntry) { + mHuns.add(entry) + whenever(mHeadsUpManager.topEntry).thenReturn(entry) + mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index f70330dbe506..bde6734bbf92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.SectionClassifier; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; @@ -92,6 +93,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Mock private NotifSection mNotifSection; @Mock private NotifPipeline mNotifPipeline; @Mock private IStatusBarService mService; + @Mock private ConversationNotificationManager mConvoManager; @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater(); private final SectionClassifier mSectionClassifier = new SectionClassifier(); private final NotifUiAdjustmentProvider mAdjustmentProvider = @@ -119,6 +121,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mock(NotifViewBarn.class), mAdjustmentProvider, mService, + mConvoManager, TEST_CHILD_BIND_CUTOFF, TEST_MAX_GROUP_DELAY); @@ -405,6 +408,13 @@ public class PreparationCoordinatorTest extends SysuiTestCase { } @Test + public void testCallConversationManagerBindWhenInflated() { + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + mNotifInflater.getInflateCallback(mEntry).onInflationFinished(mEntry, null); + verify(mConvoManager, times(1)).onEntryViewBound(eq(mEntry)); + } + + @Test public void testPartiallyInflatedGroupsAreReleasedAfterTimeout() { // GIVEN a newly-posted group with a summary and two children final GroupEntry group = new GroupEntryBuilder() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt index 0ce6ada51f23..7073cc7c5707 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt @@ -101,27 +101,27 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { @Test fun testRemoteInputActive() { `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true) - assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isTrue() - assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isFalse() } @Test fun testRemoteInputHistory() { `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry1)).thenReturn(true) - assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isTrue() - assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isTrue() } @Test fun testSmartReplyHistory() { `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry1)).thenReturn(true) - assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isTrue() assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isTrue() } @@ -136,7 +136,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { verify(lifetimeExtensionCallback, never()).onEndLifetimeExtension(any(), any()) // Start extending lifetime & validate that the extension is ended - assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue() assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isTrue() listener.onPanelCollapsed() verify(lifetimeExtensionCallback).onEndLifetimeExtension(remoteInputActiveExtender, entry1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt new file mode 100644 index 000000000000..c17fe6f8b7e2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt @@ -0,0 +1,294 @@ +/* + * 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.systemui.statusbar.notification.collection.legacy + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.GroupEventDispatcher +import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup +import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener +import com.android.systemui.statusbar.phone.NotificationGroupTestHelper +import com.android.systemui.util.mockito.mock +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class GroupEventDispatcherTest : SysuiTestCase() { + val groupMap = mutableMapOf<String, NotificationGroup>() + val groupTestHelper = NotificationGroupTestHelper(mContext) + + private val dispatcher = GroupEventDispatcher(groupMap::get) + private val listener: OnGroupChangeListener = mock() + + @Before + fun setup() { + dispatcher.registerGroupChangeListener(listener) + } + + @Test + fun testOnGroupsChangedUnbuffered() { + dispatcher.notifyGroupsChanged() + verify(listener).onGroupsChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupsChangedBuffered() { + dispatcher.openBufferScope() + dispatcher.notifyGroupsChanged() + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupsChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupsChangedDoubleBuffered() { + dispatcher.openBufferScope() + dispatcher.notifyGroupsChanged() + dispatcher.openBufferScope() // open a nested buffer scope + dispatcher.notifyGroupsChanged() + dispatcher.closeBufferScope() // should NOT flush events + dispatcher.notifyGroupsChanged() + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() // this SHOULD flush events + verify(listener).onGroupsChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupsChangedBufferCoalesces() { + dispatcher.openBufferScope() + dispatcher.notifyGroupsChanged() + dispatcher.notifyGroupsChanged() + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupsChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupCreatedIsNeverBuffered() { + val group = addGroup(1) + + dispatcher.openBufferScope() + dispatcher.notifyGroupCreated(group) + verify(listener).onGroupCreated(group, group.groupKey) + verifyNoMoreInteractions(listener) + + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupRemovedIsNeverBuffered() { + val group = addGroup(1) + + dispatcher.openBufferScope() + dispatcher.notifyGroupRemoved(group) + verify(listener).onGroupRemoved(group, group.groupKey) + verifyNoMoreInteractions(listener) + + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideAddedUnbuffered() { + val group = addGroup(1) + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, null) + verify(listener).onGroupAlertOverrideChanged(group, null, newAlertEntry) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideRemovedUnbuffered() { + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, null) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideChangedUnbuffered() { + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideChangedBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideIgnoredIfRemoved() { + dispatcher.openBufferScope() + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + verifyNoMoreInteractions(listener) + groupMap.clear() + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideMultipleChangesBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = null + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, null) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideTemporaryValueSwallowed() { + dispatcher.openBufferScope() + val group = addGroup(1) + val stableAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = null + dispatcher.notifyAlertOverrideChanged(group, stableAlertEntry) + group.alertOverride = stableAlertEntry + dispatcher.notifyAlertOverrideChanged(group, null) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideTemporaryNullSwallowed() { + dispatcher.openBufferScope() + val group = addGroup(1) + val temporaryAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = temporaryAlertEntry + dispatcher.notifyAlertOverrideChanged(group, null) + group.alertOverride = null + dispatcher.notifyAlertOverrideChanged(group, temporaryAlertEntry) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnUnbuffered() { + val group = addGroup(1) + group.suppressed = true + dispatcher.notifySuppressedChanged(group) + verify(listener).onGroupSuppressionChanged(group, true) + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOffUnbuffered() { + val group = addGroup(1) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + verify(listener).onGroupSuppressionChanged(group, false) + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupSuppressionChanged(group, false) + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnIgnoredIfRemoved() { + dispatcher.openBufferScope() + val group = addGroup(1) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + verifyNoMoreInteractions(listener) + groupMap.clear() + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnOffBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + group.suppressed = true + dispatcher.notifySuppressedChanged(group) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnOffOnBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + group.suppressed = true + dispatcher.notifySuppressedChanged(group) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + group.suppressed = true + dispatcher.notifySuppressedChanged(group) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupSuppressionChanged(group, true) + verifyNoMoreInteractions(listener) + } + + private fun addGroup(id: Int): NotificationGroup { + val group = NotificationGroup("group:$id") + groupMap[group.groupKey] = group + return group + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt index 37ad8357aa95..a09f3a35c308 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt @@ -77,7 +77,7 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testNoExtend() { `when`(shouldExtend.test(entry1)).thenReturn(false) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(extender.isExtending(entry1.key)).isFalse() verify(onStarted, never()).accept(entry1) verify(onCanceled, never()).accept(entry1) @@ -86,7 +86,7 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendThenCancelForRepost() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted).accept(entry1) verify(onCanceled, never()).accept(entry1) assertThat(extender.isExtending(entry1.key)).isTrue() @@ -108,7 +108,7 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendThenEnd() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted).accept(entry1) assertThat(extender.isExtending(entry1.key)).isTrue() extender.endLifetimeExtension(entry1.key) @@ -119,7 +119,7 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendThenEndAfterDelay() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted).accept(entry1) assertThat(extender.isExtending(entry1.key)).isTrue() @@ -142,11 +142,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { fun testExtendThenEndAll() { `when`(shouldExtend.test(entry1)).thenReturn(true) `when`(shouldExtend.test(entry2)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted).accept(entry1) assertThat(extender.isExtending(entry1.key)).isTrue() assertThat(extender.isExtending(entry2.key)).isFalse() - assertThat(extender.shouldExtendLifetime(entry2, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry2, 0)).isTrue() verify(onStarted).accept(entry2) assertThat(extender.isExtending(entry1.key)).isTrue() assertThat(extender.isExtending(entry2.key)).isTrue() @@ -160,11 +160,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendWithinEndCanReExtend() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted, times(1)).accept(entry1) `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() } extender.endLifetimeExtension(entry1.key) verify(onStarted, times(2)).accept(entry1) @@ -174,11 +174,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendWithinEndCanNotReExtend() { `when`(shouldExtend.test(entry1)).thenReturn(true, false) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted, times(1)).accept(entry1) `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { - assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isFalse() } extender.endLifetimeExtension(entry1.key) verify(onStarted, times(1)).accept(entry1) @@ -188,11 +188,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendWithinEndAllCanReExtend() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted, times(1)).accept(entry1) `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() } extender.endAllLifetimeExtensions() verify(onStarted, times(2)).accept(entry1) @@ -202,11 +202,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendWithinEndAllCanNotReExtend() { `when`(shouldExtend.test(entry1)).thenReturn(true, false) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted, times(1)).accept(entry1) `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { - assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isFalse() } extender.endAllLifetimeExtensions() verify(onStarted, times(1)).accept(entry1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 37cf7485b8ab..36a4c1e5ebfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -354,7 +354,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { TestNotificationPanelViewStateProvider() {} private float mPanelViewExpandedHeight = 100f; - private float mQsExpansionFraction = 0f; private boolean mShouldHeadsUpBeVisible = false; private float mLockscreenShadeDragProgress = 0f; @@ -364,11 +363,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Override - public float getQsExpansionFraction() { - return mQsExpansionFraction; - } - - @Override public boolean shouldHeadsUpBeVisible() { return mShouldHeadsUpBeVisible; } @@ -382,10 +376,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { this.mPanelViewExpandedHeight = panelViewExpandedHeight; } - public void setQsExpansionFraction(float qsExpansionFraction) { - this.mQsExpansionFraction = qsExpansionFraction; - } - public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) { this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index 9898b4b2fdbd..c13b3358a077 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -312,10 +312,11 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { @Test public void testUpdateChildToSummaryDoesNotTransfer() { + final String tag = "fooTag"; NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = - mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47); + mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47, tag); mockHasHeadsUpContentView(childEntry, false); mHeadsUpManager.showNotification(summaryEntry); @@ -327,7 +328,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { StatusBarNotification oldNotification = childEntry.getSbn(); childEntry.setSbn( mGroupTestHelper.createSummaryNotification( - Notification.GROUP_ALERT_SUMMARY, 47).getSbn()); + Notification.GROUP_ALERT_SUMMARY, 47, tag).getSbn()); mGroupManager.onEntryUpdated(childEntry, oldNotification); assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java index 1be27da27d25..5d7b15424fec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java @@ -21,14 +21,19 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.Notification; +import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Log; import androidx.test.filters.SmallTest; @@ -64,8 +69,10 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { private final NotificationGroupTestHelper mGroupTestHelper = new NotificationGroupTestHelper(mContext); - @Mock PeopleNotificationIdentifier mPeopleNotificationIdentifier; - @Mock HeadsUpManager mHeadsUpManager; + @Mock + PeopleNotificationIdentifier mPeopleNotificationIdentifier; + @Mock + HeadsUpManager mHeadsUpManager; @Before public void setup() { @@ -177,21 +184,81 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { helpTestAlertOverrideWithSiblings(2); } + @Test + public void testAlertOverrideWithSiblings_3() { + helpTestAlertOverrideWithSiblings(3); + } + + @Test + public void testAlertOverrideWithSiblings_9() { + helpTestAlertOverrideWithSiblings(9); + } + + /** + * Helper for testing various sibling counts + */ + private void helpTestAlertOverrideWithSiblings(int numSiblings) { + helpTestAlertOverride( + /* numSiblings */ numSiblings, + /* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* expectAlertOverride */ true); + } + + @Test + public void testAlertOverrideWithParentAlertAll() { + // tests that summary can have GROUP_ALERT_ALL and this still works + helpTestAlertOverride( + /* numSiblings */ 1, + /* summaryGroupAlert */ Notification.GROUP_ALERT_ALL, + /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* expectAlertOverride */ true); + } + + @Test + public void testAlertOverrideWithParentAlertChild() { + // Tests that if the summary alerts CHILDREN, there's no alertOverride + helpTestAlertOverride( + /* numSiblings */ 1, + /* summaryGroupAlert */ Notification.GROUP_ALERT_CHILDREN, + /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* expectAlertOverride */ false); + } + + @Test + public void testAlertOverrideWithChildrenAlertAll() { + // Tests that if the children alert ALL, there's no alertOverride + helpTestAlertOverride( + /* numSiblings */ 1, + /* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* priorityGroupAlert */ Notification.GROUP_ALERT_ALL, + /* siblingGroupAlert */ Notification.GROUP_ALERT_ALL, + /* expectAlertOverride */ false); + } + /** * This tests, for a group with a priority entry and the given number of siblings, that: * 1) the priority entry is identified as the alertOverride for the group * 2) the onAlertOverrideChanged method is called at that time * 3) when the priority entry is removed, these are reversed */ - private void helpTestAlertOverrideWithSiblings(int numSiblings) { - int groupAlert = Notification.GROUP_ALERT_SUMMARY; + private void helpTestAlertOverride(int numSiblings, + @Notification.GroupAlertBehavior int summaryGroupAlert, + @Notification.GroupAlertBehavior int priorityGroupAlert, + @Notification.GroupAlertBehavior int siblingGroupAlert, + boolean expectAlertOverride) { // Create entries in an order so that the priority entry can be deemed the newest child. NotificationEntry[] siblings = new NotificationEntry[numSiblings]; for (int i = 0; i < numSiblings; i++) { - siblings[i] = mGroupTestHelper.createChildNotification(groupAlert); + siblings[i] = mGroupTestHelper.createChildNotification(siblingGroupAlert, i, "sibling"); } - NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert); - NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert); + NotificationEntry priorityEntry = + mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority"); + NotificationEntry summaryEntry = + mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary"); // The priority entry is an important conversation. when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry))) @@ -208,10 +275,27 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { } mGroupManager.onEntryAdded(priorityEntry); + if (!expectAlertOverride) { + // Test expectation is that there will NOT be an alert, so verify that! + NotificationGroup summaryGroup = + mGroupManager.getGroupForSummary(summaryEntry.getSbn()); + assertNull(summaryGroup.alertOverride); + return; + } + int max2Siblings = Math.min(2, numSiblings); + // Verify that the summary group has the priority child as its alertOverride NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn()); assertEquals(priorityEntry, summaryGroup.alertOverride); verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry); + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true); + if (numSiblings > 1) { + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false); + } + verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey())); + verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged(); + verifyNoMoreInteractions(groupChangeListener); // Verify that only the priority notification is isolated from the group assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry)); @@ -227,6 +311,92 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { // verify that the alertOverride is removed when the priority notification is assertNull(summaryGroup.alertOverride); + verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null); + verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged(); + if (numSiblings == 0) { + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false); + } + verifyNoMoreInteractions(groupChangeListener); + } + + @Test + public void testAlertOverrideWhenUpdatingSummaryAtEnd() { + int numSiblings = 2; + int groupAlert = Notification.GROUP_ALERT_SUMMARY; + // Create entries in an order so that the priority entry can be deemed the newest child. + NotificationEntry[] siblings = new NotificationEntry[numSiblings]; + for (int i = 0; i < numSiblings; i++) { + siblings[i] = mGroupTestHelper.createChildNotification(groupAlert, i, "sibling"); + } + NotificationEntry priorityEntry = + mGroupTestHelper.createChildNotification(groupAlert, 0, "priority"); + NotificationEntry summaryEntry = + mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary"); + + // The priority entry is an important conversation. + when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry))) + .thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON); + + // Register a listener so we can verify that the event is sent. + OnGroupChangeListener groupChangeListener = mock(OnGroupChangeListener.class); + mGroupManager.registerGroupChangeListener(groupChangeListener); + + // Add all the entries. The order here shouldn't matter. + mGroupManager.onEntryAdded(summaryEntry); + for (int i = 0; i < numSiblings; i++) { + mGroupManager.onEntryAdded(siblings[i]); + } + mGroupManager.onEntryAdded(priorityEntry); + + int max2Siblings = Math.min(2, numSiblings); + + // Verify that the summary group has the priority child as its alertOverride + NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn()); + assertEquals(priorityEntry, summaryGroup.alertOverride); verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry); + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true); + if (numSiblings > 1) { + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false); + } + verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey())); + verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged(); + verifyNoMoreInteractions(groupChangeListener); + + // Verify that only the priority notification is isolated from the group + assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry)); + assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(priorityEntry)); + // Verify that the siblings are NOT isolated from the group + for (int i = 0; i < numSiblings; i++) { + assertEquals(summaryEntry, mGroupManager.getGroupSummary(siblings[i])); + assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(siblings[i])); + } + + Log.d("NotificationGroupManagerLegacyTest", + "testAlertOverrideWhenUpdatingSummaryAtEnd: About to update summary"); + + StatusBarNotification oldSummarySbn = mGroupTestHelper.incrementPost(summaryEntry, 10000); + mGroupManager.onEntryUpdated(summaryEntry, oldSummarySbn); + + verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged(); + verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null); + verifyNoMoreInteractions(groupChangeListener); + + Log.d("NotificationGroupManagerLegacyTest", + "testAlertOverrideWhenUpdatingSummaryAtEnd: About to update priority child"); + + StatusBarNotification oldPrioritySbn = mGroupTestHelper.incrementPost(priorityEntry, 10000); + mGroupManager.onEntryUpdated(priorityEntry, oldPrioritySbn); + + verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener, times(2)).onGroupCreated(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener, times(2)) + .onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry); + verify(groupChangeListener, times(max2Siblings + 3)).onGroupsChanged(); + verifyNoMoreInteractions(groupChangeListener); + + Log.d("NotificationGroupManagerLegacyTest", + "testAlertOverrideWhenUpdatingSummaryAtEnd: Done"); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java index d405fea78170..ac32b9d6f01a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,8 +25,10 @@ import android.app.ActivityManager; import android.app.Notification; import android.content.Context; import android.os.UserHandle; +import android.service.notification.StatusBarNotification; import com.android.systemui.R; +import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -44,15 +48,15 @@ public final class NotificationGroupTestHelper { } public NotificationEntry createSummaryNotification() { - return createSummaryNotification(Notification.GROUP_ALERT_ALL, mId++); + return createSummaryNotification(Notification.GROUP_ALERT_ALL, mId++, null); } public NotificationEntry createSummaryNotification(int groupAlertBehavior) { - return createSummaryNotification(groupAlertBehavior, mId++); + return createSummaryNotification(groupAlertBehavior, mId++, null); } - public NotificationEntry createSummaryNotification(int groupAlertBehavior, int id) { - return createEntry(id, true, groupAlertBehavior); + public NotificationEntry createSummaryNotification(int groupAlertBehavior, int id, String tag) { + return createEntry(id, tag, true, groupAlertBehavior); } public NotificationEntry createChildNotification() { @@ -60,14 +64,15 @@ public final class NotificationGroupTestHelper { } public NotificationEntry createChildNotification(int groupAlertBehavior) { - return createEntry(mId++, false, groupAlertBehavior); + return createEntry(mId++, null, false, groupAlertBehavior); } - public NotificationEntry createChildNotification(int groupAlertBehavior, int id) { - return createEntry(id, false, groupAlertBehavior); + public NotificationEntry createChildNotification(int groupAlertBehavior, int id, String tag) { + return createEntry(id, tag, false, groupAlertBehavior); } - public NotificationEntry createEntry(int id, boolean isSummary, int groupAlertBehavior) { + public NotificationEntry createEntry(int id, String tag, boolean isSummary, + int groupAlertBehavior) { Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentTitle("Title") .setSmallIcon(R.drawable.ic_person) @@ -80,6 +85,7 @@ public final class NotificationGroupTestHelper { .setOpPkg(TEST_PACKAGE_NAME) .setId(id) .setNotification(notif) + .setTag(tag) .setUser(new UserHandle(ActivityManager.getCurrentUser())) .build(); @@ -88,4 +94,16 @@ public final class NotificationGroupTestHelper { when(row.getEntry()).thenReturn(entry); return entry; } + + public StatusBarNotification incrementPost(NotificationEntry entry, int increment) { + StatusBarNotification oldSbn = entry.getSbn(); + final long oldPostTime = oldSbn.getPostTime(); + final long newPostTime = oldPostTime + increment; + entry.setSbn(new SbnBuilder(oldSbn) + .setPostTime(newPostTime) + .build()); + assertThat(oldSbn.getPostTime()).isEqualTo(oldPostTime); + assertThat(entry.getSbn().getPostTime()).isEqualTo(newPostTime); + return oldSbn; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index db7b2f20fa4c..a8522c787029 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -16,6 +16,10 @@ package com.android.systemui.statusbar.policy; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -25,14 +29,15 @@ import android.hardware.devicestate.DeviceStateManager; import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableContentResolver; import android.testing.TestableResources; import androidx.test.filters.SmallTest; +import com.android.internal.R; import com.android.internal.view.RotationPolicy; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wrapper.RotationPolicyWrapper; @@ -47,63 +52,55 @@ import org.mockito.MockitoAnnotations; @SmallTest public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase { - private static final String[] DEFAULT_SETTINGS = new String[]{ - "0:0", - "1:2" - }; + private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"}; - private final FakeSettings mFakeSettings = new FakeSettings(); private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); @Mock DeviceStateManager mDeviceStateManager; RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy(); DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; + private DeviceStateRotationLockSettingsManager mSettingsManager; + private TestableContentResolver mContentResolver; @Before public void setUp() { MockitoAnnotations.initMocks(/* testClass= */ this); TestableResources resources = mContext.getOrCreateTestableResources(); + resources.addOverride(R.array.config_perDeviceStateRotationLockDefaults, DEFAULT_SETTINGS); ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor = - ArgumentCaptor.forClass( - DeviceStateManager.DeviceStateCallback.class); + ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class); - mDeviceStateRotationLockSettingController = new DeviceStateRotationLockSettingController( - mFakeSettings, - mFakeRotationPolicy, - mDeviceStateManager, - mFakeExecutor, - DEFAULT_SETTINGS - ); + mContentResolver = mContext.getContentResolver(); + mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext); + mDeviceStateRotationLockSettingController = + new DeviceStateRotationLockSettingController( + mFakeRotationPolicy, mDeviceStateManager, mFakeExecutor, mSettingsManager); mDeviceStateRotationLockSettingController.setListening(true); - verify(mDeviceStateManager).registerCallback(any(), - deviceStateCallbackArgumentCaptor.capture()); + verify(mDeviceStateManager) + .registerCallback(any(), deviceStateCallbackArgumentCaptor.capture()); mDeviceStateCallback = deviceStateCallbackArgumentCaptor.getValue(); } @Test public void whenSavedSettingsEmpty_defaultsLoadedAndSaved() { - mFakeSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, "", - UserHandle.USER_CURRENT); + initializeSettingsWith(); - mDeviceStateRotationLockSettingController.initialize(); - - assertThat(mFakeSettings - .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - UserHandle.USER_CURRENT)) + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) .isEqualTo("0:0:1:2"); } @Test public void whenNoSavedValueForDeviceState_assumeIgnored() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:2:1:2", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); @@ -116,52 +113,43 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase @Test public void whenDeviceStateSwitched_loadCorrectSetting() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:2:1:1", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(0); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); - } @Test public void whenUserChangesSetting_saveSettingForCurrentState() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:1:1:2", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + mSettingsManager.onPersistedSettingsChanged(); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(0); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); - mDeviceStateRotationLockSettingController - .onRotationLockStateChanged(/* rotationLocked= */false, - /* affordanceVisible= */ true); + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ false, /* affordanceVisible= */ true); - assertThat(mFakeSettings - .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - UserHandle.USER_CURRENT)) + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) .isEqualTo("0:2:1:2"); } - @Test public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:0:1:2", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); @@ -172,12 +160,9 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase @Test public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:0:1:2", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); @@ -185,16 +170,52 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mDeviceStateCallback.onStateChanged(0); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - mDeviceStateRotationLockSettingController - .onRotationLockStateChanged(/* rotationLocked= */true, - /* affordanceVisible= */ true); + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ true, /* affordanceVisible= */ true); - assertThat(mFakeSettings - .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - UserHandle.USER_CURRENT)) + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) .isEqualTo("0:0:1:1"); } + @Test + public void whenSettingsChangedExternally_updateRotationPolicy() throws InterruptedException { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + mFakeRotationPolicy.setRotationLock(false); + mDeviceStateCallback.onStateChanged(0); + + assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); + + // Changing device state 0 to LOCKED + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + + assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); + } + + private void initializeSettingsWith(int... values) { + if (values.length % 2 != 0) { + throw new IllegalArgumentException("Expecting key-value pairs"); + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < values.length; sb.append(":")) { + sb.append(values[i++]).append(":").append(values[i++]); + } + + Settings.Secure.putStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + sb.toString(), + UserHandle.USER_CURRENT); + + mSettingsManager.onPersistedSettingsChanged(); + } + private static class FakeRotationPolicy implements RotationPolicyWrapper { private boolean mRotationLock; @@ -230,8 +251,8 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Override - public void registerRotationPolicyListener(RotationPolicy.RotationPolicyListener listener, - int userHandle) { + public void registerRotationPolicyListener( + RotationPolicy.RotationPolicyListener listener, int userHandle) { throw new AssertionError("Not implemented"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java index 087f2e6006cf..2126dda7b310 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Intent; +import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Handler; import android.os.UserHandle; @@ -68,6 +69,8 @@ public class LocationControllerImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); + when(mUserTracker.getUserProfiles()) + .thenReturn(ImmutableList.of(new UserInfo(0, "name", 0))); mDeviceConfigProxy = new DeviceConfigProxyFake(); mTestableLooper = TestableLooper.get(this); @@ -78,7 +81,8 @@ public class LocationControllerImplTest extends SysuiTestCase { new Handler(mTestableLooper.getLooper()), mock(BroadcastDispatcher.class), mock(BootCompleteCache.class), - mUserTracker); + mUserTracker, + mContext.getPackageManager()); mTestableLooper.processAllMessages(); } @@ -161,17 +165,38 @@ public class LocationControllerImplTest extends SysuiTestCase { @Test public void testCallbackNotified_additionalOps() { LocationChangeCallback callback = mock(LocationChangeCallback.class); - mLocationController.addCallback(callback); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, + "true", + true); + mTestableLooper.processAllMessages(); + + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", + System.currentTimeMillis()))); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", true); mTestableLooper.processAllMessages(); - mLocationController.onReceive(mContext, new Intent(LocationManager.MODE_CHANGED_ACTION)); + verify(callback, times(1)).onLocationActiveChanged(true); + when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of()); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", false); mTestableLooper.processAllMessages(); - verify(callback, times(2)).onLocationSettingsChanged(anyBoolean()); + verify(callback, times(1)).onLocationActiveChanged(false); + } + @Test + public void testCallbackNotified_additionalOps_shouldShowSystem() { + LocationChangeCallback callback = mock(LocationChangeCallback.class); + mLocationController.addCallback(callback); mDeviceConfigProxy.setProperty( DeviceConfig.NAMESPACE_PRIVACY, SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, @@ -181,10 +206,40 @@ public class LocationControllerImplTest extends SysuiTestCase { when(mAppOpsController.getActiveAppOps()) .thenReturn(ImmutableList.of( - new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "", + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.gms", System.currentTimeMillis()))); mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, - "", true); + "com.google.android.gms", true); + + mTestableLooper.processAllMessages(); + + verify(callback, times(0)).onLocationActiveChanged(true); + } + + + @Test + public void testCallbackNotified_additionalOps_shouldNotShowSystem() { + LocationChangeCallback callback = mock(LocationChangeCallback.class); + mLocationController.addCallback(callback); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, + "true", + true); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, + "true", + true); + mTestableLooper.processAllMessages(); + + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.google.android.gms", + System.currentTimeMillis()))); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.gms", true); mTestableLooper.processAllMessages(); @@ -192,7 +247,7 @@ public class LocationControllerImplTest extends SysuiTestCase { when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of()); mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, - "", false); + "com.google.android.gms", false); mTestableLooper.processAllMessages(); verify(callback, times(1)).onLocationActiveChanged(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index ba7bbfe11bdc..20a3fdaa99a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -102,7 +102,8 @@ public class RemoteInputViewTest extends SysuiTestCase { mReceiver = new BlockingQueueIntentReceiver(); mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION), null, - Handler.createAsync(Dependency.get(Dependency.BG_LOOPER))); + Handler.createAsync(Dependency.get(Dependency.BG_LOOPER)), + Context.RECEIVER_EXPORTED_UNAUDITED); // Avoid SecurityException RemoteInputView#sendRemoteInput(). mContext.addMockSystemService(ShortcutManager.class, mShortcutManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java index 0581264d18e2..ea620a6856f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.verifyZeroInteractions; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.testing.TestableResources; import androidx.test.filters.SmallTest; @@ -43,25 +42,19 @@ import org.mockito.MockitoAnnotations; @SmallTest public class RotationLockControllerImplTest extends SysuiTestCase { - private static final String[] DEFAULT_SETTINGS = new String[]{ - "0:0", - "1:2" - }; + private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"}; @Mock RotationPolicyWrapper mRotationPolicyWrapper; @Mock DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; - private TestableResources mResources; - private ArgumentCaptor<RotationPolicy.RotationPolicyListener> - mRotationPolicyListenerCaptor; + private ArgumentCaptor<RotationPolicy.RotationPolicyListener> mRotationPolicyListenerCaptor; @Before public void setUp() { MockitoAnnotations.initMocks(/* testClass= */ this); - mResources = mContext.getOrCreateTestableResources(); - mRotationPolicyListenerCaptor = ArgumentCaptor.forClass( - RotationPolicy.RotationPolicyListener.class); + mRotationPolicyListenerCaptor = + ArgumentCaptor.forClass(RotationPolicy.RotationPolicyListener.class); } @Test @@ -79,14 +72,7 @@ public class RotationLockControllerImplTest extends SysuiTestCase { } @Test - public void whenFlagOn_initializesDeviceStateRotationController() { - createRotationLockController(); - - verify(mDeviceStateRotationLockSettingController).initialize(); - } - - @Test - public void whenFlagOn_dviceStateRotationControllerAddedToCallbacks() { + public void whenFlagOn_deviceStateRotationControllerAddedToCallbacks() { createRotationLockController(); captureRotationPolicyListener().onChange(); @@ -103,11 +89,11 @@ public class RotationLockControllerImplTest extends SysuiTestCase { private void createRotationLockController() { createRotationLockController(DEFAULT_SETTINGS); } + private void createRotationLockController(String[] deviceStateRotationLockDefaults) { new RotationLockControllerImpl( mRotationPolicyWrapper, mDeviceStateRotationLockSettingController, - deviceStateRotationLockDefaults - ); + deviceStateRotationLockDefaults); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index 32aee2bd6554..0e25d24bea58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; @@ -119,7 +120,8 @@ public class SmartReplyViewTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mReceiver = new BlockingQueueIntentReceiver(); - mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION)); + mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION), + Context.RECEIVER_EXPORTED_UNAUDITED); mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> action.onDismiss()); mDependency.injectMockDependency(KeyguardUpdateMonitor.class); mDependency.injectMockDependency(ShadeController.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt index f600b12993b9..4e2736c74007 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt @@ -68,7 +68,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { context.mainExecutor, context, screenLifecycle - ) + ).apply { init() } deviceStates = FoldableTestUtils.findDeviceStates(context) verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 07351cf386ac..9c49e98b9e36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -18,6 +18,8 @@ package com.android.systemui.wmshell; import static android.app.Notification.FLAG_BUBBLE; import static android.app.PendingIntent.FLAG_MUTABLE; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; @@ -1285,6 +1287,58 @@ public class BubblesTest extends SysuiTestCase { assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } + @Test + public void testNotificationChannelModified_channelUpdated_removesOverflowBubble() + throws Exception { + // Setup + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + NotificationEntry entry = row.getEntry(); + entry.getChannel().setConversationId( + row.getEntry().getChannel().getParentChannelId(), + "shortcutId"); + mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(entry.getKey(), + Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); + + // Test + entry.getChannel().setDeleted(true); + mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), + entry.getSbn().getUser(), + entry.getChannel(), + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); + } + + @Test + public void testNotificationChannelModified_channelDeleted_removesOverflowBubble() + throws Exception { + // Setup + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + NotificationEntry entry = row.getEntry(); + entry.getChannel().setConversationId( + row.getEntry().getChannel().getParentChannelId(), + "shortcutId"); + mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(entry.getKey(), + Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); + + // Test + entry.getChannel().setDeleted(true); + mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), + entry.getSbn().getUser(), + entry.getChannel(), + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 9eee83ccb6e2..e12a82a1c62b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -17,6 +17,8 @@ package com.android.systemui.wmshell; import static android.app.Notification.FLAG_BUBBLE; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; @@ -1104,6 +1106,58 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } + @Test + public void testNotificationChannelModified_channelUpdated_removesOverflowBubble() + throws Exception { + // Setup + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + NotificationEntry entry = row.getEntry(); + entry.getChannel().setConversationId( + row.getEntry().getChannel().getParentChannelId(), + "shortcutId"); + mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(entry.getKey(), + Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); + + // Test + entry.getChannel().setDeleted(true); + mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), + entry.getSbn().getUser(), + entry.getChannel(), + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); + } + + @Test + public void testNotificationChannelModified_channelDeleted_removesOverflowBubble() + throws Exception { + // Setup + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + NotificationEntry entry = row.getEntry(); + entry.getChannel().setConversationId( + row.getEntry().getChannel().getParentChannelId(), + "shortcutId"); + mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(entry.getKey(), + Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); + + // Test + entry.getChannel().setDeleted(true); + mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), + entry.getSbn().getUser(), + entry.getChannel(), + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); + } + /** * Sets the bubble metadata flags for this entry. These flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not diff --git a/proto/src/camera.proto b/proto/src/camera.proto index d07a525fdffa..0338b93c8842 100644 --- a/proto/src/camera.proto +++ b/proto/src/camera.proto @@ -62,4 +62,7 @@ message CameraStreamProto { // The frame counts for each histogram bins // Expected number of fields: 10 repeated int64 histogram_counts = 13; + + // The dynamic range profile of the stream + optional int32 dynamic_range_profile = 14; } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 3047c9042848..196c6aaa7fcb 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -276,6 +276,12 @@ message SystemMessage { // Package: android NOTE_UNBLOCK_CAM_TOGGLE = 66; + // Notify the user that a CA certificate is pending for the wifi connection. + NOTE_SERVER_CA_CERTIFICATE = 67; + + // Notify the user to set up dream + NOTE_SETUP_DREAM = 68; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/Android.bp b/services/Android.bp index 74d7f654df16..f33c8c0dae15 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -77,6 +77,7 @@ filegroup { ":services.appwidget-sources", ":services.autofill-sources", ":services.backup-sources", + ":services.bluetooth-sources", // TODO(b/214988855) : Remove once apex/service-bluetooth jar is ready ":backuplib-sources", ":services.companion-sources", ":services.contentcapture-sources", diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index aa69a09034fc..91e909357bc4 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -48,6 +48,7 @@ import android.accessibilityservice.MagnificationConfig; import android.accessibilityservice.TouchInteractionController; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.app.ActivityOptions; import android.app.AlertDialog; import android.app.PendingIntent; @@ -262,6 +263,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock); private final AccessibilityTraceManager mTraceManager; + private final CaptioningManagerImpl mCaptioningManagerImpl; private int mCurrentUserId = UserHandle.USER_SYSTEM; @@ -339,6 +341,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yDisplayListener = a11yDisplayListener; mMagnificationController = magnificationController; mMagnificationProcessor = new MagnificationProcessor(mMagnificationController); + mCaptioningManagerImpl = new CaptioningManagerImpl(mContext); if (inputFilter != null) { mInputFilter = inputFilter; mHasInputFilter = true; @@ -373,6 +376,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mMagnificationController = new MagnificationController(this, mLock, mContext, new MagnificationScaleProvider(mContext)); mMagnificationProcessor = new MagnificationProcessor(mMagnificationController); + mCaptioningManagerImpl = new CaptioningManagerImpl(mContext); init(); } @@ -3458,6 +3462,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.SET_SYSTEM_AUDIO_CAPTION, + "setSystemAudioCaptioningRequested"); + + mCaptioningManagerImpl.setSystemAudioCaptioningRequested(isEnabled, userId); + } + + @Override + public boolean isSystemAudioCaptioningUiRequested(int userId) { + return mCaptioningManagerImpl.isSystemAudioCaptioningUiRequested(userId); + } + + @Override + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.SET_SYSTEM_AUDIO_CAPTION, + "setSystemAudioCaptioningUiRequested"); + + mCaptioningManagerImpl.setSystemAudioCaptioningUiRequested(isEnabled, userId); + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; synchronized (mLock) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index aba32ec465b5..59c1461e4abc 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -667,10 +667,6 @@ public class AccessibilityWindowManager { return null; } - // Don't need to add the embedded hierarchy windows into the accessibility windows list. - if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) { - return null; - } final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain(); reportedWindow.setId(windowId); @@ -703,21 +699,6 @@ public class AccessibilityWindowManager { return reportedWindow; } - private boolean isEmbeddedHierarchyWindowsLocked(int windowId) { - final IBinder leashToken = mWindowIdMap.get(windowId); - if (leashToken == null) { - return false; - } - - for (int i = 0; i < mHostEmbeddedMap.size(); i++) { - if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) { - return true; - } - } - - return false; - } - private int getTypeForWindowManagerWindowType(int windowType) { switch (windowType) { case WindowManager.LayoutParams.TYPE_APPLICATION: diff --git a/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java new file mode 100644 index 000000000000..39780d21486d --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java @@ -0,0 +1,87 @@ +/* + * 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.accessibility; + +import android.content.Context; +import android.os.Binder; +import android.provider.Settings; +import android.view.accessibility.CaptioningManager; + +/** + * Implementation class for CaptioningManager's interface that need system server identity. + */ +public class CaptioningManagerImpl implements CaptioningManager.SystemAudioCaptioningAccessing { + private static final boolean SYSTEM_AUDIO_CAPTIONING_UI_DEFAULT_ENABLED = false; + + private final Context mContext; + + public CaptioningManagerImpl(Context context) { + mContext = context; + } + + /** + * Sets the system audio caption enabled state. + * + * @param isEnabled The system audio captioning enabled state. + * @param userId The user Id. + */ + @Override + public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.ODI_CAPTIONS_ENABLED, isEnabled ? 1 : 0, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Gets the system audio caption UI enabled state. + * + * @param userId The user Id. + * @return the system audio caption UI enabled state. + */ + @Override + public boolean isSystemAudioCaptioningUiRequested(int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, + SYSTEM_AUDIO_CAPTIONING_UI_DEFAULT_ENABLED ? 1 : 0, userId) == 1; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Sets the system audio caption UI enabled state. + * + * @param isEnabled The system audio captioning UI enabled state. + * @param userId The user Id. + */ + @Override + public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, isEnabled ? 1 : 0, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } +} diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 1914164f195c..93fc0e7262aa 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -23,7 +23,7 @@ import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVER import static android.content.ComponentName.createRelative; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation; import static com.android.server.companion.RolesUtils.isRoleHolder; @@ -31,6 +31,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.companion.AssociationInfo; @@ -102,8 +103,9 @@ import java.util.Set; * @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback, * ResultReceiver, MacAddress) */ +@SuppressLint("LongLogTag") class AssociationRequestsProcessor { - private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor"; + private static final String TAG = "CompanionDevice_AssociationRequestsProcessor"; private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY = createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity"); @@ -161,7 +163,7 @@ class AssociationRequestsProcessor { // 1. Enforce permissions and other requirements. enforcePermissionsForAssociation(mContext, request, packageUid); - mService.checkUsesFeature(packageName, userId); + enforceUsesCompanionDeviceFeature(mContext, userId, packageName); // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER // to perform discovery NOR to collect user consent). diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java index 58fc8f7fe5b6..01905bb2297f 100644 --- a/services/companion/java/com/android/server/companion/AssociationStore.java +++ b/services/companion/java/com/android/server/companion/AssociationStore.java @@ -49,7 +49,25 @@ public interface AssociationStore { /** Listener for any changes to {@link AssociationInfo}-s. */ interface OnChangeListener { default void onAssociationChanged( - @ChangeType int changeType, AssociationInfo association) {} + @ChangeType int changeType, AssociationInfo association) { + switch (changeType) { + case CHANGE_TYPE_ADDED: + onAssociationAdded(association); + break; + + case CHANGE_TYPE_REMOVED: + onAssociationRemoved(association); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: + onAssociationUpdated(association, true); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: + onAssociationUpdated(association, false); + break; + } + } default void onAssociationAdded(AssociationInfo association) {} diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java index 3f0200ea584f..ce7a259f8ef2 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.StringJoiner; /** * Implementation of the {@link AssociationStore}, with addition of the methods for modification. @@ -58,33 +59,15 @@ class AssociationStoreImpl implements AssociationStore { private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<Integer, AssociationInfo> mIdMap; + private final Map<Integer, AssociationInfo> mIdMap = new HashMap<>(); @GuardedBy("mLock") - private final Map<MacAddress, Set<Integer>> mAddressMap; + private final Map<MacAddress, Set<Integer>> mAddressMap = new HashMap<>(); @GuardedBy("mLock") private final SparseArray<List<AssociationInfo>> mCachedPerUser = new SparseArray<>(); @GuardedBy("mListeners") private final Set<OnChangeListener> mListeners = new LinkedHashSet<>(); - AssociationStoreImpl(Collection<AssociationInfo> associations) { - synchronized (mLock) { - final int size = associations.size(); - mIdMap = new HashMap<>(size); - mAddressMap = new HashMap<>(size); - - for (AssociationInfo association : associations) { - final int id = association.getId(); - mIdMap.put(id, association); - - final MacAddress address = association.getDeviceMacAddress(); - if (address != null) { - mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id); - } - } - } - } - void addAssociation(@NonNull AssociationInfo association) { final int id = association.getId(); @@ -136,6 +119,8 @@ class AssociationStoreImpl implements AssociationStore { // Update the ID-to-Association map. mIdMap.put(id, updated); + // Invalidate the corresponding user cache entry. + invalidateCacheForUserLocked(current.getUserId()); // Update the MacAddress-to-List<Association> map if needed. final MacAddress updatedAddress = updated.getDeviceMacAddress(); @@ -278,25 +263,41 @@ class AssociationStoreImpl implements AssociationStore { synchronized (mListeners) { for (OnChangeListener listener : mListeners) { listener.onAssociationChanged(changeType, association); + } + } + } - switch (changeType) { - case CHANGE_TYPE_ADDED: - listener.onAssociationAdded(association); - break; + void setAssociations(Collection<AssociationInfo> allAssociations) { + if (DEBUG) { + Log.i(TAG, "setAssociations() n=" + allAssociations.size()); + final StringJoiner stringJoiner = new StringJoiner(", "); + allAssociations.forEach(assoc -> stringJoiner.add(assoc.toShortString())); + Log.v(TAG, " associations=" + stringJoiner); + } + synchronized (mLock) { + setAssociationsLocked(allAssociations); + } + } - case CHANGE_TYPE_REMOVED: - listener.onAssociationRemoved(association); - break; + @GuardedBy("mLock") + private void setAssociationsLocked(Collection<AssociationInfo> associations) { + clearLocked(); - case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: - listener.onAssociationUpdated(association, true); - break; + for (AssociationInfo association : associations) { + final int id = association.getId(); + mIdMap.put(id, association); - case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: - listener.onAssociationUpdated(association, false); - break; - } + final MacAddress address = association.getDeviceMacAddress(); + if (address != null) { + mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id); } } } + + @GuardedBy("mLock") + private void clearLocked() { + mIdMap.clear(); + mAddressMap.clear(); + mCachedPerUser.clear(); + } } diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java new file mode 100644 index 000000000000..be1bc7907cd5 --- /dev/null +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -0,0 +1,317 @@ +/* + * 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.companion; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt; +import android.companion.AssociationInfo; +import android.companion.CompanionDeviceService; +import android.content.ComponentName; +import android.content.Context; +import android.os.Handler; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.PerUser; +import com.android.internal.util.CollectionUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Manages communication with companion applications via + * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to + * the services, maintaining the connection (the binding), and invoking callback methods such as + * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)} and + * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} in the application process. + * + * <p> + * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be + * utilized by {@link CompanionDeviceManagerService}): + * <ul> + * <li> {@link #bindCompanionApplication(int, String)} + * <li> {@link #unbindCompanionApplication(int, String)} + * <li> {@link #notifyCompanionApplicationDeviceAppeared(AssociationInfo)} + * <li> {@link #notifyCompanionApplicationDeviceDisappeared(AssociationInfo)} + * <li> {@link #isCompanionApplicationBound(int, String)} + * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} + * </ul> + * + * @see CompanionDeviceService + * @see android.companion.ICompanionDeviceService + * @see CompanionDeviceServiceConnector + */ +@SuppressLint("LongLogTag") +class CompanionApplicationController { + static final boolean DEBUG = false; + private static final String TAG = "CompanionDevice_ApplicationController"; + + private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec + + interface Callback { + /** + * @return {@code true} if should schedule rebinding. + * {@code false} if we do not need to rebind. + */ + boolean onCompanionApplicationBindingDied( + @UserIdInt int userId, @NonNull String packageName); + + /** + * Callback after timeout for previously scheduled rebind has passed. + */ + void onRebindCompanionApplicationTimeout( + @UserIdInt int userId, @NonNull String packageName); + } + + private final @NonNull Context mContext; + private final @NonNull Callback mCallback; + private final @NonNull CompanionServicesRegister mCompanionServicesRegister; + @GuardedBy("mBoundCompanionApplications") + private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>> + mBoundCompanionApplications; + private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications; + + CompanionApplicationController(Context context, Callback callback) { + mContext = context; + mCallback = callback; + mCompanionServicesRegister = new CompanionServicesRegister(); + mBoundCompanionApplications = new AndroidPackageMap<>(); + mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>(); + } + + void onPackagesChanged(@UserIdInt int userId) { + mCompanionServicesRegister.invalidate(userId); + } + + void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "bind() u" + userId + "/" + packageName); + + final List<ComponentName> companionServices = + mCompanionServicesRegister.forPackage(userId, packageName); + final List<CompanionDeviceServiceConnector> serviceConnectors; + + synchronized (mBoundCompanionApplications) { + if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound."); + return; + } + + serviceConnectors = CollectionUtils.map(companionServices, componentName -> + new CompanionDeviceServiceConnector(mContext, userId, componentName)); + mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors); + } + + // The first connector in the list is always the primary connector: set a listener to it. + serviceConnectors.get(0).setListener(this::onPrimaryServiceBindingDied); + + // Now "bind" all the connectors: the primary one and the rest of them. + for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { + serviceConnector.connect(); + } + } + + void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName); + + final List<CompanionDeviceServiceConnector> serviceConnectors; + synchronized (mBoundCompanionApplications) { + serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName); + } + if (serviceConnectors == null) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound"); + return; + } + + for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { + serviceConnector.postUnbind(); + } + } + + boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) { + synchronized (mBoundCompanionApplications) { + return mBoundCompanionApplications.containsValueForPackage(userId, packageName); + } + } + + private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName) { + mScheduledForRebindingCompanionApplications.setValueForPackage(userId, packageName, true); + + Handler.getMain().postDelayed(() -> + onRebindingCompanionApplicationTimeout(userId, packageName), REBIND_TIMEOUT); + } + + boolean isRebindingCompanionApplicationScheduled( + @UserIdInt int userId, @NonNull String packageName) { + return mScheduledForRebindingCompanionApplications + .containsValueForPackage(userId, packageName); + } + + private void onRebindingCompanionApplicationTimeout( + @UserIdInt int userId, @NonNull String packageName) { + mScheduledForRebindingCompanionApplications.removePackage(userId, packageName); + + mCallback.onRebindCompanionApplicationTimeout(userId, packageName); + } + + void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) { + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + if (DEBUG) { + Log.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId + + "/" + packageName); + } + + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + if (primaryServiceConnector == null) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound."); + return; + } + + primaryServiceConnector.postOnDeviceAppeared(association); + } + + void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) { + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + if (DEBUG) { + Log.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId + + "/" + packageName); + } + + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + if (primaryServiceConnector == null) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound."); + return; + } + + primaryServiceConnector.postOnDeviceDisappeared(association); + } + + private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "onPrimaryServiceBindingDied() u" + userId + "/" + packageName); + + // First: mark as NOT bound. + synchronized (mBoundCompanionApplications) { + mBoundCompanionApplications.removePackage(userId, packageName); + } + + // Second: invoke callback, schedule rebinding if needed. + final boolean shouldScheduleRebind = + mCallback.onCompanionApplicationBindingDied(userId, packageName); + if (shouldScheduleRebind) { + scheduleRebinding(userId, packageName); + } + } + + private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector( + @UserIdInt int userId, @NonNull String packageName) { + final List<CompanionDeviceServiceConnector> connectors; + synchronized (mBoundCompanionApplications) { + connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName); + } + return connectors != null ? connectors.get(0) : null; + } + + private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { + @Override + public synchronized @NonNull Map<String, List<ComponentName>> forUser( + @UserIdInt int userId) { + return super.forUser(userId); + } + + synchronized @NonNull List<ComponentName> forPackage( + @UserIdInt int userId, @NonNull String packageName) { + return forUser(userId).getOrDefault(packageName, Collections.emptyList()); + } + + synchronized @NonNull ComponentName primaryForPackage( + @UserIdInt int userId, @NonNull String packageName) { + // The primary service is always at the head of the list. + return forPackage(userId, packageName).get(0); + } + + synchronized void invalidate(@UserIdInt int userId) { + remove(userId); + } + + @Override + protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { + return PackageUtils.getCompanionServicesForUser(mContext, userId); + } + } + + /** + * Associates an Android package (defined by userId + packageName) with a value of type T. + */ + private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> { + + void setValueForPackage( + @UserIdInt int userId, @NonNull String packageName, @NonNull T value) { + Map<String, T> forUser = get(userId); + if (forUser == null) { + forUser = /* Map<String, T> */ new HashMap(); + put(userId, forUser); + } + + forUser.put(packageName, value); + } + + boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, ?> forUser = get(userId); + return forUser != null && forUser.containsKey(packageName); + } + + T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, T> forUser = get(userId); + return forUser != null ? forUser.get(packageName) : null; + } + + T removePackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, T> forUser = get(userId); + if (forUser == null) return null; + return forUser.remove(packageName); + } + + void dump() { + if (size() == 0) { + Log.d(TAG, "<empty>"); + return; + } + + for (int i = 0; i < size(); i++) { + final int userId = keyAt(i); + final Map<String, T> forUser = get(userId); + if (forUser.isEmpty()) { + Log.d(TAG, "u" + userId + ": <empty>"); + } + + for (Map.Entry<String, T> packageValue : forUser.entrySet()) { + final String packageName = packageValue.getKey(); + final T value = packageValue.getValue(); + Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value); + } + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 94a97d864f58..1e50c80f0e71 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -159,7 +159,7 @@ public class CompanionDeviceManagerService extends SystemService // Persistent data store for all Associations. private PersistentDataStore mPersistentStore; - private AssociationStoreImpl mAssociationStore; + private final AssociationStoreImpl mAssociationStore = new AssociationStoreImpl(); private AssociationRequestsProcessor mAssociationRequestsProcessor; private PowerWhitelistManager mPowerWhitelistManager; @@ -219,16 +219,8 @@ public class CompanionDeviceManagerService extends SystemService @Override public void onStart() { mPersistentStore = new PersistentDataStore(); - final Set<AssociationInfo> allAssociations = new ArraySet<>(); - - synchronized (mPreviouslyUsedIds) { - // The data is stored in DE directories, so we can read the data for all users now - // (which would not be possible if the data was stored to CE directories). - mPersistentStore.readStateForUsers( - mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); - } - mAssociationStore = new AssociationStoreImpl(allAssociations); + loadAssociationsFromDisk(); mAssociationStore.registerListener(this); mCompanionDevicePresenceController = new CompanionDevicePresenceController(this); @@ -239,6 +231,18 @@ public class CompanionDeviceManagerService extends SystemService publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl); } + void loadAssociationsFromDisk() { + final Set<AssociationInfo> allAssociations = new ArraySet<>(); + synchronized (mPreviouslyUsedIds) { + // The data is stored in DE directories, so we can read the data for all users now + // (which would not be possible if the data was stored to CE directories). + mPersistentStore.readStateForUsers( + mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); + } + + mAssociationStore.setAssociations(allAssociations); + } + @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { @@ -600,11 +604,13 @@ public class CompanionDeviceManagerService extends SystemService return; } - association.setLastTimeConnected(System.currentTimeMillis()); - mAssociationStore.updateAssociation(association); + AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association) + .setLastTimeConnected(System.currentTimeMillis()) + .build(); + mAssociationStore.updateAssociation(updatedAssociationInfo); mCompanionDevicePresenceController.onDeviceNotifyAppeared( - association, getContext(), mMainHandler); + updatedAssociationInfo, getContext(), mMainHandler); } @Override @@ -651,8 +657,10 @@ public class CompanionDeviceManagerService extends SystemService + " for user " + userId)); } - association.setNotifyOnDeviceNearby(active); - mAssociationStore.updateAssociation(association); + AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association) + .setNotifyOnDeviceNearby(active) + .build(); + mAssociationStore.updateAssociation(updatedAssociationInfo); } @Override diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java index 444768491660..fc6681705cb6 100644 --- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java +++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java @@ -48,7 +48,7 @@ import java.util.Objects; public class CompanionDevicePresenceController { private static final String LOG_TAG = "CompanionDevicePresenceController"; PerUser<ArrayMap<String, List<BoundService>>> mBoundServices; - private static final String META_DATA_KEY_PRIMARY = "primary"; + private static final String META_DATA_KEY_PRIMARY = "android.companion.primary"; private final CompanionDeviceManagerService mService; public CompanionDevicePresenceController(CompanionDeviceManagerService service) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java new file mode 100644 index 000000000000..777917cd5a9e --- /dev/null +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -0,0 +1,119 @@ +/* + * 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.companion; + +import static android.content.Context.BIND_IMPORTANT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt; +import android.companion.AssociationInfo; +import android.companion.CompanionDeviceService; +import android.companion.ICompanionDeviceService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; + +import com.android.internal.infra.ServiceConnector; + +/** + * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the + * application process. + */ +@SuppressLint("LongLogTag") +class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> { + private static final String TAG = "CompanionDevice_ServiceConnector"; + private static final boolean DEBUG = false; + private static final int BINDING_FLAGS = BIND_IMPORTANT; + + /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */ + interface Listener { + void onBindingDied(@UserIdInt int userId, @NonNull String packageName); + } + + private final @UserIdInt int mUserId; + private final @NonNull ComponentName mComponentName; + private @Nullable Listener mListener; + + CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId, + @NonNull ComponentName componentName) { + super(context, buildIntent(componentName), BINDING_FLAGS, userId, null); + mUserId = userId; + mComponentName = componentName; + } + + void setListener(@Nullable Listener listener) { + mListener = listener; + } + + void postOnDeviceAppeared(@NonNull AssociationInfo associationInfo) { + post(companionService -> companionService.onDeviceAppeared(associationInfo)); + } + + void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) { + post(companionService -> companionService.onDeviceDisappeared(associationInfo)); + } + + /** + * Post "unbind" job, which will run *after* all previously posted jobs complete. + * + * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly, + * because the latter may cause previously posted callback, such as + * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped. + */ + void postUnbind() { + post(it -> unbind()); + } + + @Override + protected void onServiceConnectionStatusChanged( + @NonNull ICompanionDeviceService service, boolean isConnected) { + if (DEBUG) { + Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString() + + " connected=" + isConnected); + } + } + + @Override + public void onBindingDied(@NonNull ComponentName name) { + // IMPORTANT: call super! + super.onBindingDied(name); + + if (DEBUG) Log.d(TAG, "onBindingDied() " + mComponentName.toShortString()); + + mListener.onBindingDied(mUserId, mComponentName.getPackageName()); + } + + @Override + protected ICompanionDeviceService binderAsInterface(@NonNull IBinder service) { + return ICompanionDeviceService.Stub.asInterface(service); + } + + @Override + protected long getAutoDisconnectTimeoutMs() { + // Do NOT auto-disconnect. + return -1; + } + + private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) { + return new Intent(CompanionDeviceService.SERVICE_INTERFACE) + .setComponent(componentName); + } +} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 5c0571d801aa..5f46d5c4c4bf 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -82,7 +82,10 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { mService.onDeviceDisconnected(getNextArgRequired()); } break; - + case "clear-association-memory-cache": { + mService.loadAssociationsFromDisk(); + } + break; default: return handleDefaultCommands(cmd); } @@ -110,5 +113,8 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { pw.println(" Create a new Association."); pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS"); pw.println(" Remove an existing Association."); + pw.println(" clear-association-memory-cache"); + pw.println(" Clear the in-memory association cache and reload all association " + + "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); } } diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java new file mode 100644 index 000000000000..fcb14a4f04d0 --- /dev/null +++ b/services/companion/java/com/android/server/companion/PackageUtils.java @@ -0,0 +1,126 @@ +/* + * 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.companion; + +import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; +import static android.content.pm.PackageManager.GET_CONFIGURATIONS; +import static android.content.pm.PackageManager.GET_META_DATA; +import static android.content.pm.PackageManager.GET_PERMISSIONS; + +import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.companion.CompanionDeviceService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.PackageInfoFlags; +import android.content.pm.PackageManager.ResolveInfoFlags; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Binder; +import android.util.Slog; + +import com.android.internal.util.ArrayUtils; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Utility methods for working with {@link PackageInfo}-s. + */ +final class PackageUtils { + private static final Intent COMPANION_SERVICE_INTENT = + new Intent(CompanionDeviceService.SERVICE_INTERFACE); + private static final String META_DATA_PRIMARY_TAG = "android.companion.primary"; + + static @Nullable PackageInfo getPackageInfo(@NonNull Context context, + @UserIdInt int userId, @NonNull String packageName) { + final PackageManager pm = context.getPackageManager(); + final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS); + return Binder.withCleanCallingIdentity(() -> + pm.getPackageInfoAsUser(packageName, flags , userId)); + } + + static void enforceUsesCompanionDeviceFeature(@NonNull Context context, + @UserIdInt int userId, @NonNull String packageName) { + final boolean requested = ArrayUtils.contains( + getPackageInfo(context, userId, packageName).reqFeatures, + FEATURE_COMPANION_DEVICE_SETUP); + + if (requested) { + throw new IllegalStateException("Must declare uses-feature " + + FEATURE_COMPANION_DEVICE_SETUP + + " in manifest to use this API"); + } + } + + /** + * @return list of {@link CompanionDeviceService}-s per package for a given user. + * Services marked as "primary" would always appear at the head of the lists, *before* + * all non-primary services. + */ + static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser( + @NonNull Context context, @UserIdInt int userId) { + final PackageManager pm = context.getPackageManager(); + final ResolveInfoFlags flags = ResolveInfoFlags.of(GET_META_DATA); + final List<ResolveInfo> companionServices = + pm.queryIntentServicesAsUser(COMPANION_SERVICE_INTENT, flags, userId); + + final Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>(); + + for (ResolveInfo resolveInfo : companionServices) { + final ServiceInfo service = resolveInfo.serviceInfo; + + final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE + .equals(resolveInfo.serviceInfo.permission); + if (!requiresPermission) { + Slog.w(LOG_TAG, "CompanionDeviceService " + + service.getComponentName().flattenToShortString() + " must require " + + "android.permission.BIND_COMPANION_DEVICE_SERVICE"); + continue; + } + + // Use LinkedList, because we'll need to prepend "primary" services, while appending the + // other (non-primary) services to the list. + final LinkedList<ComponentName> services = + (LinkedList<ComponentName>) packageNameToServiceInfoList.computeIfAbsent( + service.packageName, it -> new LinkedList<>()); + + final ComponentName componentName = service.getComponentName(); + if (isPrimaryCompanionDeviceService(service)) { + // "Primary" service should be at the head of the list. + services.addFirst(componentName); + } else { + services.addLast(componentName); + } + } + + return packageNameToServiceInfoList; + } + + private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) { + return service.metaData != null && service.metaData.getBoolean(META_DATA_PRIMARY_TAG); + } +} diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index ef3aa7fea1b5..3c8c3cb8f842 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -67,17 +67,20 @@ import java.util.concurrent.ConcurrentMap; * The class responsible for persisting Association records and other related information (such as * previously used IDs) to a disk, and reading the data back from the disk. * - * Before Android T the data was stored to `companion_device_manager_associations.xml` file in - * {@link Environment#getUserSystemDirectory(int)} - * (eg. `/data/system/users/0/companion_device_manager_associations.xml`) - * @see #getBaseLegacyStorageFileForUser(int) + * <p> + * Before Android T the data was stored in "companion_device_manager_associations.xml" file in + * {@link Environment#getUserSystemDirectory(int) /data/system/user/}. * - * Before Android T the data was stored using the v0 schema. + * See {@link #getBaseLegacyStorageFileForUser(int) getBaseLegacyStorageFileForUser()}. * - * @see #readAssociationsV0(TypedXmlPullParser, int, Collection) - * @see #readAssociationV0(TypedXmlPullParser, int, int, Collection) + * <p> + * Before Android T the data was stored using the v0 schema. See: + * <ul> + * <li>{@link #readAssociationsV0(TypedXmlPullParser, int, Collection) readAssociationsV0()}. + * <li>{@link #readAssociationV0(TypedXmlPullParser, int, int, Collection) readAssociationV0()}. + * </ul> * - * The following snippet is a sample of a the file that is using v0 schema. + * The following snippet is a sample of a file that is using v0 schema. * <pre>{@code * <associations> * <association @@ -93,24 +96,28 @@ import java.util.concurrent.ConcurrentMap; * </associations> * }</pre> * + * <p> + * Since Android T the data is stored to "companion_device_manager.xml" file in + * {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}. * - * Since Android T the data is stored to `companion_device_manager.xml` file in - * {@link Environment#getDataSystemDeDirectory(int)}. - * (eg. `/data/system_de/0/companion_device_manager.xml`) - * @see #getBaseStorageFileForUser(int) - + * See {@link #getBaseStorageFileForUser(int) getBaseStorageFileForUser()} + * + * <p> * Since Android T the data is stored using the v1 schema. - * In the v1 schema, a list of the previously used IDs is storead along with the association + * + * In the v1 schema, a list of the previously used IDs is stored along with the association * records. - * V1 schema adds a new optional `display_name` attribute, and makes the `mac_address` attribute - * optional. * - * @see #CURRENT_PERSISTENCE_VERSION - * @see #readAssociationsV1(TypedXmlPullParser, int, Collection) - * @see #readAssociationV1(TypedXmlPullParser, int, Collection) - * @see #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) + * V1 schema adds a new optional "display_name" attribute, and makes the "mac_address" attribute + * optional. + * <ul> + * <li> {@link #CURRENT_PERSISTENCE_VERSION} + * <li> {@link #readAssociationsV1(TypedXmlPullParser, int, Collection) readAssociationsV1()} + * <li> {@link #readAssociationV1(TypedXmlPullParser, int, Collection) readAssociationV1()} + * <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()} + * </ul> * - * The following snippet is a sample of a the file that is using v0 schema. + * The following snippet is a sample of a file that is using v0 schema. * <pre>{@code * <state persistence-version="1"> * <associations> @@ -206,6 +213,8 @@ final class PersistentDataStore { final AtomicFile file = getStorageFileForUser(userId); if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath()); + // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize + // accesses to the file on the file system using this AtomicFile object. synchronized (file) { File legacyBaseFile = null; final AtomicFile readFrom; @@ -269,6 +278,8 @@ final class PersistentDataStore { final AtomicFile file = getStorageFileForUser(userId); if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath()); + // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize + // accesses to the file on the file system using this AtomicFile object. synchronized (file) { persistStateToFileLocked(file, associations, previouslyUsedIdsPerPackage); } @@ -329,6 +340,13 @@ final class PersistentDataStore { }); } + /** + * Creates and caches {@link AtomicFile} object that represents the back-up file for the given + * user. + * + * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it + * possible to synchronize reads and writes to the file using the returned object. + */ private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) { return mUserIdToStorageFile.computeIfAbsent(userId, u -> new AtomicFile(getBaseStorageFileForUser(userId))); diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java new file mode 100644 index 000000000000..a4fa1c12b29b --- /dev/null +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -0,0 +1,175 @@ +/* + * 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.companion.presence; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.companion.AssociationInfo; +import android.net.MacAddress; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.util.Log; + +import com.android.server.companion.AssociationStore; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@SuppressLint("LongLogTag") +class BluetoothCompanionDeviceConnectionListener + extends BluetoothAdapter.BluetoothConnectionCallback + implements AssociationStore.OnChangeListener { + private static final boolean DEBUG = false; + private static final String TAG = "CompanionDevice_PresenceMonitor_BT"; + + interface Callback { + void onBluetoothCompanionDeviceConnected(int associationId); + + void onBluetoothCompanionDeviceDisconnected(int associationId); + } + + private final @NonNull AssociationStore mAssociationStore; + private final @NonNull Callback mCallback; + /** A set of ALL connected BT device (not only companion.) */ + private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>(); + + BluetoothCompanionDeviceConnectionListener(@NonNull AssociationStore associationStore, + @NonNull Callback callback) { + mAssociationStore = associationStore; + mCallback = callback; + } + + public void init(@NonNull BluetoothAdapter btAdapter) { + if (DEBUG) Log.i(TAG, "init()"); + + btAdapter.registerBluetoothConnectionCallback( + new HandlerExecutor(Handler.getMain()), /* callback */this); + mAssociationStore.registerListener(this); + } + + /** + * Overrides + * {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}. + */ + @Override + public void onDeviceConnected(@NonNull BluetoothDevice device) { + if (DEBUG) Log.i(TAG, "onDevice_Connected() " + toString(device)); + + final MacAddress macAddress = MacAddress.fromString(device.getAddress()); + if (mAllConnectedDevices.put(macAddress, device) != null) { + if (DEBUG) Log.w(TAG, "Device " + toString(device) + " is already connected."); + return; + } + + onDeviceConnectivityChanged(device, true); + } + + /** + * Overrides + * {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}. + * Also invoked when user turns BT off while the device is connected. + */ + @Override + public void onDeviceDisconnected(@NonNull BluetoothDevice device, + @DisconnectReason int reason) { + if (DEBUG) { + Log.i(TAG, "onDevice_Disconnected() " + toString(device)); + Log.d(TAG, " reason=" + disconnectReasonText(reason)); + } + + final MacAddress macAddress = MacAddress.fromString(device.getAddress()); + if (mAllConnectedDevices.remove(macAddress) == null) { + if (DEBUG) Log.w(TAG, "The device wasn't tracked as connected " + toString(device)); + return; + } + + onDeviceConnectivityChanged(device, false); + } + + private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) { + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(device.getAddress()); + + if (DEBUG) { + Log.d(TAG, "onDevice_ConnectivityChanged() " + toString(device) + + " connected=" + connected); + if (associations.isEmpty()) { + Log.d(TAG, " > No CDM associations"); + } else { + Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); + } + } + + for (AssociationInfo association : associations) { + final int id = association.getId(); + if (connected) { + mCallback.onBluetoothCompanionDeviceConnected(id); + } else { + mCallback.onBluetoothCompanionDeviceDisconnected(id); + } + } + } + + @Override + public void onAssociationAdded(AssociationInfo association) { + if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association); + + if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) { + mCallback.onBluetoothCompanionDeviceConnected(association.getId()); + } + } + + @Override + public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) { + if (DEBUG) { + Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged + + " " + association); + } + + if (!addressChanged) { + // Don't need to do anything. + return; + } + + // At the moment CDM does allow changing association addresses, so we will never come here. + // This will be implemented when CDM support updating addresses. + throw new IllegalArgumentException("Address changes are not supported."); + } + + private static String toString(@NonNull BluetoothDevice btDevice) { + final StringBuilder sb = new StringBuilder(btDevice.getAddress()); + + sb.append(" [name="); + final String name = btDevice.getName(); + if (name != null) { + sb.append('\'').append(name).append('\''); + } else { + sb.append("null"); + } + + final String alias = btDevice.getAlias(); + if (alias != null) { + sb.append(", alias='").append(alias).append("'"); + } + + return sb.append(']').toString(); + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index e98b63ecd4b5..0fd29675d469 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -16,10 +16,12 @@ package com.android.server.companion.virtual; +import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; @@ -27,10 +29,12 @@ import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.os.Build; import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Slog; import android.window.DisplayWindowPolicyController; -import java.util.HashSet; import java.util.List; +import java.util.Set; /** @@ -38,6 +42,8 @@ import java.util.List; */ class GenericWindowPolicyController extends DisplayWindowPolicyController { + private static final String TAG = "VirtualDeviceManager"; + /** * If required, allow the secure activity to display on remote device since * {@link android.os.Build.VERSION_CODES#TIRAMISU}. @@ -45,10 +51,23 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L; + @NonNull + private final ArraySet<UserHandle> mAllowedUsers; + @Nullable + private final ArraySet<ComponentName> mAllowedActivities; + @Nullable + private final ArraySet<ComponentName> mBlockedActivities; - @NonNull final HashSet<Integer> mRunningUids = new HashSet<>(); + @NonNull + final ArraySet<Integer> mRunningUids = new ArraySet<>(); - GenericWindowPolicyController(int windowFlags, int systemWindowFlags) { + GenericWindowPolicyController(int windowFlags, int systemWindowFlags, + @NonNull ArraySet<UserHandle> allowedUsers, + @Nullable Set<ComponentName> allowedActivities, + @Nullable Set<ComponentName> blockedActivities) { + mAllowedUsers = allowedUsers; + mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); + mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); setInterestedWindowFlags(windowFlags, systemWindowFlags); } @@ -58,7 +77,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { final int activityCount = activities.size(); for (int i = 0; i < activityCount; i++) { final ActivityInfo aInfo = activities.get(i); - if ((aInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) { + if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) { return false; } } @@ -68,21 +87,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @Override public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags, int systemWindowFlags) { - if ((activityInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) { - return false; - } - if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE, - activityInfo.packageName, - UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) { - // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure. - if ((windowFlags & FLAG_SECURE) != 0) { - return false; - } - if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) { - return false; - } - } - return true; + return canContainActivity(activityInfo, windowFlags, systemWindowFlags); } @Override @@ -91,11 +96,9 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { } @Override - public void onRunningAppsChanged(int[] runningUids) { + public void onRunningAppsChanged(ArraySet<Integer> runningUids) { mRunningUids.clear(); - for (int i = 0; i < runningUids.length; i++) { - mRunningUids.add(runningUids[i]); - } + mRunningUids.addAll(runningUids); } /** @@ -105,4 +108,40 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { boolean containsUid(int uid) { return mRunningUids.contains(uid); } + + private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags, + int systemWindowFlags) { + if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) { + return false; + } + final UserHandle activityUser = + UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid); + if (!mAllowedUsers.contains(activityUser)) { + Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser); + return false; + } + if (mBlockedActivities != null + && mBlockedActivities.contains(activityInfo.getComponentName())) { + Slog.d(TAG, + "Virtual device blocking launch of " + activityInfo.getComponentName()); + return false; + } + if (mAllowedActivities != null + && !mAllowedActivities.contains(activityInfo.getComponentName())) { + Slog.d(TAG, + activityInfo.getComponentName() + " is not in the allowed list."); + return false; + } + if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE, + activityInfo.packageName, activityUser)) { + // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure. + if ((windowFlags & FLAG_SECURE) != 0) { + return false; + } + if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) { + return false; + } + } + return true; + } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index ca35e033ed70..59c9d8c625b5 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -16,14 +16,23 @@ package com.android.server.companion.virtual; +import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED; +import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY; +import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.annotation.NonNull; +import android.app.Activity; +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.graphics.Point; +import android.hardware.display.DisplayManager; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -32,6 +41,11 @@ import android.hardware.input.VirtualTouchEvent; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.ArraySet; +import android.util.Slog; import android.util.SparseArray; import android.window.DisplayWindowPolicyController; @@ -39,23 +53,25 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; final class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient { + private static final String TAG = "VirtualDeviceImpl"; private final Object mVirtualDeviceLock = new Object(); private final Context mContext; private final AssociationInfo mAssociationInfo; + private final PendingTrampolineCallback mPendingTrampolineCallback; private final int mOwnerUid; private final InputController mInputController; @VisibleForTesting - final List<Integer> mVirtualDisplayIds = new ArrayList<>(); + final Set<Integer> mVirtualDisplayIds = new ArraySet<>(); private final OnDeviceCloseListener mListener; private final IBinder mAppToken; + private final VirtualDeviceParams mParams; /** * A mapping from the virtual display ID to its corresponding @@ -65,17 +81,22 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub new SparseArray<>(); VirtualDeviceImpl(Context context, AssociationInfo associationInfo, - IBinder token, int ownerUid, OnDeviceCloseListener listener) { - this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener); + IBinder token, int ownerUid, OnDeviceCloseListener listener, + PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) { + this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener, + pendingTrampolineCallback, params); } @VisibleForTesting VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token, - int ownerUid, InputController inputController, OnDeviceCloseListener listener) { + int ownerUid, InputController inputController, OnDeviceCloseListener listener, + PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) { mContext = context; mAssociationInfo = associationInfo; + mPendingTrampolineCallback = pendingTrampolineCallback; mOwnerUid = ownerUid; mAppToken = token; + mParams = params; if (inputController == null) { mInputController = new InputController(mVirtualDeviceLock); } else { @@ -89,12 +110,67 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } - @Override + /** + * Returns the flags that should be added to any virtual displays created on this virtual + * device. + */ + int getBaseVirtualDisplayFlags() { + int flags = 0; + if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { + flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; + } + return flags; + } + + @Override // Binder call public int getAssociationId() { return mAssociationInfo.getId(); } @Override // Binder call + public void launchPendingIntent(int displayId, PendingIntent pendingIntent, + ResultReceiver resultReceiver) { + if (!mVirtualDisplayIds.contains(displayId)) { + throw new SecurityException("Display ID " + displayId + + " not found for this virtual device"); + } + if (pendingIntent.isActivity()) { + try { + sendPendingIntent(displayId, pendingIntent); + resultReceiver.send(Activity.RESULT_OK, null); + } catch (PendingIntent.CanceledException e) { + Slog.w(TAG, "Pending intent canceled", e); + resultReceiver.send(Activity.RESULT_CANCELED, null); + } + } else { + PendingTrampoline pendingTrampoline = new PendingTrampoline(pendingIntent, + resultReceiver, displayId); + mPendingTrampolineCallback.startWaitingForPendingTrampoline(pendingTrampoline); + try { + sendPendingIntent(displayId, pendingIntent); + } catch (PendingIntent.CanceledException e) { + Slog.w(TAG, "Pending intent canceled", e); + resultReceiver.send(Activity.RESULT_CANCELED, null); + mPendingTrampolineCallback.stopWaitingForPendingTrampoline(pendingTrampoline); + } + } + } + + private void sendPendingIntent(int displayId, PendingIntent pendingIntent) + throws PendingIntent.CanceledException { + pendingIntent.send( + mContext, + /* code= */ 0, + /* intent= */ null, + /* onFinished= */ null, + /* handler= */ null, + /* requiredPermission= */ null, + ActivityOptions.makeBasic() + .setLaunchDisplayId(displayId) + .toBundle()); + } + + @Override // Binder call public void close() { mListener.onClose(mAssociationInfo.getId()); mAppToken.unlinkToDeath(this, 0); @@ -250,6 +326,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { fout.println(" VirtualDevice: "); + fout.println(" mAssociationId: " + mAssociationInfo.getId()); + fout.println(" mParams: " + mParams); fout.println(" mVirtualDisplayIds: "); synchronized (mVirtualDeviceLock) { for (int id : mVirtualDisplayIds) { @@ -267,11 +345,31 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mVirtualDisplayIds.add(displayId); final GenericWindowPolicyController dwpc = new GenericWindowPolicyController(FLAG_SECURE, - SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles(), + mParams.getAllowedActivities(), + mParams.getBlockedActivities()); mWindowPolicyControllers.put(displayId, dwpc); return dwpc; } + private ArraySet<UserHandle> getAllowedUserHandles() { + ArraySet<UserHandle> result = new ArraySet<>(); + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + UserManager userManager = mContext.getSystemService(UserManager.class); + for (UserHandle profile : userManager.getAllProfiles()) { + int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier()); + if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED + || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) { + result.add(profile); + } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) { + if (mParams.getUsersWithMatchingAccounts().contains(profile)) { + result.add(profile); + } + } + } + return result; + } + void onVirtualDisplayRemovedLocked(int displayId) { if (!mVirtualDisplayIds.contains(displayId)) { throw new IllegalStateException( @@ -302,4 +400,58 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub interface OnDeviceCloseListener { void onClose(int associationId); } + + interface PendingTrampolineCallback { + /** + * Called when the callback should start waiting for the given pending trampoline. + * Implementations should try to listen for activity starts associated with the given + * {@code pendingTrampoline}, and launch the activity on the display with + * {@link PendingTrampoline#mDisplayId}. + */ + void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline); + + /** + * Called when the callback should stop waiting for the given pending trampoline. This can + * happen, for example, when the pending intent failed to send. + */ + void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline); + } + + /** + * A data class storing a pending trampoline this device is expecting. + */ + static class PendingTrampoline { + + /** + * The original pending intent sent, for which a trampoline activity launch is expected. + */ + final PendingIntent mPendingIntent; + + /** + * The result receiver associated with this pending call. {@link Activity#RESULT_OK} will + * be sent to the receiver if the trampoline activity was captured successfully. + * {@link Activity#RESULT_CANCELED} is sent otherwise. + */ + final ResultReceiver mResultReceiver; + + /** + * The display ID to send the captured trampoline activity launch to. + */ + final int mDisplayId; + + private PendingTrampoline(PendingIntent pendingIntent, ResultReceiver resultReceiver, + int displayId) { + mPendingIntent = pendingIntent; + mResultReceiver = resultReceiver; + mDisplayId = displayId; + } + + @Override + public String toString() { + return "PendingTrampoline{" + + "pendingIntent=" + mPendingIntent + + ", resultReceiver=" + mResultReceiver + + ", displayId=" + mDisplayId + "}"; + } + } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 0db670e46909..7e0c2fc37da6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -16,16 +16,23 @@ package com.android.server.companion.virtual; +import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityOptions; import android.companion.AssociationInfo; import android.companion.CompanionDeviceManager; import android.companion.CompanionDeviceManager.OnAssociationsChangedListener; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceManager; +import android.companion.virtual.VirtualDeviceParams; import android.content.Context; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Parcel; import android.os.RemoteException; import android.util.ExceptionUtils; @@ -36,6 +43,9 @@ import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; +import com.android.server.companion.virtual.VirtualDeviceImpl.PendingTrampoline; +import com.android.server.wm.ActivityInterceptorCallback; +import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -47,10 +57,12 @@ import java.util.concurrent.ConcurrentHashMap; public class VirtualDeviceManagerService extends SystemService { private static final boolean DEBUG = false; - private static final String LOG_TAG = "VirtualDeviceManagerService"; + private static final String TAG = "VirtualDeviceManagerService"; private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler); /** * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for @@ -79,10 +91,39 @@ public class VirtualDeviceManagerService extends SystemService { mImpl = new VirtualDeviceManagerImpl(); } + private final ActivityInterceptorCallback mActivityInterceptorCallback = + new ActivityInterceptorCallback() { + + @Nullable + @Override + public ActivityInterceptResult intercept(ActivityInterceptorInfo info) { + if (info.callingPackage == null) { + return null; + } + PendingTrampoline pt = mPendingTrampolines.remove(info.callingPackage); + if (pt == null) { + return null; + } + pt.mResultReceiver.send(Activity.RESULT_OK, null); + ActivityOptions options = info.checkedOptions; + if (options == null) { + options = ActivityOptions.makeBasic(); + } + return new ActivityInterceptResult( + info.intent, options.setLaunchDisplayId(pt.mDisplayId)); + } + }; + @Override public void onStart() { publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); publishLocalService(VirtualDeviceManagerInternal.class, new LocalService()); + + ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService( + ActivityTaskManagerInternal.class); + activityTaskManagerInternal.registerActivityStartInterceptor( + VIRTUAL_DEVICE_SERVICE_ORDERED_ID, + mActivityInterceptorCallback); } @GuardedBy("mVirtualDeviceManagerLock") @@ -127,11 +168,15 @@ public class VirtualDeviceManagerService extends SystemService { } } - class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub { + class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements + VirtualDeviceImpl.PendingTrampolineCallback { @Override // Binder call public IVirtualDevice createVirtualDevice( - IBinder token, String packageName, int associationId) { + IBinder token, + String packageName, + int associationId, + @NonNull VirtualDeviceParams params) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CREATE_VIRTUAL_DEVICE, "createVirtualDevice"); @@ -160,7 +205,8 @@ public class VirtualDeviceManagerService extends SystemService { mVirtualDevices.remove(associationId); } } - }); + }, + this, params); mVirtualDevices.put(associationInfo.getId(), virtualDevice); return virtualDevice; } @@ -181,7 +227,7 @@ public class VirtualDeviceManagerService extends SystemService { } } } else { - Slog.w(LOG_TAG, "No associations for user " + callingUserId); + Slog.w(TAG, "No associations for user " + callingUserId); } return null; } @@ -192,7 +238,7 @@ public class VirtualDeviceManagerService extends SystemService { try { return super.onTransact(code, data, reply, flags); } catch (Throwable e) { - Slog.e(LOG_TAG, "Error during IPC", e); + Slog.e(TAG, "Error during IPC", e); throw ExceptionUtils.propagate(e, RemoteException.class); } } @@ -201,7 +247,7 @@ public class VirtualDeviceManagerService extends SystemService { public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, fout)) { return; } fout.println("Created virtual devices: "); @@ -211,10 +257,24 @@ public class VirtualDeviceManagerService extends SystemService { } } } + + @Override + public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) { + PendingTrampoline existing = mPendingTrampolines.put( + pendingTrampoline.mPendingIntent.getCreatorPackage(), + pendingTrampoline); + if (existing != null) { + existing.mResultReceiver.send(Activity.RESULT_CANCELED, null); + } + } + + @Override + public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) { + mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage()); + } } private final class LocalService extends VirtualDeviceManagerInternal { - @Override public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) { synchronized (mVirtualDeviceManagerLock) { @@ -238,6 +298,11 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) { + return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags(); + } + + @Override public boolean isAppOwnerOfAnyVirtualDevice(int uid) { synchronized (mVirtualDeviceManagerLock) { int size = mVirtualDevices.size(); @@ -263,4 +328,42 @@ public class VirtualDeviceManagerService extends SystemService { return false; } } + + private static final class PendingTrampolineMap { + /** + * The maximum duration, in milliseconds, to wait for a trampoline activity launch after + * invoking a pending intent. + */ + private static final int TRAMPOLINE_WAIT_MS = 5000; + + private final ConcurrentHashMap<String, PendingTrampoline> mMap = new ConcurrentHashMap<>(); + private final Handler mHandler; + + PendingTrampolineMap(Handler handler) { + mHandler = handler; + } + + PendingTrampoline put( + @NonNull String packageName, @NonNull PendingTrampoline pendingTrampoline) { + PendingTrampoline existing = mMap.put(packageName, pendingTrampoline); + mHandler.removeCallbacksAndMessages(existing); + mHandler.postDelayed( + () -> { + final String creatorPackage = + pendingTrampoline.mPendingIntent.getCreatorPackage(); + if (creatorPackage != null) { + remove(creatorPackage); + } + }, + pendingTrampoline, + TRAMPOLINE_WAIT_MS); + return existing; + } + + PendingTrampoline remove(@NonNull String packageName) { + PendingTrampoline pendingTrampoline = mMap.remove(packageName); + mHandler.removeCallbacksAndMessages(pendingTrampoline); + return pendingTrampoline; + } + } } diff --git a/services/core/Android.bp b/services/core/Android.bp index 7b17162f95dd..094ed375325e 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -100,9 +100,10 @@ java_library_static { name: "services.core.unboosted", defaults: ["platform_service_defaults"], srcs: [ - ":android.hardware.biometrics.face-V1-java-source", + ":android.hardware.biometrics.face-V2-java-source", ":statslog-art-java-gen", ":statslog-contexthub-java-gen", + ":services.bluetooth-sources", // TODO(b/214988855) : Remove once apex is ready ":services.core-sources", ":services.core.protologsrc", ":dumpstate_aidl", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 9b2948f42ed8..60cae4d67f5a 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -30,7 +30,6 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.SigningDetails.CertCapabilities; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.component.ParsedMainComponent; import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; @@ -50,6 +49,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageApi; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import java.io.IOException; diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 844ac86e8eb5..5d48d7821cad 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -853,7 +853,9 @@ public final class BatteryService extends SystemService { pw.println("Battery service (battery) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" set [-f] [ac|usb|wireless|status|level|temp|present|invalid] <value>"); + pw.println(" get [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid]"); + pw.println( + " set [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid] <value>"); pw.println(" Force a battery property value, freezing battery state."); pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); pw.println(" unplug [-f]"); @@ -863,7 +865,7 @@ public final class BatteryService extends SystemService { pw.println(" Unfreeze battery state, returning to current hardware values."); pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); if (Build.IS_DEBUGGABLE) { - pw.println(" disable_charge"); + pw.println(" suspend_input"); pw.println(" Suspend charging even if plugged in. "); } } @@ -893,6 +895,46 @@ public final class BatteryService extends SystemService { android.Manifest.permission.DEVICE_POWER, null); unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw); } break; + case "get": { + final String key = shell.getNextArg(); + if (key == null) { + pw.println("No property specified"); + return -1; + + } + switch (key) { + case "present": + pw.println(mHealthInfo.batteryPresent); + break; + case "ac": + pw.println(mHealthInfo.chargerAcOnline); + break; + case "usb": + pw.println(mHealthInfo.chargerUsbOnline); + break; + case "wireless": + pw.println(mHealthInfo.chargerWirelessOnline); + break; + case "status": + pw.println(mHealthInfo.batteryStatus); + break; + case "level": + pw.println(mHealthInfo.batteryLevel); + break; + case "counter": + pw.println(mHealthInfo.batteryChargeCounterUah); + break; + case "temp": + pw.println(mHealthInfo.batteryTemperatureTenthsCelsius); + break; + case "invalid": + pw.println(mInvalidCharger); + break; + default: + pw.println("Unknown get option: " + key); + break; + } + } break; case "set": { int opts = parseOptions(shell); getContext().enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java deleted file mode 100644 index 380b1f37b981..000000000000 --- a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 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; - -import android.annotation.RequiresPermission; -import android.content.Context; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.provider.Settings; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -/** - * The BluetoothAirplaneModeListener handles system airplane mode change callback and checks - * whether we need to inform BluetoothManagerService on this change. - * - * The information of airplane mode turns on would not be passed to the BluetoothManagerService - * when Bluetooth is on and Bluetooth is in one of the following situations: - * 1. Bluetooth A2DP is connected. - * 2. Bluetooth Hearing Aid profile is connected. - * 3. Bluetooth LE Audio is connected - */ -class BluetoothAirplaneModeListener { - private static final String TAG = "BluetoothAirplaneModeListener"; - @VisibleForTesting static final String TOAST_COUNT = "bluetooth_airplane_toast_count"; - - private static final int MSG_AIRPLANE_MODE_CHANGED = 0; - - @VisibleForTesting static final int MAX_TOAST_COUNT = 10; // 10 times - - private final BluetoothManagerService mBluetoothManager; - private final BluetoothAirplaneModeHandler mHandler; - private BluetoothModeChangeHelper mAirplaneHelper; - - @VisibleForTesting int mToastCount = 0; - - BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context) { - mBluetoothManager = service; - - mHandler = new BluetoothAirplaneModeHandler(looper); - context.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, - mAirplaneModeObserver); - } - - private final ContentObserver mAirplaneModeObserver = new ContentObserver(null) { - @Override - public void onChange(boolean unused) { - // Post from system main thread to android_io thread. - Message msg = mHandler.obtainMessage(MSG_AIRPLANE_MODE_CHANGED); - mHandler.sendMessage(msg); - } - }; - - private class BluetoothAirplaneModeHandler extends Handler { - BluetoothAirplaneModeHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_AIRPLANE_MODE_CHANGED: - handleAirplaneModeChange(); - break; - default: - Log.e(TAG, "Invalid message: " + msg.what); - break; - } - } - } - - /** - * Call after boot complete - */ - @VisibleForTesting - void start(BluetoothModeChangeHelper helper) { - Log.i(TAG, "start"); - mAirplaneHelper = helper; - mToastCount = mAirplaneHelper.getSettingsInt(TOAST_COUNT); - } - - @VisibleForTesting - boolean shouldPopToast() { - if (mToastCount >= MAX_TOAST_COUNT) { - return false; - } - mToastCount++; - mAirplaneHelper.setSettingsInt(TOAST_COUNT, mToastCount); - return true; - } - - @VisibleForTesting - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - void handleAirplaneModeChange() { - if (shouldSkipAirplaneModeChange()) { - Log.i(TAG, "Ignore airplane mode change"); - // Airplane mode enabled when Bluetooth is being used for audio/headering aid. - // Bluetooth is not disabled in such case, only state is changed to - // BLUETOOTH_ON_AIRPLANE mode. - mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON, - BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); - if (shouldPopToast()) { - mAirplaneHelper.showToastMessage(); - } - return; - } - if (mAirplaneHelper != null) { - mAirplaneHelper.onAirplaneModeChanged(mBluetoothManager); - } - } - - @VisibleForTesting - boolean shouldSkipAirplaneModeChange() { - if (mAirplaneHelper == null) { - return false; - } - if (!mAirplaneHelper.isBluetoothOn() || !mAirplaneHelper.isAirplaneModeOn() - || !mAirplaneHelper.isMediaProfileConnected()) { - return false; - } - return true; - } -} diff --git a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java b/services/core/java/com/android/server/BluetoothDeviceConfigListener.java deleted file mode 100644 index 611a37de70f4..000000000000 --- a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020 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; - -import android.provider.DeviceConfig; -import android.util.Slog; - -import java.util.ArrayList; - -/** - * The BluetoothDeviceConfigListener handles system device config change callback and checks - * whether we need to inform BluetoothManagerService on this change. - * - * The information of device config change would not be passed to the BluetoothManagerService - * when Bluetooth is on and Bluetooth is in one of the following situations: - * 1. Bluetooth A2DP is connected. - * 2. Bluetooth Hearing Aid profile is connected. - */ -class BluetoothDeviceConfigListener { - private static final String TAG = "BluetoothDeviceConfigListener"; - - private final BluetoothManagerService mService; - private final boolean mLogDebug; - - BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) { - mService = service; - mLogDebug = logDebug; - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_BLUETOOTH, - (Runnable r) -> r.run(), - mDeviceConfigChangedListener); - } - - private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener = - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_BLUETOOTH)) { - return; - } - if (mLogDebug) { - ArrayList<String> flags = new ArrayList<>(); - for (String name : properties.getKeyset()) { - flags.add(name + "='" + properties.getString(name, "") + "'"); - } - Slog.d(TAG, "onPropertiesChanged: " + String.join(",", flags)); - } - boolean foundInit = false; - for (String name : properties.getKeyset()) { - if (name.startsWith("INIT_")) { - foundInit = true; - break; - } - } - if (!foundInit) { - return; - } - mService.onInitFlagsChanged(); - } - }; - -} diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java deleted file mode 100644 index bc8da8443a7d..000000000000 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ /dev/null @@ -1,2957 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import static android.Manifest.permission.BLUETOOTH_CONNECT; -import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; -import static android.os.UserHandle.USER_SYSTEM; -import static android.permission.PermissionCheckerManager.PERMISSION_HARD_DENIED; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.app.AppGlobals; -import android.app.AppOpsManager; -import android.app.BroadcastOptions; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHearingAid; -import android.bluetooth.BluetoothLeAudio; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProtoEnums; -import android.bluetooth.IBluetooth; -import android.bluetooth.IBluetoothCallback; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothHeadset; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.IBluetoothManagerCallback; -import android.bluetooth.IBluetoothProfileServiceConnection; -import android.bluetooth.IBluetoothStateChangeCallback; -import android.content.ActivityNotFoundException; -import android.content.AttributionSource; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; -import android.content.pm.UserInfo; -import android.database.ContentObserver; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.PowerExemptionManager; -import android.os.Process; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.UserHandle; -import android.os.UserManager; -import android.permission.PermissionManager; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.text.TextUtils; -import android.util.FeatureFlagUtils; -import android.util.Log; -import android.util.Slog; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.DumpUtils; -import com.android.internal.util.FrameworkStatsLog; -import com.android.server.pm.UserManagerInternal; -import com.android.server.pm.UserManagerInternal.UserRestrictionsListener; -import com.android.server.pm.UserRestrictionsUtils; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Locale; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -class BluetoothManagerService extends IBluetoothManager.Stub { - private static final String TAG = "BluetoothManagerService"; - private static final boolean DBG = true; - - private static final String BLUETOOTH_PRIVILEGED = - android.Manifest.permission.BLUETOOTH_PRIVILEGED; - - private static final int ACTIVE_LOG_MAX_SIZE = 20; - private static final int CRASH_LOG_MAX_SIZE = 100; - - private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind - //Maximum msec to wait for service restart - private static final int SERVICE_RESTART_TIME_MS = 400; - //Maximum msec to wait for restart due to error - private static final int ERROR_RESTART_TIME_MS = 3000; - //Maximum msec to delay MESSAGE_USER_SWITCHED - private static final int USER_SWITCHED_TIME_MS = 200; - // Delay for the addProxy function in msec - private static final int ADD_PROXY_DELAY_MS = 100; - // Delay for retrying enable and disable in msec - private static final int ENABLE_DISABLE_DELAY_MS = 300; - private static final int DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS = 300; - private static final int DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS = 86400; - - private static final int MESSAGE_ENABLE = 1; - private static final int MESSAGE_DISABLE = 2; - private static final int MESSAGE_HANDLE_ENABLE_DELAYED = 3; - private static final int MESSAGE_HANDLE_DISABLE_DELAYED = 4; - private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30; - private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31; - private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40; - private static final int MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED = 41; - private static final int MESSAGE_RESTART_BLUETOOTH_SERVICE = 42; - private static final int MESSAGE_BLUETOOTH_STATE_CHANGE = 60; - private static final int MESSAGE_TIMEOUT_BIND = 100; - private static final int MESSAGE_TIMEOUT_UNBIND = 101; - private static final int MESSAGE_GET_NAME_AND_ADDRESS = 200; - private static final int MESSAGE_USER_SWITCHED = 300; - private static final int MESSAGE_USER_UNLOCKED = 301; - private static final int MESSAGE_ADD_PROXY_DELAYED = 400; - private static final int MESSAGE_BIND_PROFILE_SERVICE = 401; - private static final int MESSAGE_RESTORE_USER_SETTING = 500; - private static final int MESSAGE_INIT_FLAGS_CHANGED = 600; - - private static final int RESTORE_SETTING_TO_ON = 1; - private static final int RESTORE_SETTING_TO_OFF = 0; - - private static final int MAX_ERROR_RESTART_RETRIES = 6; - private static final int MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES = 10; - - // Bluetooth persisted setting is off - private static final int BLUETOOTH_OFF = 0; - // Bluetooth persisted setting is on - // and Airplane mode won't affect Bluetooth state at start up - private static final int BLUETOOTH_ON_BLUETOOTH = 1; - // Bluetooth persisted setting is on - // but Airplane mode will affect Bluetooth state at start up - // and Airplane mode will have higher priority. - @VisibleForTesting - static final int BLUETOOTH_ON_AIRPLANE = 2; - - private static final int SERVICE_IBLUETOOTH = 1; - private static final int SERVICE_IBLUETOOTHGATT = 2; - - private final Context mContext; - - // Locks are not provided for mName and mAddress. - // They are accessed in handler or broadcast receiver, same thread context. - private String mAddress; - private String mName; - private final ContentResolver mContentResolver; - private final int mUserId; - private final RemoteCallbackList<IBluetoothManagerCallback> mCallbacks; - private final RemoteCallbackList<IBluetoothStateChangeCallback> mStateChangeCallbacks; - private IBinder mBluetoothBinder; - private IBluetooth mBluetooth; - private IBluetoothGatt mBluetoothGatt; - private final ReentrantReadWriteLock mBluetoothLock = new ReentrantReadWriteLock(); - private boolean mBinding; - private boolean mUnbinding; - - private BluetoothModeChangeHelper mBluetoothModeChangeHelper; - - private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener; - - private BluetoothDeviceConfigListener mBluetoothDeviceConfigListener; - - // used inside handler thread - private boolean mQuietEnable = false; - private boolean mEnable; - - private static CharSequence timeToLog(long timestamp) { - return android.text.format.DateFormat.format("MM-dd HH:mm:ss", timestamp); - } - - /** - * Used for tracking apps that enabled / disabled Bluetooth. - */ - private class ActiveLog { - private int mReason; - private String mPackageName; - private boolean mEnable; - private long mTimestamp; - - ActiveLog(int reason, String packageName, boolean enable, long timestamp) { - mReason = reason; - mPackageName = packageName; - mEnable = enable; - mTimestamp = timestamp; - } - - public String toString() { - return timeToLog(mTimestamp) + (mEnable ? " Enabled " : " Disabled ") - + " due to " + getEnableDisableReasonString(mReason) + " by " + mPackageName; - } - - void dump(ProtoOutputStream proto) { - proto.write(BluetoothManagerServiceDumpProto.ActiveLog.TIMESTAMP_MS, mTimestamp); - proto.write(BluetoothManagerServiceDumpProto.ActiveLog.ENABLE, mEnable); - proto.write(BluetoothManagerServiceDumpProto.ActiveLog.PACKAGE_NAME, mPackageName); - proto.write(BluetoothManagerServiceDumpProto.ActiveLog.REASON, mReason); - } - } - - private final LinkedList<ActiveLog> mActiveLogs = new LinkedList<>(); - private final LinkedList<Long> mCrashTimestamps = new LinkedList<>(); - private int mCrashes; - private long mLastEnabledTime; - - // configuration from external IBinder call which is used to - // synchronize with broadcast receiver. - private boolean mQuietEnableExternal; - private boolean mEnableExternal; - - // Map of apps registered to keep BLE scanning on. - private Map<IBinder, ClientDeathRecipient> mBleApps = - new ConcurrentHashMap<IBinder, ClientDeathRecipient>(); - - private int mState; - private final BluetoothHandler mHandler; - private int mErrorRecoveryRetryCounter; - private final int mSystemUiUid; - - private boolean mIsHearingAidProfileSupported; - - private AppOpsManager mAppOps; - - // Save a ProfileServiceConnections object for each of the bound - // bluetooth profile services - private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>(); - - private final boolean mWirelessConsentRequired; - - private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() { - @Override - public void onBluetoothStateChange(int prevState, int newState) throws RemoteException { - Message msg = - mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE, prevState, newState); - mHandler.sendMessage(msg); - } - }; - - private final UserRestrictionsListener mUserRestrictionsListener = - new UserRestrictionsListener() { - @Override - public void onUserRestrictionsChanged(int userId, Bundle newRestrictions, - Bundle prevRestrictions) { - - if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions, - UserManager.DISALLOW_BLUETOOTH_SHARING)) { - updateOppLauncherComponentState(userId, - newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING)); - } - - // DISALLOW_BLUETOOTH can only be set by DO or PO on the system user. - if (userId == USER_SYSTEM - && UserRestrictionsUtils.restrictionsChanged(prevRestrictions, - newRestrictions, UserManager.DISALLOW_BLUETOOTH)) { - if (userId == USER_SYSTEM && newRestrictions.getBoolean( - UserManager.DISALLOW_BLUETOOTH)) { - updateOppLauncherComponentState(userId, true); // Sharing disallowed - sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED, - mContext.getPackageName()); - } else { - updateOppLauncherComponentState(userId, newRestrictions.getBoolean( - UserManager.DISALLOW_BLUETOOTH_SHARING)); - } - } - } - }; - - @VisibleForTesting - public void onInitFlagsChanged() { - mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED); - mHandler.sendEmptyMessageDelayed( - MESSAGE_INIT_FLAGS_CHANGED, - DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS); - } - - public boolean onFactoryReset(AttributionSource attributionSource) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, - "Need BLUETOOTH_PRIVILEGED permission"); - - // Wait for stable state if bluetooth is temporary state. - int state = getState(); - if (state == BluetoothAdapter.STATE_BLE_TURNING_ON - || state == BluetoothAdapter.STATE_TURNING_ON - || state == BluetoothAdapter.STATE_TURNING_OFF) { - if (!waitForState(Set.of(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_ON))) { - return false; - } - } - - // Clear registered LE apps to force shut-off Bluetooth - clearBleApps(); - state = getState(); - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth == null) { - return false; - } - if (state == BluetoothAdapter.STATE_BLE_ON) { - addActiveLog( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET, - mContext.getPackageName(), false); - mBluetooth.onBrEdrDown(attributionSource); - return true; - } else if (state == BluetoothAdapter.STATE_ON) { - addActiveLog( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET, - mContext.getPackageName(), false); - mBluetooth.disable(attributionSource); - return true; - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to shutdown Bluetooth", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - return false; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onAirplaneModeChanged() { - synchronized (this) { - if (isBluetoothPersistedStateOn()) { - if (isAirplaneModeOn()) { - persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE); - } else { - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - } - } - - int st = BluetoothAdapter.STATE_OFF; - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - st = mBluetooth.getState(); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call getState", e); - return; - } finally { - mBluetoothLock.readLock().unlock(); - } - - Slog.d(TAG, - "Airplane Mode change - current state: " + BluetoothAdapter.nameForState( - st) + ", isAirplaneModeOn()=" + isAirplaneModeOn()); - - if (isAirplaneModeOn()) { - // Clear registered LE apps to force shut-off - clearBleApps(); - - // If state is BLE_ON make sure we trigger disableBLE - if (st == BluetoothAdapter.STATE_BLE_ON) { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - addActiveLog( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE, - mContext.getPackageName(), false); - mBluetooth.onBrEdrDown(mContext.getAttributionSource()); - mEnable = false; - mEnableExternal = false; - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBrEdrDown", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } else if (st == BluetoothAdapter.STATE_ON) { - sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE, - mContext.getPackageName()); - } - } else if (mEnableExternal) { - sendEnableMsg(mQuietEnableExternal, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE, - mContext.getPackageName()); - } - } - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) { - String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME); - if (DBG) { - Slog.d(TAG, "Bluetooth Adapter name changed to " + newName + " by " - + mContext.getPackageName()); - } - if (newName != null) { - storeNameAndAddress(newName, null); - } - } else if (BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED.equals(action)) { - String newAddress = intent.getStringExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS); - if (newAddress != null) { - if (DBG) { - Slog.d(TAG, "Bluetooth Adapter address changed to " + newAddress); - } - storeNameAndAddress(null, newAddress); - } else { - if (DBG) { - Slog.e(TAG, "No Bluetooth Adapter address parameter found"); - } - } - } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { - final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); - if (Settings.Global.BLUETOOTH_ON.equals(name)) { - // The Bluetooth On state may be changed during system restore. - final String prevValue = - intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE); - final String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE); - - if (DBG) { - Slog.d(TAG, - "ACTION_SETTING_RESTORED with BLUETOOTH_ON, prevValue=" + prevValue - + ", newValue=" + newValue); - } - - if ((newValue != null) && (prevValue != null) && !prevValue.equals(newValue)) { - Message msg = mHandler.obtainMessage(MESSAGE_RESTORE_USER_SETTING, - newValue.equals("0") ? RESTORE_SETTING_TO_OFF - : RESTORE_SETTING_TO_ON, 0); - mHandler.sendMessage(msg); - } - } - } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) - || BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(action) - || BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(action)) { - final int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, - BluetoothProfile.STATE_CONNECTED); - if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED) - && state == BluetoothProfile.STATE_DISCONNECTED - && !mBluetoothModeChangeHelper.isMediaProfileConnected()) { - Slog.i(TAG, "Device disconnected, reactivating pending flag changes"); - onInitFlagsChanged(); - } - } - } - }; - - BluetoothManagerService(Context context) { - mHandler = new BluetoothHandler(IoThread.get().getLooper()); - - mContext = context; - - mWirelessConsentRequired = context.getResources() - .getBoolean(com.android.internal.R.bool.config_wirelessConsentRequired); - - mCrashes = 0; - mBluetooth = null; - mBluetoothBinder = null; - mBluetoothGatt = null; - mBinding = false; - mUnbinding = false; - mEnable = false; - mState = BluetoothAdapter.STATE_OFF; - mQuietEnableExternal = false; - mEnableExternal = false; - mAddress = null; - mName = null; - mErrorRecoveryRetryCounter = 0; - mContentResolver = context.getContentResolver(); - mUserId = mContentResolver.getUserId(); - // Observe BLE scan only mode settings change. - registerForBleScanModeChange(); - mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>(); - mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>(); - - mIsHearingAidProfileSupported = context.getResources() - .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported); - - // TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils - String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS); - if (!TextUtils.isEmpty(value)) { - boolean isHearingAidEnabled = Boolean.parseBoolean(value); - Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled); - FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled); - if (isHearingAidEnabled && !mIsHearingAidProfileSupported) { - // Overwrite to enable support by FeatureFlag - mIsHearingAidProfileSupported = true; - } - } - - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); - filter.addAction(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED); - filter.addAction(Intent.ACTION_SETTING_RESTORED); - filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); - filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - mContext.registerReceiver(mReceiver, filter); - - loadStoredNameAndAddress(); - if (isBluetoothPersistedStateOn()) { - if (DBG) { - Slog.d(TAG, "Startup: Bluetooth persisted state is ON."); - } - mEnableExternal = true; - } - - String airplaneModeRadios = - Settings.Global.getString(mContentResolver, Settings.Global.AIRPLANE_MODE_RADIOS); - if (airplaneModeRadios == null || airplaneModeRadios.contains( - Settings.Global.RADIO_BLUETOOTH)) { - mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener( - this, IoThread.get().getLooper(), context); - } - - int systemUiUid = -1; - // Check if device is configured with no home screen, which implies no SystemUI. - boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen); - if (!noHome) { - PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); - systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(), - MATCH_SYSTEM_ONLY, USER_SYSTEM); - } - if (systemUiUid >= 0) { - Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid)); - } else { - // Some platforms, such as wearables do not have a system ui. - Slog.w(TAG, "Unable to resolve SystemUI's UID."); - } - mSystemUiUid = systemUiUid; - } - - /** - * Returns true if airplane mode is currently on - */ - private boolean isAirplaneModeOn() { - return Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, 0) == 1; - } - - private boolean supportBluetoothPersistedState() { - return mContext.getResources().getBoolean(R.bool.config_supportBluetoothPersistedState); - } - - /** - * Returns true if the Bluetooth saved state is "on" - */ - private boolean isBluetoothPersistedStateOn() { - if (!supportBluetoothPersistedState()) { - return false; - } - int state = Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, -1); - if (DBG) { - Slog.d(TAG, "Bluetooth persisted state: " + state); - } - return state != BLUETOOTH_OFF; - } - - private boolean isBluetoothPersistedStateOnAirplane() { - if (!supportBluetoothPersistedState()) { - return false; - } - int state = Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, -1); - if (DBG) { - Slog.d(TAG, "Bluetooth persisted state: " + state); - } - return state == BLUETOOTH_ON_AIRPLANE; - } - - /** - * Returns true if the Bluetooth saved state is BLUETOOTH_ON_BLUETOOTH - */ - private boolean isBluetoothPersistedStateOnBluetooth() { - if (!supportBluetoothPersistedState()) { - return false; - } - return Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, - BLUETOOTH_ON_BLUETOOTH) == BLUETOOTH_ON_BLUETOOTH; - } - - /** - * Save the Bluetooth on/off state - */ - private void persistBluetoothSetting(int value) { - if (DBG) { - Slog.d(TAG, "Persisting Bluetooth Setting: " + value); - } - // waive WRITE_SECURE_SETTINGS permission check - final long callingIdentity = Binder.clearCallingIdentity(); - try { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.BLUETOOTH_ON, value); - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - } - - /** - * Returns true if the Bluetooth Adapter's name and address is - * locally cached - * @return - */ - private boolean isNameAndAddressSet() { - return mName != null && mAddress != null && mName.length() > 0 && mAddress.length() > 0; - } - - /** - * Retrieve the Bluetooth Adapter's name and address and save it in - * in the local cache - */ - private void loadStoredNameAndAddress() { - if (DBG) { - Slog.d(TAG, "Loading stored name and address"); - } - if (mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_bluetooth_address_validation) - && Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.BLUETOOTH_NAME, 0, mUserId) - == 0) { - // if the valid flag is not set, don't load the address and name - if (DBG) { - Slog.d(TAG, "invalid bluetooth name and address stored"); - } - return; - } - mName = Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId); - mAddress = Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId); - if (DBG) { - Slog.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress); - } - } - - /** - * Save the Bluetooth name and address in the persistent store. - * Only non-null values will be saved. - * @param name - * @param address - */ - private void storeNameAndAddress(String name, String address) { - if (name != null) { - Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_NAME, name, - mUserId); - mName = name; - if (DBG) { - Slog.d(TAG, "Stored Bluetooth name: " + Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.BLUETOOTH_NAME, - mUserId)); - } - } - - if (address != null) { - Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, - address, mUserId); - mAddress = address; - if (DBG) { - Slog.d(TAG, - "Stored Bluetoothaddress: " + Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, - mUserId)); - } - } - - if ((name != null) && (address != null)) { - Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 1, - mUserId); - } - } - - public IBluetooth registerAdapter(IBluetoothManagerCallback callback) { - if (callback == null) { - Slog.w(TAG, "Callback is null in registerAdapter"); - return null; - } - synchronized (mCallbacks) { - mCallbacks.register(callback); - } - return mBluetooth; - } - - public void unregisterAdapter(IBluetoothManagerCallback callback) { - if (callback == null) { - Slog.w(TAG, "Callback is null in unregisterAdapter"); - return; - } - synchronized (mCallbacks) { - mCallbacks.unregister(callback); - } - } - - public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { - if (callback == null) { - Slog.w(TAG, "registerStateChangeCallback: Callback is null!"); - return; - } - Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_STATE_CHANGE_CALLBACK); - msg.obj = callback; - mHandler.sendMessage(msg); - } - - public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) { - if (callback == null) { - Slog.w(TAG, "unregisterStateChangeCallback: Callback is null!"); - return; - } - Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK); - msg.obj = callback; - mHandler.sendMessage(msg); - } - - public boolean isEnabled() { - return getState() == BluetoothAdapter.STATE_ON; - } - - public int getState() { - if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG, "getState(): report OFF for non-active and non system user"); - return BluetoothAdapter.STATE_OFF; - } - - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - return mBluetooth.getState(); - } - } catch (RemoteException e) { - Slog.e(TAG, "getState()", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - return BluetoothAdapter.STATE_OFF; - } - - class ClientDeathRecipient implements IBinder.DeathRecipient { - private String mPackageName; - - ClientDeathRecipient(String packageName) { - mPackageName = packageName; - } - - public void binderDied() { - if (DBG) { - Slog.d(TAG, "Binder is dead - unregister " + mPackageName); - } - - for (Map.Entry<IBinder, ClientDeathRecipient> entry : mBleApps.entrySet()) { - IBinder token = entry.getKey(); - ClientDeathRecipient deathRec = entry.getValue(); - if (deathRec.equals(this)) { - updateBleAppCount(token, false, mPackageName); - break; - } - } - } - - public String getPackageName() { - return mPackageName; - } - } - - @Override - public boolean isBleScanAlwaysAvailable() { - if (isAirplaneModeOn() && !mEnable) { - return false; - } - try { - return Settings.Global.getInt(mContentResolver, - Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE) != 0; - } catch (SettingNotFoundException e) { - } - return false; - } - - @Override - public boolean isHearingAidProfileSupported() { - return mIsHearingAidProfileSupported; - } - - private boolean isDeviceProvisioned() { - return Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, - 0) != 0; - } - - // Monitor change of BLE scan only mode settings. - private void registerForProvisioningStateChange() { - ContentObserver contentObserver = new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - if (!isDeviceProvisioned()) { - if (DBG) { - Slog.d(TAG, "DEVICE_PROVISIONED setting changed, but device is not " - + "provisioned"); - } - return; - } - if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)) { - Slog.i(TAG, "Device provisioned, reactivating pending flag changes"); - onInitFlagsChanged(); - } - } - }; - - mContentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), false, - contentObserver); - } - - // Monitor change of BLE scan only mode settings. - private void registerForBleScanModeChange() { - ContentObserver contentObserver = new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - if (isBleScanAlwaysAvailable()) { - // Nothing to do - return; - } - // BLE scan is not available. - disableBleScanMode(); - clearBleApps(); - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, - mContext.getPackageName(), false); - mBluetooth.onBrEdrDown(mContext.getAttributionSource()); - } - } catch (RemoteException e) { - Slog.e(TAG, "error when disabling bluetooth", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } - }; - - mContentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE), false, - contentObserver); - } - - // Disable ble scan only mode. - private void disableBleScanMode() { - try { - mBluetoothLock.writeLock().lock(); - if (mBluetooth != null && (mBluetooth.getState() != BluetoothAdapter.STATE_ON)) { - if (DBG) { - Slog.d(TAG, "Reseting the mEnable flag for clean disable"); - } - mEnable = false; - } - } catch (RemoteException e) { - Slog.e(TAG, "getState()", e); - } finally { - mBluetoothLock.writeLock().unlock(); - } - } - - private int updateBleAppCount(IBinder token, boolean enable, String packageName) { - ClientDeathRecipient r = mBleApps.get(token); - if (r == null && enable) { - ClientDeathRecipient deathRec = new ClientDeathRecipient(packageName); - try { - token.linkToDeath(deathRec, 0); - } catch (RemoteException ex) { - throw new IllegalArgumentException("BLE app (" + packageName + ") already dead!"); - } - mBleApps.put(token, deathRec); - if (DBG) { - Slog.d(TAG, "Registered for death of " + packageName); - } - } else if (!enable && r != null) { - // Unregister death recipient as the app goes away. - token.unlinkToDeath(r, 0); - mBleApps.remove(token); - if (DBG) { - Slog.d(TAG, "Unregistered for death of " + packageName); - } - } - int appCount = mBleApps.size(); - if (DBG) { - Slog.d(TAG, appCount + " registered Ble Apps"); - } - return appCount; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean checkBluetoothPermissions(AttributionSource attributionSource, String message, - boolean requireForeground) { - if (isBluetoothDisallowed()) { - if (DBG) { - Slog.d(TAG, "checkBluetoothPermissions: bluetooth disallowed"); - } - return false; - } - // Check if packageName belongs to callingUid - final int callingUid = Binder.getCallingUid(); - final boolean isCallerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID; - if (!isCallerSystem) { - checkPackage(callingUid, attributionSource.getPackageName()); - - if (requireForeground && !checkIfCallerIsForegroundUser()) { - Slog.w(TAG, "Not allowed for non-active and non system user"); - return false; - } - - if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, message)) { - return false; - } - } - return true; - } - - public boolean enableBle(AttributionSource attributionSource, IBinder token) - throws RemoteException { - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "enableBle", false)) { - if (DBG) { - Slog.d(TAG, "enableBle(): bluetooth disallowed"); - } - return false; - } - - if (DBG) { - Slog.d(TAG, "enableBle(" + packageName + "): mBluetooth =" + mBluetooth - + " mBinding = " + mBinding + " mState = " - + BluetoothAdapter.nameForState(mState)); - } - updateBleAppCount(token, true, packageName); - - if (mState == BluetoothAdapter.STATE_ON - || mState == BluetoothAdapter.STATE_BLE_ON - || mState == BluetoothAdapter.STATE_TURNING_ON - || mState == BluetoothAdapter.STATE_TURNING_OFF - || mState == BluetoothAdapter.STATE_BLE_TURNING_ON) { - Log.d(TAG, "enableBLE(): Bluetooth is already enabled or is turning on"); - return true; - } - synchronized (mReceiver) { - // waive WRITE_SECURE_SETTINGS permission check - sendEnableMsg(false, BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, - packageName, true); - } - return true; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean disableBle(AttributionSource attributionSource, IBinder token) - throws RemoteException { - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "disableBle", false)) { - if (DBG) { - Slog.d(TAG, "disableBLE(): bluetooth disallowed"); - } - return false; - } - - if (DBG) { - Slog.d(TAG, "disableBle(" + packageName + "): mBluetooth =" + mBluetooth - + " mBinding = " + mBinding + " mState = " - + BluetoothAdapter.nameForState(mState)); - } - - if (mState == BluetoothAdapter.STATE_OFF) { - Slog.d(TAG, "disableBLE(): Already disabled"); - return false; - } - updateBleAppCount(token, false, packageName); - - if (mState == BluetoothAdapter.STATE_BLE_ON && !isBleAppPresent()) { - if (mEnable) { - disableBleScanMode(); - } - if (!mEnableExternal) { - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, - packageName, false); - sendBrEdrDownCallback(attributionSource); - } - } - return true; - } - - // Clear all apps using BLE scan only mode. - private void clearBleApps() { - mBleApps.clear(); - } - - /** @hide */ - public boolean isBleAppPresent() { - if (DBG) { - Slog.d(TAG, "isBleAppPresent() count: " + mBleApps.size()); - } - return mBleApps.size() > 0; - } - - /** - * Call IBluetooth.onLeServiceUp() to continue if Bluetooth should be on, - * call IBluetooth.onBrEdrDown() to disable if Bluetooth should be off. - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - private void continueFromBleOnState() { - if (DBG) { - Slog.d(TAG, "continueFromBleOnState()"); - } - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth == null) { - Slog.e(TAG, "onBluetoothServiceUp: mBluetooth is null!"); - return; - } - if (!mEnableExternal && !isBleAppPresent()) { - Slog.i(TAG, "Bluetooth was disabled while enabling BLE, disable BLE now"); - mEnable = false; - mBluetooth.onBrEdrDown(mContext.getAttributionSource()); - return; - } - if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) { - // This triggers transition to STATE_ON - mBluetooth.onLeServiceUp(mContext.getAttributionSource()); - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onServiceUp", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } - - /** - * Inform BluetoothAdapter instances that BREDR part is down - * and turn off all service and stack if no LE app needs it - */ - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - private void sendBrEdrDownCallback(AttributionSource attributionSource) { - if (DBG) { - Slog.d(TAG, "Calling sendBrEdrDownCallback callbacks"); - } - - if (mBluetooth == null) { - Slog.w(TAG, "Bluetooth handle is null"); - return; - } - - if (isBleAppPresent()) { - // Need to stay at BLE ON. Disconnect all Gatt connections - try { - mBluetoothGatt.unregAll(attributionSource); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to disconnect all apps.", e); - } - } else { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - mBluetooth.onBrEdrDown(attributionSource); - } - } catch (RemoteException e) { - Slog.e(TAG, "Call to onBrEdrDown() failed.", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } - - } - - public boolean enableNoAutoConnect(AttributionSource attributionSource) { - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "enableNoAutoConnect", false)) { - if (DBG) { - Slog.d(TAG, "enableNoAutoConnect(): not enabling - bluetooth disallowed"); - } - return false; - } - - if (DBG) { - Slog.d(TAG, "enableNoAutoConnect(): mBluetooth =" + mBluetooth + " mBinding = " - + mBinding); - } - - int callingAppId = UserHandle.getAppId(Binder.getCallingUid()); - if (callingAppId != Process.NFC_UID) { - throw new SecurityException("no permission to enable Bluetooth quietly"); - } - - synchronized (mReceiver) { - mQuietEnableExternal = true; - mEnableExternal = true; - sendEnableMsg(true, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); - } - return true; - } - - public boolean enable(AttributionSource attributionSource) throws RemoteException { - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "enable", true)) { - if (DBG) { - Slog.d(TAG, "enable(): not enabling - bluetooth disallowed"); - } - return false; - } - - final int callingUid = Binder.getCallingUid(); - final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID; - if (!callerSystem && !isEnabled() && mWirelessConsentRequired - && startConsentUiIfNeeded(packageName, - callingUid, BluetoothAdapter.ACTION_REQUEST_ENABLE)) { - return false; - } - - if (DBG) { - Slog.d(TAG, "enable(" + packageName + "): mBluetooth =" + mBluetooth + " mBinding = " - + mBinding + " mState = " + BluetoothAdapter.nameForState(mState)); - } - - synchronized (mReceiver) { - mQuietEnableExternal = false; - mEnableExternal = true; - // waive WRITE_SECURE_SETTINGS permission check - sendEnableMsg(false, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); - } - if (DBG) { - Slog.d(TAG, "enable returning"); - } - return true; - } - - public boolean disable(AttributionSource attributionSource, boolean persist) - throws RemoteException { - if (!persist) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, - "Need BLUETOOTH_PRIVILEGED permission"); - } - - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "disable", true)) { - if (DBG) { - Slog.d(TAG, "disable(): not disabling - bluetooth disallowed"); - } - return false; - } - - final int callingUid = Binder.getCallingUid(); - final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID; - if (!callerSystem && isEnabled() && mWirelessConsentRequired - && startConsentUiIfNeeded(packageName, - callingUid, BluetoothAdapter.ACTION_REQUEST_DISABLE)) { - return false; - } - - if (DBG) { - Slog.d(TAG, "disable(): mBluetooth = " + mBluetooth + " mBinding = " + mBinding); - } - - synchronized (mReceiver) { - if (!isBluetoothPersistedStateOnAirplane()) { - if (persist) { - persistBluetoothSetting(BLUETOOTH_OFF); - } - mEnableExternal = false; - } - sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, - packageName); - } - return true; - } - - private boolean startConsentUiIfNeeded(String packageName, - int callingUid, String intentAction) throws RemoteException { - if (checkBluetoothPermissionWhenWirelessConsentRequired()) { - return false; - } - try { - // Validate the package only if we are going to use it - ApplicationInfo applicationInfo = mContext.getPackageManager() - .getApplicationInfoAsUser(packageName, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - UserHandle.getUserId(callingUid)); - if (applicationInfo.uid != callingUid) { - throw new SecurityException("Package " + packageName - + " not in uid " + callingUid); - } - - Intent intent = new Intent(intentAction); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); - intent.setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - try { - mContext.startActivity(intent); - } catch (ActivityNotFoundException e) { - // Shouldn't happen - Slog.e(TAG, "Intent to handle action " + intentAction + " missing"); - return false; - } - return true; - } catch (PackageManager.NameNotFoundException e) { - throw new RemoteException(e.getMessage()); - } - } - - /** - * Check if AppOpsManager is available and the packageName belongs to uid - * - * A null package belongs to any uid - */ - private void checkPackage(int uid, String packageName) { - if (mAppOps == null) { - Slog.w(TAG, "checkPackage(): called before system boot up, uid " - + uid + ", packageName " + packageName); - throw new IllegalStateException("System has not boot yet"); - } - if (packageName == null) { - Slog.w(TAG, "checkPackage(): called with null packageName from " + uid); - return; - } - try { - mAppOps.checkPackage(uid, packageName); - } catch (SecurityException e) { - Slog.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + uid); - throw new SecurityException(e.getMessage()); - } - } - - /** - * Check if the caller must still pass permission check or if the caller is exempted - * from the consent UI via the MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED check. - * - * Commands from some callers may be exempted from triggering the consent UI when - * enabling bluetooth. This exemption is checked via the - * MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED and allows calls to skip - * the consent UI where it may otherwise be required. - * - * @hide - */ - @SuppressLint("AndroidFrameworkRequiresPermission") - private boolean checkBluetoothPermissionWhenWirelessConsentRequired() { - int result = mContext.checkCallingPermission( - android.Manifest.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED); - return result == PackageManager.PERMISSION_GRANTED; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void unbindAndFinish() { - if (DBG) { - Slog.d(TAG, "unbindAndFinish(): " + mBluetooth + " mBinding = " + mBinding - + " mUnbinding = " + mUnbinding); - } - - try { - mBluetoothLock.writeLock().lock(); - if (mUnbinding) { - return; - } - mUnbinding = true; - mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); - mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE); - if (mBluetooth != null) { - //Unregister callback object - try { - mBluetooth.unregisterCallback(mBluetoothCallback, - mContext.getAttributionSource()); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to unregister BluetoothCallback", re); - } - mBluetoothBinder = null; - mBluetooth = null; - mContext.unbindService(mConnection); - mUnbinding = false; - mBinding = false; - } else { - mUnbinding = false; - } - mBluetoothGatt = null; - } finally { - mBluetoothLock.writeLock().unlock(); - } - } - - public IBluetoothGatt getBluetoothGatt() { - // sync protection - return mBluetoothGatt; - } - - @Override - public boolean bindBluetoothProfileService(int bluetoothProfile, - IBluetoothProfileServiceConnection proxy) { - if (mState != BluetoothAdapter.STATE_ON) { - if (DBG) { - Slog.d(TAG, "Trying to bind to profile: " + bluetoothProfile - + ", while Bluetooth was disabled"); - } - return false; - } - synchronized (mProfileServices) { - ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile)); - if (psc == null) { - if (DBG) { - Slog.d(TAG, "Creating new ProfileServiceConnections object for" + " profile: " - + bluetoothProfile); - } - - if (bluetoothProfile != BluetoothProfile.HEADSET) { - return false; - } - - Intent intent = new Intent(IBluetoothHeadset.class.getName()); - psc = new ProfileServiceConnections(intent); - if (!psc.bindService()) { - return false; - } - - mProfileServices.put(new Integer(bluetoothProfile), psc); - } - } - - // Introducing a delay to give the client app time to prepare - Message addProxyMsg = mHandler.obtainMessage(MESSAGE_ADD_PROXY_DELAYED); - addProxyMsg.arg1 = bluetoothProfile; - addProxyMsg.obj = proxy; - mHandler.sendMessageDelayed(addProxyMsg, ADD_PROXY_DELAY_MS); - return true; - } - - @Override - public void unbindBluetoothProfileService(int bluetoothProfile, - IBluetoothProfileServiceConnection proxy) { - synchronized (mProfileServices) { - Integer profile = new Integer(bluetoothProfile); - ProfileServiceConnections psc = mProfileServices.get(profile); - if (psc == null) { - return; - } - psc.removeProxy(proxy); - if (psc.isEmpty()) { - // All prxoies are disconnected, unbind with the service. - try { - mContext.unbindService(psc); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e); - } - mProfileServices.remove(profile); - } - } - } - - private void unbindAllBluetoothProfileServices() { - synchronized (mProfileServices) { - for (Integer i : mProfileServices.keySet()) { - ProfileServiceConnections psc = mProfileServices.get(i); - try { - mContext.unbindService(psc); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e); - } - psc.removeAllProxies(); - } - mProfileServices.clear(); - } - } - - /** - * Send enable message and set adapter name and address. Called when the boot phase becomes - * PHASE_SYSTEM_SERVICES_READY. - */ - public void handleOnBootPhase() { - if (DBG) { - Slog.d(TAG, "Bluetooth boot completed"); - } - mAppOps = mContext.getSystemService(AppOpsManager.class); - UserManagerInternal userManagerInternal = - LocalServices.getService(UserManagerInternal.class); - userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); - final boolean isBluetoothDisallowed = isBluetoothDisallowed(); - if (isBluetoothDisallowed) { - return; - } - final boolean isSafeMode = mContext.getPackageManager().isSafeMode(); - if (mEnableExternal && isBluetoothPersistedStateOnBluetooth() && !isSafeMode) { - if (DBG) { - Slog.d(TAG, "Auto-enabling Bluetooth."); - } - sendEnableMsg(mQuietEnableExternal, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT, - mContext.getPackageName()); - } else if (!isNameAndAddressSet()) { - if (DBG) { - Slog.d(TAG, "Getting adapter name and address"); - } - Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); - mHandler.sendMessage(getMsg); - } - - mBluetoothModeChangeHelper = new BluetoothModeChangeHelper(mContext); - if (mBluetoothAirplaneModeListener != null) { - mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper); - } - registerForProvisioningStateChange(); - mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG); - } - - /** - * Called when switching to a different foreground user. - */ - public void handleOnSwitchUser(int userHandle) { - if (DBG) { - Slog.d(TAG, "User " + userHandle + " switched"); - } - mHandler.obtainMessage(MESSAGE_USER_SWITCHED, userHandle, 0).sendToTarget(); - } - - /** - * Called when user is unlocked. - */ - public void handleOnUnlockUser(int userHandle) { - if (DBG) { - Slog.d(TAG, "User " + userHandle + " unlocked"); - } - mHandler.obtainMessage(MESSAGE_USER_UNLOCKED, userHandle, 0).sendToTarget(); - } - - /** - * This class manages the clients connected to a given ProfileService - * and maintains the connection with that service. - */ - private final class ProfileServiceConnections - implements ServiceConnection, IBinder.DeathRecipient { - final RemoteCallbackList<IBluetoothProfileServiceConnection> mProxies = - new RemoteCallbackList<IBluetoothProfileServiceConnection>(); - IBinder mService; - ComponentName mClassName; - Intent mIntent; - boolean mInvokingProxyCallbacks = false; - - ProfileServiceConnections(Intent intent) { - mService = null; - mClassName = null; - mIntent = intent; - } - - private boolean bindService() { - int state = BluetoothAdapter.STATE_OFF; - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - state = mBluetooth.getState(); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call getState", e); - return false; - } finally { - mBluetoothLock.readLock().unlock(); - } - - if (state != BluetoothAdapter.STATE_ON) { - if (DBG) { - Slog.d(TAG, "Unable to bindService while Bluetooth is disabled"); - } - return false; - } - - if (mIntent != null && mService == null && doBind(mIntent, this, 0, - UserHandle.CURRENT_OR_SELF)) { - Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE); - msg.obj = this; - mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS); - return true; - } - Slog.w(TAG, "Unable to bind with intent: " + mIntent); - return false; - } - - private void addProxy(IBluetoothProfileServiceConnection proxy) { - mProxies.register(proxy); - if (mService != null) { - try { - proxy.onServiceConnected(mClassName, mService); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to connect to proxy", e); - } - } else { - if (!mHandler.hasMessages(MESSAGE_BIND_PROFILE_SERVICE, this)) { - Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE); - msg.obj = this; - mHandler.sendMessage(msg); - } - } - } - - private void removeProxy(IBluetoothProfileServiceConnection proxy) { - if (proxy != null) { - if (mProxies.unregister(proxy)) { - try { - proxy.onServiceDisconnected(mClassName); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to disconnect proxy", e); - } - } - } else { - Slog.w(TAG, "Trying to remove a null proxy"); - } - } - - private void removeAllProxies() { - onServiceDisconnected(mClassName); - mProxies.kill(); - } - - private boolean isEmpty() { - return mProxies.getRegisteredCallbackCount() == 0; - } - - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - // remove timeout message - mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE, this); - mService = service; - mClassName = className; - try { - mService.linkToDeath(this, 0); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to linkToDeath", e); - } - - if (mInvokingProxyCallbacks) { - Slog.e(TAG, "Proxy callbacks already in progress."); - return; - } - mInvokingProxyCallbacks = true; - - final int n = mProxies.beginBroadcast(); - try { - for (int i = 0; i < n; i++) { - try { - mProxies.getBroadcastItem(i).onServiceConnected(className, service); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to connect to proxy", e); - } - } - } finally { - mProxies.finishBroadcast(); - mInvokingProxyCallbacks = false; - } - } - - @Override - public void onServiceDisconnected(ComponentName className) { - if (mService == null) { - return; - } - try { - mService.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.e(TAG, "error unlinking to death", e); - } - mService = null; - mClassName = null; - - if (mInvokingProxyCallbacks) { - Slog.e(TAG, "Proxy callbacks already in progress."); - return; - } - mInvokingProxyCallbacks = true; - - final int n = mProxies.beginBroadcast(); - try { - for (int i = 0; i < n; i++) { - try { - mProxies.getBroadcastItem(i).onServiceDisconnected(className); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to disconnect from proxy", e); - } - } - } finally { - mProxies.finishBroadcast(); - mInvokingProxyCallbacks = false; - } - } - - @Override - public void binderDied() { - if (DBG) { - Slog.w(TAG, "Profile service for profile: " + mClassName + " died."); - } - onServiceDisconnected(mClassName); - // Trigger rebind - Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE); - msg.obj = this; - mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS); - } - } - - private void sendBluetoothStateCallback(boolean isUp) { - try { - int n = mStateChangeCallbacks.beginBroadcast(); - if (DBG) { - Slog.d(TAG, "Broadcasting onBluetoothStateChange(" + isUp + ") to " + n - + " receivers."); - } - for (int i = 0; i < n; i++) { - try { - mStateChangeCallbacks.getBroadcastItem(i).onBluetoothStateChange(isUp); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBluetoothStateChange() on callback #" + i, e); - } - } - } finally { - mStateChangeCallbacks.finishBroadcast(); - } - } - - /** - * Inform BluetoothAdapter instances that Adapter service is up - */ - private void sendBluetoothServiceUpCallback() { - synchronized (mCallbacks) { - try { - int n = mCallbacks.beginBroadcast(); - Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers."); - for (int i = 0; i < n; i++) { - try { - mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e); - } - } - } finally { - mCallbacks.finishBroadcast(); - } - } - } - - /** - * Inform BluetoothAdapter instances that Adapter service is down - */ - private void sendBluetoothServiceDownCallback() { - synchronized (mCallbacks) { - try { - int n = mCallbacks.beginBroadcast(); - Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers."); - for (int i = 0; i < n; i++) { - try { - mCallbacks.getBroadcastItem(i).onBluetoothServiceDown(); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e); - } - } - } finally { - mCallbacks.finishBroadcast(); - } - } - } - - public String getAddress(AttributionSource attributionSource) { - if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, "getAddress")) { - return null; - } - - if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG, "getAddress(): not allowed for non-active and non system user"); - return null; - } - - if (mContext.checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS) - != PackageManager.PERMISSION_GRANTED) { - return BluetoothAdapter.DEFAULT_MAC_ADDRESS; - } - - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - return mBluetooth.getAddressWithAttribution(attributionSource); - } - } catch (RemoteException e) { - Slog.e(TAG, - "getAddress(): Unable to retrieve address remotely. Returning cached address", - e); - } finally { - mBluetoothLock.readLock().unlock(); - } - - // mAddress is accessed from outside. - // It is alright without a lock. Here, bluetooth is off, no other thread is - // changing mAddress - return mAddress; - } - - public String getName(AttributionSource attributionSource) { - if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, "getName")) { - return null; - } - - if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG, "getName(): not allowed for non-active and non system user"); - return null; - } - - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - return mBluetooth.getName(attributionSource); - } - } catch (RemoteException e) { - Slog.e(TAG, "getName(): Unable to retrieve name remotely. Returning cached name", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - - // mName is accessed from outside. - // It alright without a lock. Here, bluetooth is off, no other thread is - // changing mName - return mName; - } - - private class BluetoothServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName componentName, IBinder service) { - String name = componentName.getClassName(); - if (DBG) { - Slog.d(TAG, "BluetoothServiceConnection: " + name); - } - Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED); - if (name.equals("com.android.bluetooth.btservice.AdapterService")) { - msg.arg1 = SERVICE_IBLUETOOTH; - } else if (name.equals("com.android.bluetooth.gatt.GattService")) { - msg.arg1 = SERVICE_IBLUETOOTHGATT; - } else { - Slog.e(TAG, "Unknown service connected: " + name); - return; - } - msg.obj = service; - mHandler.sendMessage(msg); - } - - public void onServiceDisconnected(ComponentName componentName) { - // Called if we unexpectedly disconnect. - String name = componentName.getClassName(); - if (DBG) { - Slog.d(TAG, "BluetoothServiceConnection, disconnected: " + name); - } - Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED); - if (name.equals("com.android.bluetooth.btservice.AdapterService")) { - msg.arg1 = SERVICE_IBLUETOOTH; - } else if (name.equals("com.android.bluetooth.gatt.GattService")) { - msg.arg1 = SERVICE_IBLUETOOTHGATT; - } else { - Slog.e(TAG, "Unknown service disconnected: " + name); - return; - } - mHandler.sendMessage(msg); - } - } - - private BluetoothServiceConnection mConnection = new BluetoothServiceConnection(); - - private class BluetoothHandler extends Handler { - boolean mGetNameAddressOnly = false; - private int mWaitForEnableRetry; - private int mWaitForDisableRetry; - - BluetoothHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_GET_NAME_AND_ADDRESS: - if (DBG) { - Slog.d(TAG, "MESSAGE_GET_NAME_AND_ADDRESS"); - } - try { - mBluetoothLock.writeLock().lock(); - if ((mBluetooth == null) && (!mBinding)) { - if (DBG) { - Slog.d(TAG, "Binding to service to get name and address"); - } - mGetNameAddressOnly = true; - Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND); - mHandler.sendMessageDelayed(timeoutMsg, TIMEOUT_BIND_MS); - Intent i = new Intent(IBluetooth.class.getName()); - if (!doBind(i, mConnection, - Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.CURRENT)) { - mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); - } else { - mBinding = true; - } - } else if (mBluetooth != null) { - try { - storeNameAndAddress( - mBluetooth.getName(mContext.getAttributionSource()), - mBluetooth.getAddressWithAttribution( - mContext.getAttributionSource())); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to grab names", re); - } - if (mGetNameAddressOnly && !mEnable) { - unbindAndFinish(); - } - mGetNameAddressOnly = false; - } - } finally { - mBluetoothLock.writeLock().unlock(); - } - break; - - case MESSAGE_ENABLE: - int quietEnable = msg.arg1; - int isBle = msg.arg2; - if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) - || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) { - // We are handling enable or disable right now, wait for it. - mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ENABLE, - quietEnable, isBle), ENABLE_DISABLE_DELAY_MS); - break; - } - - if (DBG) { - Slog.d(TAG, "MESSAGE_ENABLE(" + quietEnable + "): mBluetooth = " - + mBluetooth); - } - mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mEnable = true; - - if (isBle == 0) { - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - } - - // Use service interface to get the exact state - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - boolean isHandled = true; - int state = mBluetooth.getState(); - switch (state) { - case BluetoothAdapter.STATE_BLE_ON: - if (isBle == 1) { - Slog.i(TAG, "Already at BLE_ON State"); - } else { - Slog.w(TAG, "BT Enable in BLE_ON State, going to ON"); - mBluetooth.onLeServiceUp(mContext.getAttributionSource()); - } - break; - case BluetoothAdapter.STATE_BLE_TURNING_ON: - case BluetoothAdapter.STATE_TURNING_ON: - case BluetoothAdapter.STATE_ON: - Slog.i(TAG, "MESSAGE_ENABLE: already enabled"); - break; - default: - isHandled = false; - break; - } - if (isHandled) break; - } - } catch (RemoteException e) { - Slog.e(TAG, "", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - - mQuietEnable = (quietEnable == 1); - if (mBluetooth == null) { - handleEnable(mQuietEnable); - } else { - // - // We need to wait until transitioned to STATE_OFF and - // the previous Bluetooth process has exited. The - // waiting period has three components: - // (a) Wait until the local state is STATE_OFF. This - // is accomplished by sending delay a message - // MESSAGE_HANDLE_ENABLE_DELAYED - // (b) Wait until the STATE_OFF state is updated to - // all components. - // (c) Wait until the Bluetooth process exits, and - // ActivityManager detects it. - // The waiting for (b) and (c) is accomplished by - // delaying the MESSAGE_RESTART_BLUETOOTH_SERVICE - // message. The delay time is backed off if Bluetooth - // continuously failed to turn on itself. - // - mWaitForEnableRetry = 0; - Message enableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_ENABLE_DELAYED); - mHandler.sendMessageDelayed(enableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - } - break; - - case MESSAGE_DISABLE: - if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) || mBinding - || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) { - // We are handling enable or disable right now, wait for it. - mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_DISABLE), - ENABLE_DISABLE_DELAY_MS); - break; - } - - if (DBG) { - Slog.d(TAG, "MESSAGE_DISABLE: mBluetooth = " + mBluetooth - + ", mBinding = " + mBinding); - } - mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); - - if (mEnable && mBluetooth != null) { - mWaitForDisableRetry = 0; - Message disableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 0, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - } else { - mEnable = false; - handleDisable(); - } - break; - - case MESSAGE_HANDLE_ENABLE_DELAYED: { - // The Bluetooth is turning off, wait for STATE_OFF - if (mState != BluetoothAdapter.STATE_OFF) { - if (mWaitForEnableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForEnableRetry++; - Message enableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_ENABLE_DELAYED); - mHandler.sendMessageDelayed(enableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - break; - } else { - Slog.e(TAG, "Wait for STATE_OFF timeout"); - } - } - // Either state is changed to STATE_OFF or reaches the maximum retry, we - // should move forward to the next step. - mWaitForEnableRetry = 0; - Message restartMsg = - mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs()); - Slog.d(TAG, "Handle enable is finished"); - break; - } - - case MESSAGE_HANDLE_DISABLE_DELAYED: { - boolean disabling = (msg.arg1 == 1); - Slog.d(TAG, "MESSAGE_HANDLE_DISABLE_DELAYED: disabling:" + disabling); - if (!disabling) { - // The Bluetooth is turning on, wait for STATE_ON - if (mState != BluetoothAdapter.STATE_ON) { - if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForDisableRetry++; - Message disableDelayedMsg = mHandler.obtainMessage( - MESSAGE_HANDLE_DISABLE_DELAYED, 0, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, - ENABLE_DISABLE_DELAY_MS); - break; - } else { - Slog.e(TAG, "Wait for STATE_ON timeout"); - } - } - // Either state is changed to STATE_ON or reaches the maximum retry, we - // should move forward to the next step. - mWaitForDisableRetry = 0; - mEnable = false; - handleDisable(); - // Wait for state exiting STATE_ON - Message disableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - } else { - // The Bluetooth is turning off, wait for exiting STATE_ON - if (mState == BluetoothAdapter.STATE_ON) { - if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForDisableRetry++; - Message disableDelayedMsg = mHandler.obtainMessage( - MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, - ENABLE_DISABLE_DELAY_MS); - break; - } else { - Slog.e(TAG, "Wait for exiting STATE_ON timeout"); - } - } - // Either state is exited from STATE_ON or reaches the maximum retry, we - // should move forward to the next step. - Slog.d(TAG, "Handle disable is finished"); - } - break; - } - - case MESSAGE_RESTORE_USER_SETTING: - if ((msg.arg1 == RESTORE_SETTING_TO_OFF) && mEnable) { - if (DBG) { - Slog.d(TAG, "Restore Bluetooth state to disabled"); - } - persistBluetoothSetting(BLUETOOTH_OFF); - mEnableExternal = false; - sendDisableMsg( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING, - mContext.getPackageName()); - } else if ((msg.arg1 == RESTORE_SETTING_TO_ON) && !mEnable) { - if (DBG) { - Slog.d(TAG, "Restore Bluetooth state to enabled"); - } - mQuietEnableExternal = false; - mEnableExternal = true; - // waive WRITE_SECURE_SETTINGS permission check - sendEnableMsg(false, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING, - mContext.getPackageName()); - } - break; - case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: { - IBluetoothStateChangeCallback callback = - (IBluetoothStateChangeCallback) msg.obj; - mStateChangeCallbacks.register(callback); - break; - } - case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK: { - IBluetoothStateChangeCallback callback = - (IBluetoothStateChangeCallback) msg.obj; - mStateChangeCallbacks.unregister(callback); - break; - } - case MESSAGE_ADD_PROXY_DELAYED: { - ProfileServiceConnections psc = mProfileServices.get(msg.arg1); - if (psc == null) { - break; - } - IBluetoothProfileServiceConnection proxy = - (IBluetoothProfileServiceConnection) msg.obj; - psc.addProxy(proxy); - break; - } - case MESSAGE_BIND_PROFILE_SERVICE: { - ProfileServiceConnections psc = (ProfileServiceConnections) msg.obj; - removeMessages(MESSAGE_BIND_PROFILE_SERVICE, msg.obj); - if (psc == null) { - break; - } - psc.bindService(); - break; - } - case MESSAGE_BLUETOOTH_SERVICE_CONNECTED: { - if (DBG) { - Slog.d(TAG, "MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1); - } - - IBinder service = (IBinder) msg.obj; - try { - mBluetoothLock.writeLock().lock(); - if (msg.arg1 == SERVICE_IBLUETOOTHGATT) { - mBluetoothGatt = - IBluetoothGatt.Stub.asInterface(Binder.allowBlocking(service)); - continueFromBleOnState(); - break; - } // else must be SERVICE_IBLUETOOTH - - //Remove timeout - mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); - - mBinding = false; - mBluetoothBinder = service; - mBluetooth = IBluetooth.Stub.asInterface(Binder.allowBlocking(service)); - - if (!isNameAndAddressSet()) { - Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); - mHandler.sendMessage(getMsg); - if (mGetNameAddressOnly) { - return; - } - } - - //Register callback object - try { - mBluetooth.registerCallback(mBluetoothCallback, - mContext.getAttributionSource()); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to register BluetoothCallback", re); - } - //Inform BluetoothAdapter instances that service is up - sendBluetoothServiceUpCallback(); - - //Do enable request - try { - if (!mBluetooth.enable(mQuietEnable, mContext.getAttributionSource())) { - Slog.e(TAG, "IBluetooth.enable() returned false"); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call enable()", e); - } - } finally { - mBluetoothLock.writeLock().unlock(); - } - - if (!mEnable) { - waitForState(Set.of(BluetoothAdapter.STATE_ON)); - handleDisable(); - waitForState(Set.of(BluetoothAdapter.STATE_OFF, - BluetoothAdapter.STATE_TURNING_ON, - BluetoothAdapter.STATE_TURNING_OFF, - BluetoothAdapter.STATE_BLE_TURNING_ON, - BluetoothAdapter.STATE_BLE_ON, - BluetoothAdapter.STATE_BLE_TURNING_OFF)); - } - break; - } - case MESSAGE_BLUETOOTH_STATE_CHANGE: { - int prevState = msg.arg1; - int newState = msg.arg2; - if (DBG) { - Slog.d(TAG, - "MESSAGE_BLUETOOTH_STATE_CHANGE: " + BluetoothAdapter.nameForState( - prevState) + " > " + BluetoothAdapter.nameForState( - newState)); - } - mState = newState; - bluetoothStateChangeHandler(prevState, newState); - // handle error state transition case from TURNING_ON to OFF - // unbind and rebind bluetooth service and enable bluetooth - if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_ON) && (newState - == BluetoothAdapter.STATE_OFF) && (mBluetooth != null) && mEnable) { - recoverBluetoothServiceFromError(false); - } - if ((prevState == BluetoothAdapter.STATE_TURNING_ON) && (newState - == BluetoothAdapter.STATE_BLE_ON) && (mBluetooth != null) && mEnable) { - recoverBluetoothServiceFromError(true); - } - // If we tried to enable BT while BT was in the process of shutting down, - // wait for the BT process to fully tear down and then force a restart - // here. This is a bit of a hack (b/29363429). - if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_OFF) && (newState - == BluetoothAdapter.STATE_OFF)) { - if (mEnable) { - Slog.d(TAG, "Entering STATE_OFF but mEnabled is true; restarting."); - waitForState(Set.of(BluetoothAdapter.STATE_OFF)); - Message restartMsg = - mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs()); - } - } - if (newState == BluetoothAdapter.STATE_ON - || newState == BluetoothAdapter.STATE_BLE_ON) { - // bluetooth is working, reset the counter - if (mErrorRecoveryRetryCounter != 0) { - Slog.w(TAG, "bluetooth is recovered from error"); - mErrorRecoveryRetryCounter = 0; - } - } - break; - } - case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: { - Slog.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED(" + msg.arg1 + ")"); - try { - mBluetoothLock.writeLock().lock(); - if (msg.arg1 == SERVICE_IBLUETOOTH) { - // if service is unbinded already, do nothing and return - if (mBluetooth == null) { - break; - } - mBluetooth = null; - } else if (msg.arg1 == SERVICE_IBLUETOOTHGATT) { - mBluetoothGatt = null; - break; - } else { - Slog.e(TAG, "Unknown argument for service disconnect!"); - break; - } - } finally { - mBluetoothLock.writeLock().unlock(); - } - - // log the unexpected crash - addCrashLog(); - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH, - mContext.getPackageName(), false); - if (mEnable) { - mEnable = false; - // Send a Bluetooth Restart message - Message restartMsg = - mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs()); - } - - sendBluetoothServiceDownCallback(); - - // Send BT state broadcast to update - // the BT icon correctly - if ((mState == BluetoothAdapter.STATE_TURNING_ON) || (mState - == BluetoothAdapter.STATE_ON)) { - bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON, - BluetoothAdapter.STATE_TURNING_OFF); - mState = BluetoothAdapter.STATE_TURNING_OFF; - } - if (mState == BluetoothAdapter.STATE_TURNING_OFF) { - bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF, - BluetoothAdapter.STATE_OFF); - } - - mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); - mState = BluetoothAdapter.STATE_OFF; - break; - } - case MESSAGE_RESTART_BLUETOOTH_SERVICE: { - mErrorRecoveryRetryCounter++; - Slog.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE: retry count=" - + mErrorRecoveryRetryCounter); - if (mErrorRecoveryRetryCounter < MAX_ERROR_RESTART_RETRIES) { - /* Enable without persisting the setting as - it doesnt change when IBluetooth - service restarts */ - mEnable = true; - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED, - mContext.getPackageName(), true); - handleEnable(mQuietEnable); - } else { - Slog.e(TAG, "Reach maximum retry to restart Bluetooth!"); - } - break; - } - case MESSAGE_TIMEOUT_BIND: { - Slog.e(TAG, "MESSAGE_TIMEOUT_BIND"); - mBluetoothLock.writeLock().lock(); - mBinding = false; - mBluetoothLock.writeLock().unlock(); - break; - } - case MESSAGE_TIMEOUT_UNBIND: { - Slog.e(TAG, "MESSAGE_TIMEOUT_UNBIND"); - mBluetoothLock.writeLock().lock(); - mUnbinding = false; - mBluetoothLock.writeLock().unlock(); - break; - } - - case MESSAGE_USER_SWITCHED: { - if (DBG) { - Slog.d(TAG, "MESSAGE_USER_SWITCHED"); - } - mHandler.removeMessages(MESSAGE_USER_SWITCHED); - - /* disable and enable BT when detect a user switch */ - if (mBluetooth != null && isEnabled()) { - restartForReason(BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH); - } else if (mBinding || mBluetooth != null) { - Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED); - userMsg.arg2 = 1 + msg.arg2; - // if user is switched when service is binding retry after a delay - mHandler.sendMessageDelayed(userMsg, USER_SWITCHED_TIME_MS); - if (DBG) { - Slog.d(TAG, "Retry MESSAGE_USER_SWITCHED " + userMsg.arg2); - } - } - break; - } - case MESSAGE_USER_UNLOCKED: { - if (DBG) { - Slog.d(TAG, "MESSAGE_USER_UNLOCKED"); - } - mHandler.removeMessages(MESSAGE_USER_SWITCHED); - - if (mEnable && !mBinding && (mBluetooth == null)) { - // We should be connected, but we gave up for some - // reason; maybe the Bluetooth service wasn't encryption - // aware, so try binding again. - if (DBG) { - Slog.d(TAG, "Enabled but not bound; retrying after unlock"); - } - handleEnable(mQuietEnable); - } - break; - } - case MESSAGE_INIT_FLAGS_CHANGED: { - if (DBG) { - Slog.d(TAG, "MESSAGE_INIT_FLAGS_CHANGED"); - } - mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED); - if (mBluetoothModeChangeHelper.isMediaProfileConnected()) { - Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " - + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS - + " ms due to existing connections"); - mHandler.sendEmptyMessageDelayed( - MESSAGE_INIT_FLAGS_CHANGED, - DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS); - break; - } - if (!isDeviceProvisioned()) { - Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " - + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS - + "ms because device is not provisioned"); - mHandler.sendEmptyMessageDelayed( - MESSAGE_INIT_FLAGS_CHANGED, - DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS); - break; - } - if (mBluetooth != null && isEnabled()) { - Slog.i(TAG, "Restarting Bluetooth due to init flag change"); - restartForReason( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED); - } - break; - } - } - } - - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED - }) - private void restartForReason(int reason) { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - mBluetooth.unregisterCallback(mBluetoothCallback, - mContext.getAttributionSource()); - } - } catch (RemoteException re) { - Slog.e(TAG, "Unable to unregister", re); - } finally { - mBluetoothLock.readLock().unlock(); - } - - if (mState == BluetoothAdapter.STATE_TURNING_OFF) { - // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE - bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_OFF); - mState = BluetoothAdapter.STATE_OFF; - } - if (mState == BluetoothAdapter.STATE_OFF) { - bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_TURNING_ON); - mState = BluetoothAdapter.STATE_TURNING_ON; - } - - waitForState(Set.of(BluetoothAdapter.STATE_ON)); - - if (mState == BluetoothAdapter.STATE_TURNING_ON) { - bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON); - } - - unbindAllBluetoothProfileServices(); - // disable - addActiveLog(reason, mContext.getPackageName(), false); - handleDisable(); - // Pbap service need receive STATE_TURNING_OFF intent to close - bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON, - BluetoothAdapter.STATE_TURNING_OFF); - - boolean didDisableTimeout = - !waitForState(Set.of(BluetoothAdapter.STATE_OFF)); - - bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF, - BluetoothAdapter.STATE_OFF); - sendBluetoothServiceDownCallback(); - - try { - mBluetoothLock.writeLock().lock(); - if (mBluetooth != null) { - mBluetooth = null; - // Unbind - mContext.unbindService(mConnection); - } - mBluetoothGatt = null; - } finally { - mBluetoothLock.writeLock().unlock(); - } - - // - // If disabling Bluetooth times out, wait for an - // additional amount of time to ensure the process is - // shut down completely before attempting to restart. - // - if (didDisableTimeout) { - SystemClock.sleep(3000); - } else { - SystemClock.sleep(100); - } - - mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); - mState = BluetoothAdapter.STATE_OFF; - // enable - addActiveLog(reason, mContext.getPackageName(), true); - // mEnable flag could have been reset on disableBLE. Reenable it. - mEnable = true; - handleEnable(mQuietEnable); - } - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void handleEnable(boolean quietMode) { - mQuietEnable = quietMode; - - try { - mBluetoothLock.writeLock().lock(); - if ((mBluetooth == null) && (!mBinding)) { - Slog.d(TAG, "binding Bluetooth service"); - //Start bind timeout and bind - Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND); - mHandler.sendMessageDelayed(timeoutMsg, TIMEOUT_BIND_MS); - Intent i = new Intent(IBluetooth.class.getName()); - if (!doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.CURRENT)) { - mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); - } else { - mBinding = true; - } - } else if (mBluetooth != null) { - //Enable bluetooth - try { - if (!mBluetooth.enable(mQuietEnable, mContext.getAttributionSource())) { - Slog.e(TAG, "IBluetooth.enable() returned false"); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call enable()", e); - } - } - } finally { - mBluetoothLock.writeLock().unlock(); - } - } - - boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) { - ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); - intent.setComponent(comp); - if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) { - Slog.e(TAG, "Fail to bind to: " + intent); - return false; - } - return true; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void handleDisable() { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - if (DBG) { - Slog.d(TAG, "Sending off request."); - } - if (!mBluetooth.disable(mContext.getAttributionSource())) { - Slog.e(TAG, "IBluetooth.disable() returned false"); - } - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call disable()", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } - - private boolean checkIfCallerIsForegroundUser() { - int foregroundUser; - int callingUser = UserHandle.getCallingUserId(); - int callingUid = Binder.getCallingUid(); - final long callingIdentity = Binder.clearCallingIdentity(); - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - UserInfo ui = um.getProfileParent(callingUser); - int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL; - int callingAppId = UserHandle.getAppId(callingUid); - boolean valid = false; - try { - foregroundUser = ActivityManager.getCurrentUser(); - valid = (callingUser == foregroundUser) || parentUser == foregroundUser - || callingAppId == Process.NFC_UID || callingAppId == mSystemUiUid - || callingAppId == Process.SHELL_UID; - if (DBG && !valid) { - Slog.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid + " callingUser=" - + callingUser + " parentUser=" + parentUser + " foregroundUser=" - + foregroundUser); - } - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - return valid; - } - - private void sendBleStateChanged(int prevState, int newState) { - if (DBG) { - Slog.d(TAG, - "Sending BLE State Change: " + BluetoothAdapter.nameForState(prevState) + " > " - + BluetoothAdapter.nameForState(newState)); - } - // Send broadcast message to everyone else - Intent intent = new Intent(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, getTempAllowlistBroadcastOptions()); - } - - private boolean isBleState(int state) { - switch (state) { - case BluetoothAdapter.STATE_BLE_ON: - case BluetoothAdapter.STATE_BLE_TURNING_ON: - case BluetoothAdapter.STATE_BLE_TURNING_OFF: - return true; - } - return false; - } - - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - private void bluetoothStateChangeHandler(int prevState, int newState) { - boolean isStandardBroadcast = true; - if (prevState == newState) { // No change. Nothing to do. - return; - } - // Notify all proxy objects first of adapter state change - if (newState == BluetoothAdapter.STATE_BLE_ON || newState == BluetoothAdapter.STATE_OFF) { - boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF - && newState == BluetoothAdapter.STATE_BLE_ON); - - if (newState == BluetoothAdapter.STATE_OFF) { - // If Bluetooth is off, send service down event to proxy objects, and unbind - if (DBG) { - Slog.d(TAG, "Bluetooth is complete send Service Down"); - } - sendBluetoothServiceDownCallback(); - unbindAndFinish(); - sendBleStateChanged(prevState, newState); - - /* Currently, the OFF intent is broadcasted externally only when we transition - * from TURNING_OFF to BLE_ON state. So if the previous state is a BLE state, - * we are guaranteed that the OFF intent has been broadcasted earlier and we - * can safely skip it. - * Conversely, if the previous state is not a BLE state, it indicates that some - * sort of crash has occurred, moving us directly to STATE_OFF without ever - * passing through BLE_ON. We should broadcast the OFF intent in this case. */ - isStandardBroadcast = !isBleState(prevState); - - } else if (!intermediate_off) { - // connect to GattService - if (DBG) { - Slog.d(TAG, "Bluetooth is in LE only mode"); - } - if (mBluetoothGatt != null || !mContext.getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { - continueFromBleOnState(); - } else { - if (DBG) { - Slog.d(TAG, "Binding Bluetooth GATT service"); - } - Intent i = new Intent(IBluetoothGatt.class.getName()); - doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.CURRENT); - } - sendBleStateChanged(prevState, newState); - //Don't broadcase this as std intent - isStandardBroadcast = false; - - } else if (intermediate_off) { - if (DBG) { - Slog.d(TAG, "Intermediate off, back to LE only mode"); - } - // For LE only mode, broadcast as is - sendBleStateChanged(prevState, newState); - sendBluetoothStateCallback(false); // BT is OFF for general users - // Broadcast as STATE_OFF - newState = BluetoothAdapter.STATE_OFF; - sendBrEdrDownCallback(mContext.getAttributionSource()); - } - } else if (newState == BluetoothAdapter.STATE_ON) { - boolean isUp = (newState == BluetoothAdapter.STATE_ON); - sendBluetoothStateCallback(isUp); - sendBleStateChanged(prevState, newState); - - } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON - || newState == BluetoothAdapter.STATE_BLE_TURNING_OFF) { - sendBleStateChanged(prevState, newState); - isStandardBroadcast = false; - - } else if (newState == BluetoothAdapter.STATE_TURNING_ON - || newState == BluetoothAdapter.STATE_TURNING_OFF) { - sendBleStateChanged(prevState, newState); - } - - if (isStandardBroadcast) { - if (prevState == BluetoothAdapter.STATE_BLE_ON) { - // Show prevState of BLE_ON as OFF to standard users - prevState = BluetoothAdapter.STATE_OFF; - } - if (DBG) { - Slog.d(TAG, - "Sending State Change: " + BluetoothAdapter.nameForState(prevState) + " > " - + BluetoothAdapter.nameForState(newState)); - } - Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, - getTempAllowlistBroadcastOptions()); - } - } - - private boolean waitForState(Set<Integer> states) { - int i = 0; - while (i < 10) { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth == null) { - break; - } - if (states.contains(mBluetooth.getState())) { - return true; - } - } catch (RemoteException e) { - Slog.e(TAG, "getState()", e); - break; - } finally { - mBluetoothLock.readLock().unlock(); - } - SystemClock.sleep(300); - i++; - } - Slog.e(TAG, "waitForState " + states + " time out"); - return false; - } - - private void sendDisableMsg(int reason, String packageName) { - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE)); - addActiveLog(reason, packageName, false); - } - - private void sendEnableMsg(boolean quietMode, int reason, String packageName) { - sendEnableMsg(quietMode, reason, packageName, false); - } - - private void sendEnableMsg(boolean quietMode, int reason, String packageName, boolean isBle) { - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE, quietMode ? 1 : 0, - isBle ? 1 : 0)); - addActiveLog(reason, packageName, true); - mLastEnabledTime = SystemClock.elapsedRealtime(); - } - - private void addActiveLog(int reason, String packageName, boolean enable) { - synchronized (mActiveLogs) { - if (mActiveLogs.size() > ACTIVE_LOG_MAX_SIZE) { - mActiveLogs.remove(); - } - mActiveLogs.add( - new ActiveLog(reason, packageName, enable, System.currentTimeMillis())); - } - - int state = enable ? FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED : - FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED; - FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED, - Binder.getCallingUid(), null, state, reason, packageName); - } - - private void addCrashLog() { - synchronized (mCrashTimestamps) { - if (mCrashTimestamps.size() == CRASH_LOG_MAX_SIZE) { - mCrashTimestamps.removeFirst(); - } - mCrashTimestamps.add(System.currentTimeMillis()); - mCrashes++; - } - } - - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - private void recoverBluetoothServiceFromError(boolean clearBle) { - Slog.e(TAG, "recoverBluetoothServiceFromError"); - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - //Unregister callback object - mBluetooth.unregisterCallback(mBluetoothCallback, mContext.getAttributionSource()); - } - } catch (RemoteException re) { - Slog.e(TAG, "Unable to unregister", re); - } finally { - mBluetoothLock.readLock().unlock(); - } - - SystemClock.sleep(500); - - // disable - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR, - mContext.getPackageName(), false); - handleDisable(); - - waitForState(Set.of(BluetoothAdapter.STATE_OFF)); - - sendBluetoothServiceDownCallback(); - - try { - mBluetoothLock.writeLock().lock(); - if (mBluetooth != null) { - mBluetooth = null; - // Unbind - mContext.unbindService(mConnection); - } - mBluetoothGatt = null; - } finally { - mBluetoothLock.writeLock().unlock(); - } - - mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); - mState = BluetoothAdapter.STATE_OFF; - - if (clearBle) { - clearBleApps(); - } - - mEnable = false; - - // Send a Bluetooth Restart message to reenable bluetooth - Message restartMsg = mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mHandler.sendMessageDelayed(restartMsg, ERROR_RESTART_TIME_MS); - } - - private boolean isBluetoothDisallowed() { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - return mContext.getSystemService(UserManager.class) - .hasUserRestriction(UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM); - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - } - - /** - * Disables BluetoothOppLauncherActivity component, so the Bluetooth sharing option is not - * offered to the user if Bluetooth or sharing is disallowed. Puts the component to its default - * state if Bluetooth is not disallowed. - * - * @param userId user to disable bluetooth sharing for. - * @param bluetoothSharingDisallowed whether bluetooth sharing is disallowed. - */ - private void updateOppLauncherComponentState(int userId, boolean bluetoothSharingDisallowed) { - final ComponentName oppLauncherComponent = new ComponentName("com.android.bluetooth", - "com.android.bluetooth.opp.BluetoothOppLauncherActivity"); - final int newState = - bluetoothSharingDisallowed ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED - : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; - try { - final IPackageManager imp = AppGlobals.getPackageManager(); - imp.setComponentEnabledSetting(oppLauncherComponent, newState, - PackageManager.DONT_KILL_APP, userId); - } catch (Exception e) { - // The component was not found, do nothing. - } - } - - private int getServiceRestartMs() { - return (mErrorRecoveryRetryCounter + 1) * SERVICE_RESTART_TIME_MS; - } - - @Override - public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) { - return; - } - if ((args.length > 0) && args[0].startsWith("--proto")) { - dumpProto(fd); - return; - } - String errorMsg = null; - - writer.println("Bluetooth Status"); - writer.println(" enabled: " + isEnabled()); - writer.println(" state: " + BluetoothAdapter.nameForState(mState)); - writer.println(" address: " + mAddress); - writer.println(" name: " + mName); - if (mEnable) { - long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime; - String onDurationString = String.format(Locale.US, "%02d:%02d:%02d.%03d", - (int) (onDuration / (1000 * 60 * 60)), - (int) ((onDuration / (1000 * 60)) % 60), (int) ((onDuration / 1000) % 60), - (int) (onDuration % 1000)); - writer.println(" time since enabled: " + onDurationString); - } - - if (mActiveLogs.size() == 0) { - writer.println("\nBluetooth never enabled!"); - } else { - writer.println("\nEnable log:"); - for (ActiveLog log : mActiveLogs) { - writer.println(" " + log); - } - } - - writer.println( - "\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s")); - if (mCrashes == CRASH_LOG_MAX_SIZE) { - writer.println("(last " + CRASH_LOG_MAX_SIZE + ")"); - } - for (Long time : mCrashTimestamps) { - writer.println(" " + timeToLog(time)); - } - - writer.println("\n" + mBleApps.size() + " BLE app" + (mBleApps.size() == 1 ? "" : "s") - + " registered"); - for (ClientDeathRecipient app : mBleApps.values()) { - writer.println(" " + app.getPackageName()); - } - - writer.println("\nBluetoothManagerService:"); - writer.println(" mEnable:" + mEnable); - writer.println(" mQuietEnable:" + mQuietEnable); - writer.println(" mEnableExternal:" + mEnableExternal); - writer.println(" mQuietEnableExternal:" + mQuietEnableExternal); - - writer.println(""); - writer.flush(); - if (args.length == 0) { - // Add arg to produce output - args = new String[1]; - args[0] = "--print"; - } - - if (mBluetoothBinder == null) { - errorMsg = "Bluetooth Service not connected"; - } else { - try { - mBluetoothBinder.dump(fd, args); - } catch (RemoteException re) { - errorMsg = "RemoteException while dumping Bluetooth Service"; - } - } - if (errorMsg != null) { - writer.println(errorMsg); - } - } - - private void dumpProto(FileDescriptor fd) { - final ProtoOutputStream proto = new ProtoOutputStream(fd); - proto.write(BluetoothManagerServiceDumpProto.ENABLED, isEnabled()); - proto.write(BluetoothManagerServiceDumpProto.STATE, mState); - proto.write(BluetoothManagerServiceDumpProto.STATE_NAME, - BluetoothAdapter.nameForState(mState)); - proto.write(BluetoothManagerServiceDumpProto.ADDRESS, mAddress); - proto.write(BluetoothManagerServiceDumpProto.NAME, mName); - if (mEnable) { - proto.write(BluetoothManagerServiceDumpProto.LAST_ENABLED_TIME_MS, mLastEnabledTime); - } - proto.write(BluetoothManagerServiceDumpProto.CURR_TIMESTAMP_MS, - SystemClock.elapsedRealtime()); - for (ActiveLog log : mActiveLogs) { - long token = proto.start(BluetoothManagerServiceDumpProto.ACTIVE_LOGS); - log.dump(proto); - proto.end(token); - } - proto.write(BluetoothManagerServiceDumpProto.NUM_CRASHES, mCrashes); - proto.write(BluetoothManagerServiceDumpProto.CRASH_LOG_MAXED, - mCrashes == CRASH_LOG_MAX_SIZE); - for (Long time : mCrashTimestamps) { - proto.write(BluetoothManagerServiceDumpProto.CRASH_TIMESTAMPS_MS, time); - } - proto.write(BluetoothManagerServiceDumpProto.NUM_BLE_APPS, mBleApps.size()); - for (ClientDeathRecipient app : mBleApps.values()) { - proto.write(BluetoothManagerServiceDumpProto.BLE_APP_PACKAGE_NAMES, - app.getPackageName()); - } - proto.flush(); - } - - private static String getEnableDisableReasonString(int reason) { - switch (reason) { - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST: - return "APPLICATION_REQUEST"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE: - return "AIRPLANE_MODE"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED: - return "DISALLOWED"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED: - return "RESTARTED"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR: - return "START_ERROR"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT: - return "SYSTEM_BOOT"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH: - return "CRASH"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH: - return "USER_SWITCH"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING: - return "RESTORE_USER_SETTING"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET: - return "FACTORY_RESET"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED: - return "INIT_FLAGS_CHANGED"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_UNSPECIFIED: - default: return "UNKNOWN[" + reason + "]"; - } - } - - @SuppressLint("AndroidFrameworkRequiresPermission") - private static boolean checkPermissionForDataDelivery(Context context, String permission, - AttributionSource attributionSource, String message) { - PermissionManager pm = context.getSystemService(PermissionManager.class); - if (pm == null) { - return false; - } - AttributionSource currentAttribution = new AttributionSource - .Builder(context.getAttributionSource()) - .setNext(attributionSource) - .build(); - final int result = pm.checkPermissionForDataDeliveryFromDataSource(permission, - currentAttribution, message); - if (result == PERMISSION_GRANTED) { - return true; - } - - final String msg = "Need " + permission + " permission for " + attributionSource + ": " - + message; - if (result == PERMISSION_HARD_DENIED) { - throw new SecurityException(msg); - } else { - Log.w(TAG, msg); - return false; - } - } - - /** - * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns - * false if the result is a soft denial. Throws SecurityException if the result is a hard - * denial. - * - * <p>Should be used in situations where the app op should not be noted. - */ - @SuppressLint("AndroidFrameworkRequiresPermission") - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public static boolean checkConnectPermissionForDataDelivery( - Context context, AttributionSource attributionSource, String message) { - return checkPermissionForDataDelivery(context, BLUETOOTH_CONNECT, - attributionSource, message); - } - - static @NonNull Bundle getTempAllowlistBroadcastOptions() { - final long duration = 10_000; - final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); - bOptions.setTemporaryAppAllowlist(duration, - TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, - PowerExemptionManager.REASON_BLUETOOTH_BROADCAST, ""); - return bOptions.toBundle(); - } -} diff --git a/services/core/java/com/android/server/BluetoothModeChangeHelper.java b/services/core/java/com/android/server/BluetoothModeChangeHelper.java deleted file mode 100644 index e5854c968207..000000000000 --- a/services/core/java/com/android/server/BluetoothModeChangeHelper.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2020 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; - -import android.annotation.RequiresPermission; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHearingAid; -import android.bluetooth.BluetoothLeAudio; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProfile.ServiceListener; -import android.content.Context; -import android.content.res.Resources; -import android.provider.Settings; -import android.widget.Toast; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; - -/** - * Helper class that handles callout and callback methods without - * complex logic. - */ -public class BluetoothModeChangeHelper { - private volatile BluetoothA2dp mA2dp; - private volatile BluetoothHearingAid mHearingAid; - private volatile BluetoothLeAudio mLeAudio; - private final BluetoothAdapter mAdapter; - private final Context mContext; - - BluetoothModeChangeHelper(Context context) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mContext = context; - - mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP); - mAdapter.getProfileProxy(mContext, mProfileServiceListener, - BluetoothProfile.HEARING_AID); - mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.LE_AUDIO); - } - - private final ServiceListener mProfileServiceListener = new ServiceListener() { - @Override - public void onServiceConnected(int profile, BluetoothProfile proxy) { - // Setup Bluetooth profile proxies - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = (BluetoothA2dp) proxy; - break; - case BluetoothProfile.HEARING_AID: - mHearingAid = (BluetoothHearingAid) proxy; - break; - case BluetoothProfile.LE_AUDIO: - mLeAudio = (BluetoothLeAudio) proxy; - break; - default: - break; - } - } - - @Override - public void onServiceDisconnected(int profile) { - // Clear Bluetooth profile proxies - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = null; - break; - case BluetoothProfile.HEARING_AID: - mHearingAid = null; - break; - case BluetoothProfile.LE_AUDIO: - mLeAudio = null; - break; - default: - break; - } - } - }; - - @VisibleForTesting - public boolean isMediaProfileConnected() { - return isA2dpConnected() || isHearingAidConnected() || isLeAudioConnected(); - } - - @VisibleForTesting - public boolean isBluetoothOn() { - final BluetoothAdapter adapter = mAdapter; - if (adapter == null) { - return false; - } - return adapter.getLeState() == BluetoothAdapter.STATE_ON; - } - - @VisibleForTesting - public boolean isAirplaneModeOn() { - return Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, 0) == 1; - } - - @VisibleForTesting - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onAirplaneModeChanged(BluetoothManagerService managerService) { - managerService.onAirplaneModeChanged(); - } - - @VisibleForTesting - public int getSettingsInt(String name) { - return Settings.Global.getInt(mContext.getContentResolver(), - name, 0); - } - - @VisibleForTesting - public void setSettingsInt(String name, int value) { - Settings.Global.putInt(mContext.getContentResolver(), - name, value); - } - - @VisibleForTesting - public void showToastMessage() { - Resources r = mContext.getResources(); - final CharSequence text = r.getString( - R.string.bluetooth_airplane_mode_toast, 0); - Toast.makeText(mContext, text, Toast.LENGTH_LONG).show(); - } - - private boolean isA2dpConnected() { - final BluetoothA2dp a2dp = mA2dp; - if (a2dp == null) { - return false; - } - return a2dp.getConnectedDevices().size() > 0; - } - - private boolean isHearingAidConnected() { - final BluetoothHearingAid hearingAid = mHearingAid; - if (hearingAid == null) { - return false; - } - return hearingAid.getConnectedDevices().size() > 0; - } - - private boolean isLeAudioConnected() { - final BluetoothLeAudio leAudio = mLeAudio; - if (leAudio == null) { - return false; - } - return leAudio.getConnectedDevices().size() > 0; - } -} diff --git a/services/core/java/com/android/server/BluetoothService.java b/services/core/java/com/android/server/BluetoothService.java deleted file mode 100644 index 1a1eecd0f439..000000000000 --- a/services/core/java/com/android/server/BluetoothService.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2015 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; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.os.UserManager; - -import com.android.server.SystemService.TargetUser; - -class BluetoothService extends SystemService { - private BluetoothManagerService mBluetoothManagerService; - private boolean mInitialized = false; - - public BluetoothService(Context context) { - super(context); - mBluetoothManagerService = new BluetoothManagerService(context); - } - - private void initialize() { - if (!mInitialized) { - mBluetoothManagerService.handleOnBootPhase(); - mInitialized = true; - } - } - - @Override - public void onStart() { - } - - @Override - public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { - publishBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, - mBluetoothManagerService); - } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY && - !UserManager.isHeadlessSystemUserMode()) { - initialize(); - } - } - - @Override - public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { - if (!mInitialized) { - initialize(); - } else { - mBluetoothManagerService.handleOnSwitchUser(to.getUserIdentifier()); - } - } - - @Override - public void onUserUnlocking(@NonNull TargetUser user) { - mBluetoothManagerService.handleOnUnlockUser(user.getUserIdentifier()); - } -} diff --git a/services/core/java/com/android/server/Dumpable.java b/services/core/java/com/android/server/Dumpable.java index 866f81c1c5c6..004f923774e1 100644 --- a/services/core/java/com/android/server/Dumpable.java +++ b/services/core/java/com/android/server/Dumpable.java @@ -24,6 +24,8 @@ import android.util.IndentingPrintWriter; * * <p>See {@link SystemServer.SystemServerDumper} for usage example. */ +// TODO(b/149254050): replace / merge with package android.util.Dumpable (it would require +// exporting IndentingPrintWriter as @SystemApi) and/or changing the method to use a prefix public interface Dumpable { /** diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 513d86e79f48..62dc27330f87 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -126,8 +126,8 @@ public class MasterClearReceiver extends BroadcastReceiver { private boolean wipeUser(Context context, @UserIdInt int userId, String wipeReason) { final UserManager userManager = context.getSystemService(UserManager.class); - final int result = userManager.removeUserOrSetEphemeral( - userId, /* evenWhenDisallowed= */ false); + final int result = userManager.removeUserWhenPossible( + UserHandle.of(userId), /* overrideDevicePolicy= */ false); if (result == UserManager.REMOVE_RESULT_ERROR) { Slogf.e(TAG, "Can't remove user %d", userId); return false; diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index a2c2dbd407a5..39516802e93b 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -39,6 +39,8 @@ import static android.net.NetworkStats.STATS_PER_UID; import static android.net.NetworkStats.TAG_NONE; import static android.net.TrafficStats.UID_TETHERING; +import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT; + import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; @@ -133,12 +135,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub { private static final int MAX_UID_RANGES_PER_COMMAND = 10; - /** - * Name representing {@link #setGlobalAlert(long)} limit when delivered to - * {@link INetworkManagementEventObserver#limitReached(String, String)}. - */ - public static final String LIMIT_GLOBAL_ALERT = "globalAlert"; - static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1; static final boolean MODIFY_OPERATION_ADD = true; diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java index 1abe4588261a..e915fa1522a1 100644 --- a/services/core/java/com/android/server/SerialService.java +++ b/services/core/java/com/android/server/SerialService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.annotation.EnforcePermission; import android.content.Context; import android.hardware.ISerialManager; import android.os.ParcelFileDescriptor; @@ -34,9 +35,8 @@ public class SerialService extends ISerialManager.Stub { com.android.internal.R.array.config_serialPorts); } + @EnforcePermission(android.Manifest.permission.SERIAL_PORT) public String[] getSerialPorts() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null); - ArrayList<String> ports = new ArrayList<String>(); for (int i = 0; i < mSerialPorts.length; i++) { String path = mSerialPorts[i]; @@ -49,8 +49,8 @@ public class SerialService extends ISerialManager.Stub { return result; } + @EnforcePermission(android.Manifest.permission.SERIAL_PORT) public ParcelFileDescriptor openSerialPort(String path) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null); for (int i = 0; i < mSerialPorts.length; i++) { if (mSerialPorts[i].equals(path)) { return native_open(path); diff --git a/services/core/java/com/android/server/SmartStorageMaintIdler.java b/services/core/java/com/android/server/SmartStorageMaintIdler.java new file mode 100644 index 000000000000..2dff72fdc344 --- /dev/null +++ b/services/core/java/com/android/server/SmartStorageMaintIdler.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 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; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.util.Slog; + +import java.util.concurrent.TimeUnit; + +public class SmartStorageMaintIdler extends JobService { + private static final String TAG = "SmartStorageMaintIdler"; + + private static final ComponentName SMART_STORAGE_MAINT_SERVICE = + new ComponentName("android", SmartStorageMaintIdler.class.getName()); + + private static final int SMART_MAINT_JOB_ID = 2808; + + private boolean mStarted; + private JobParameters mJobParams; + private final Runnable mFinishCallback = new Runnable() { + @Override + public void run() { + Slog.i(TAG, "Got smart storage maintenance service completion callback"); + if (mStarted) { + jobFinished(mJobParams, false); + mStarted = false; + } + // ... and try again in a next period + scheduleSmartIdlePass(SmartStorageMaintIdler.this, + StorageManagerService.SMART_IDLE_MAINT_PERIOD); + } + }; + + @Override + public boolean onStartJob(JobParameters params) { + mJobParams = params; + StorageManagerService ms = StorageManagerService.sSelf; + if (ms != null) { + mStarted = true; + ms.runSmartIdleMaint(mFinishCallback); + } + return ms != null; + } + + @Override + public boolean onStopJob(JobParameters params) { + mStarted = false; + return false; + } + + /** + * Schedule the smart storage idle maintenance job + */ + public static void scheduleSmartIdlePass(Context context, int nHours) { + StorageManagerService ms = StorageManagerService.sSelf; + if ((ms == null) || ms.isPassedLifetimeThresh()) { + return; + } + + JobScheduler tm = context.getSystemService(JobScheduler.class); + + long nextScheduleTime = TimeUnit.HOURS.toMillis(nHours); + + JobInfo.Builder builder = new JobInfo.Builder(SMART_MAINT_JOB_ID, + SMART_STORAGE_MAINT_SERVICE); + + builder.setMinimumLatency(nextScheduleTime); + tm.schedule(builder.build()); + } +} diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index ec018de8f9a4..98764e02d803 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -79,6 +79,7 @@ import android.content.res.Configuration; import android.content.res.ObbInfo; import android.database.ContentObserver; import android.net.Uri; +import android.os.BatteryManager; import android.os.Binder; import android.os.Build; import android.os.DropBoxManager; @@ -159,6 +160,8 @@ import com.android.server.storage.StorageSessionController.ExternalStorageServic import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -340,7 +343,44 @@ class StorageManagerService extends IStorageManager.Stub @Nullable public static String sMediaStoreAuthorityProcessName; + // Run period in hour for smart idle maintenance + static final int SMART_IDLE_MAINT_PERIOD = 1; + private final AtomicFile mSettingsFile; + private final AtomicFile mHourlyWriteFile; + + private static final int MAX_HOURLY_WRITE_RECORDS = 72; + + /** + * Default config values for smart idle maintenance + * Actual values will be controlled by DeviceConfig + */ + // Decide whether smart idle maintenance is enabled or not + private static final boolean DEFAULT_SMART_IDLE_MAINT_ENABLED = false; + // Storage lifetime percentage threshold to decide to turn off the feature + private static final int DEFAULT_LIFETIME_PERCENT_THRESHOLD = 70; + // Minimum required number of dirty + free segments to trigger GC + private static final int DEFAULT_MIN_SEGMENTS_THRESHOLD = 512; + // Determine how much portion of current dirty segments will be GCed + private static final float DEFAULT_DIRTY_RECLAIM_RATE = 0.5F; + // Multiplier to amplify the target segment number for GC + private static final float DEFAULT_SEGMENT_RECLAIM_WEIGHT = 1.0F; + // Low battery level threshold to decide to turn off the feature + private static final float DEFAULT_LOW_BATTERY_LEVEL = 20F; + // Decide whether charging is required to turn on the feature + private static final boolean DEFAULT_CHARGING_REQUIRED = true; + + private volatile int mLifetimePercentThreshold; + private volatile int mMinSegmentsThreshold; + private volatile float mDirtyReclaimRate; + private volatile float mSegmentReclaimWeight; + private volatile float mLowBatteryLevel; + private volatile boolean mChargingRequired; + private volatile boolean mNeedGC; + + private volatile boolean mPassedLifetimeThresh; + // Tracking storage hourly write amounts + private volatile int[] mStorageHourlyWrites; /** * <em>Never</em> hold the lock while performing downcalls into vold, since @@ -896,6 +936,10 @@ class StorageManagerService extends IStorageManager.Stub } private void handleSystemReady() { + if (prepareSmartIdleMaint()) { + SmartStorageMaintIdler.scheduleSmartIdlePass(mContext, SMART_IDLE_MAINT_PERIOD); + } + // Start scheduling nominally-daily fstrim operations MountServiceIdler.scheduleIdlePass(mContext); @@ -1912,6 +1956,10 @@ class StorageManagerService extends IStorageManager.Stub mSettingsFile = new AtomicFile( new File(Environment.getDataSystemDirectory(), "storage.xml"), "storage-settings"); + mHourlyWriteFile = new AtomicFile( + new File(Environment.getDataSystemDirectory(), "storage-hourly-writes")); + + mStorageHourlyWrites = new int[MAX_HOURLY_WRITE_RECORDS]; synchronized (mLock) { readSettingsLocked(); @@ -2574,7 +2622,7 @@ class StorageManagerService extends IStorageManager.Stub // fstrim time is still updated. If file based checkpoints are used, we run // idle maintenance (GC + fstrim) regardless of checkpoint status. if (!needsCheckpoint() || !supportsBlockCheckpoint()) { - mVold.runIdleMaint(new IVoldTaskListener.Stub() { + mVold.runIdleMaint(mNeedGC, new IVoldTaskListener.Stub() { @Override public void onStatus(int status, PersistableBundle extras) { // Not currently used @@ -2625,6 +2673,176 @@ class StorageManagerService extends IStorageManager.Stub abortIdleMaint(null); } + private boolean prepareSmartIdleMaint() { + /** + * We can choose whether going with a new storage smart idle maintenance job + * or falling back to the traditional way using DeviceConfig + */ + boolean smartIdleMaintEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "smart_idle_maint_enabled", + DEFAULT_SMART_IDLE_MAINT_ENABLED); + if (smartIdleMaintEnabled) { + mLifetimePercentThreshold = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "lifetime_threshold", DEFAULT_LIFETIME_PERCENT_THRESHOLD); + mMinSegmentsThreshold = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "min_segments_threshold", DEFAULT_MIN_SEGMENTS_THRESHOLD); + mDirtyReclaimRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "dirty_reclaim_rate", DEFAULT_DIRTY_RECLAIM_RATE); + mSegmentReclaimWeight = DeviceConfig.getFloat( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "segment_reclaim_weight", DEFAULT_SEGMENT_RECLAIM_WEIGHT); + mLowBatteryLevel = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "low_battery_level", DEFAULT_LOW_BATTERY_LEVEL); + mChargingRequired = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "charging_required", DEFAULT_CHARGING_REQUIRED); + + // If we use the smart idle maintenance, we need to turn off GC in the traditional idle + // maintenance to avoid the conflict + mNeedGC = false; + + loadStorageHourlyWrites(); + try { + mVold.refreshLatestWrite(); + } catch (Exception e) { + Slog.wtf(TAG, e); + } + refreshLifetimeConstraint(); + } + return smartIdleMaintEnabled; + } + + // Return whether storage lifetime exceeds the threshold + public boolean isPassedLifetimeThresh() { + return mPassedLifetimeThresh; + } + + private void loadStorageHourlyWrites() { + FileInputStream fis = null; + + try { + fis = mHourlyWriteFile.openRead(); + ObjectInputStream ois = new ObjectInputStream(fis); + mStorageHourlyWrites = (int[])ois.readObject(); + } catch (FileNotFoundException e) { + // Missing data is okay, probably first boot + } catch (Exception e) { + Slog.wtf(TAG, "Failed reading write records", e); + } finally { + IoUtils.closeQuietly(fis); + } + } + + private int getAverageHourlyWrite() { + return Arrays.stream(mStorageHourlyWrites).sum() / MAX_HOURLY_WRITE_RECORDS; + } + + private void updateStorageHourlyWrites(int latestWrite) { + FileOutputStream fos = null; + + System.arraycopy(mStorageHourlyWrites,0, mStorageHourlyWrites, 1, + MAX_HOURLY_WRITE_RECORDS - 1); + mStorageHourlyWrites[0] = latestWrite; + try { + fos = mHourlyWriteFile.startWrite(); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(mStorageHourlyWrites); + mHourlyWriteFile.finishWrite(fos); + } catch (IOException e) { + if (fos != null) { + mHourlyWriteFile.failWrite(fos); + } + } + } + + private boolean checkChargeStatus() { + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatus = mContext.registerReceiver(null, ifilter); + + if (mChargingRequired) { + int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + if (status != BatteryManager.BATTERY_STATUS_CHARGING && + status != BatteryManager.BATTERY_STATUS_FULL) { + Slog.w(TAG, "Battery is not being charged"); + return false; + } + } + + int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + float chargePercent = level * 100f / (float)scale; + + if (chargePercent < mLowBatteryLevel) { + Slog.w(TAG, "Battery level is " + chargePercent + ", which is lower than threshold: " + + mLowBatteryLevel); + return false; + } + return true; + } + + private boolean refreshLifetimeConstraint() { + int storageLifeTime = 0; + + try { + storageLifeTime = mVold.getStorageLifeTime(); + } catch (Exception e) { + Slog.wtf(TAG, e); + return false; + } + + if (storageLifeTime == -1) { + Slog.w(TAG, "Failed to get storage lifetime"); + return false; + } else if (storageLifeTime > mLifetimePercentThreshold) { + Slog.w(TAG, "Ended smart idle maintenance, because of lifetime(" + storageLifeTime + + ")" + ", lifetime threshold(" + mLifetimePercentThreshold + ")"); + mPassedLifetimeThresh = true; + return false; + } + Slog.i(TAG, "Storage lifetime: " + storageLifeTime); + return true; + } + + void runSmartIdleMaint(Runnable callback) { + enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); + + try { + // Block based checkpoint process runs fstrim. So, if checkpoint is in progress + // (first boot after OTA), We skip the smart idle maintenance + if (!needsCheckpoint() || !supportsBlockCheckpoint()) { + if (!refreshLifetimeConstraint() || !checkChargeStatus()) { + return; + } + + int latestHourlyWrite = mVold.getWriteAmount(); + if (latestHourlyWrite == -1) { + Slog.w(TAG, "Failed to get storage hourly write"); + return; + } + + updateStorageHourlyWrites(latestHourlyWrite); + int avgHourlyWrite = getAverageHourlyWrite(); + + Slog.i(TAG, "Set smart idle maintenance: " + "latest hourly write: " + + latestHourlyWrite + ", average hourly write: " + avgHourlyWrite + + ", min segment threshold: " + mMinSegmentsThreshold + + ", dirty reclaim rate: " + mDirtyReclaimRate + + ", segment reclaim weight:" + mSegmentReclaimWeight); + mVold.setGCUrgentPace(avgHourlyWrite, mMinSegmentsThreshold, mDirtyReclaimRate, + mSegmentReclaimWeight); + } else { + Slog.i(TAG, "Skipping smart idle maintenance - block based checkpoint in progress"); + } + } catch (Exception e) { + Slog.wtf(TAG, e); + } finally { + if (callback != null) { + callback.run(); + } + } + } + @Override public void setDebugFlags(int flags, int mask) { enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 81627a05c9a4..c236a7f80000 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -19,6 +19,9 @@ package com.android.server; import static android.app.UiModeManager.DEFAULT_PRIORITY; import static android.app.UiModeManager.MODE_NIGHT_AUTO; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; import static android.app.UiModeManager.MODE_NIGHT_NO; import static android.app.UiModeManager.MODE_NIGHT_YES; import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE; @@ -40,6 +43,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.UiModeManager; +import android.app.UiModeManager.NightModeCustomType; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -115,6 +119,7 @@ final class UiModeManagerService extends SystemService { private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; private int mNightMode = UiModeManager.MODE_NIGHT_NO; + private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0); private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0); private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME; @@ -136,6 +141,7 @@ final class UiModeManagerService extends SystemService { private boolean mWatch; private boolean mVrHeadset; private boolean mComputedNightMode; + private boolean mLastBedtimeRequestedNightMode = false; private int mCarModeEnableFlags; private boolean mSetupWizardComplete; @@ -541,7 +547,9 @@ final class UiModeManagerService extends SystemService { mNightMode = Secure.getIntForUser(context.getContentResolver(), Secure.UI_NIGHT_MODE, res.getInteger( com.android.internal.R.integer.config_defaultNightMode), userId); - mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(), + mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(), + Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId); + mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(), Secure.UI_NIGHT_MODE_OVERRIDE_ON, 0, userId) != 0; mOverrideNightModeOff = Secure.getIntForUser(context.getContentResolver(), Secure.UI_NIGHT_MODE_OVERRIDE_OFF, 0, userId) != 0; @@ -702,6 +710,14 @@ final class UiModeManagerService extends SystemService { @Override public void setNightMode(int mode) { + // MODE_NIGHT_CUSTOM_TYPE_SCHEDULE is the default for MODE_NIGHT_CUSTOM. + int customModeType = mode == MODE_NIGHT_CUSTOM + ? MODE_NIGHT_CUSTOM_TYPE_SCHEDULE + : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; + setNightModeInternal(mode, customModeType); + } + + private void setNightModeInternal(int mode, int customModeType) { if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) != PackageManager.PERMISSION_GRANTED)) { @@ -722,12 +738,14 @@ final class UiModeManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - if (mNightMode != mode) { + if (mNightMode != mode || mNightModeCustomType != customModeType) { if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) { unregisterScreenOffEventLocked(); cancelCustomAlarm(); } - + mNightModeCustomType = mode == MODE_NIGHT_CUSTOM + ? customModeType + : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; mNightMode = mode; resetNightModeOverrideLocked(); persistNightMode(user); @@ -754,6 +772,30 @@ final class UiModeManagerService extends SystemService { } @Override + public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) { + if (getContext().checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "setNightModeCustomType requires MODIFY_DAY_NIGHT_MODE permission"); + } + setNightModeInternal(MODE_NIGHT_CUSTOM, nightModeCustomType); + } + + @Override + public int getNightModeCustomType() { + if (getContext().checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "getNightModeCustomType requires MODIFY_DAY_NIGHT_MODE permission"); + } + synchronized (mLock) { + return mNightModeCustomType; + } + } + + @Override public void setApplicationNightMode(@UiModeManager.NightMode int mode) { switch (mode) { case UiModeManager.MODE_NIGHT_NO: @@ -808,10 +850,19 @@ final class UiModeManagerService extends SystemService { } @Override + public boolean setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active) { + return setNightModeActivatedForModeInternal(modeNightCustomType, active); + } + + @Override public boolean setNightModeActivated(boolean active) { - if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission( + return setNightModeActivatedForModeInternal(mNightModeCustomType, active); + } + + private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) { + if (getContext().checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) - != PackageManager.PERMISSION_GRANTED)) { + != PackageManager.PERMISSION_GRANTED) { Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission"); return false; } @@ -824,6 +875,14 @@ final class UiModeManagerService extends SystemService { return false; } + // Store the last requested bedtime night mode state so that we don't need to notify + // anyone if the user decides to switch to the night mode to bedtime. + if (modeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) { + mLastBedtimeRequestedNightMode = active; + } + if (modeCustomType != mNightModeCustomType) { + return false; + } synchronized (mLock) { final long ident = Binder.clearCallingIdentity(); try { @@ -1422,6 +1481,8 @@ final class UiModeManagerService extends SystemService { Secure.putIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, mNightMode, user); Secure.putLongForUser(getContext().getContentResolver(), + Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user); + Secure.putLongForUser(getContext().getContentResolver(), Secure.DARK_THEME_CUSTOM_START_TIME, mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000, user); Secure.putLongForUser(getContext().getContentResolver(), @@ -1473,10 +1534,14 @@ final class UiModeManagerService extends SystemService { } if (mNightMode == MODE_NIGHT_CUSTOM) { - registerTimeChangeEvent(); - final boolean activate = computeCustomNightMode(); - updateComputedNightModeLocked(activate); - scheduleNextCustomTimeListener(); + if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) { + updateComputedNightModeLocked(mLastBedtimeRequestedNightMode); + } else { + registerTimeChangeEvent(); + final boolean activate = computeCustomNightMode(); + updateComputedNightModeLocked(activate); + scheduleNextCustomTimeListener(); + } } else { unregisterTimeChangeEvent(); } @@ -1494,6 +1559,7 @@ final class UiModeManagerService extends SystemService { "updateConfigurationLocked: mDockState=" + mDockState + "; mCarMode=" + mCarModeEnabled + "; mNightMode=" + mNightMode + + "; mNightModeCustomType=" + mNightModeCustomType + "; uiMode=" + uiMode); } @@ -1534,7 +1600,8 @@ final class UiModeManagerService extends SystemService { } private boolean shouldApplyAutomaticChangesImmediately() { - return mCar || !mPowerManager.isInteractive(); + return mCar || !mPowerManager.isInteractive() + || mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME; } private void scheduleNextCustomTimeListener() { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 7993936cd568..e040319b8aee 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3472,11 +3472,9 @@ public final class ActiveServices { } private int getAllowMode(Intent service, @Nullable String callingPackage) { - if (callingPackage == null || service.getComponent() == null) { - return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; - } - if (callingPackage.equals(service.getComponent().getPackageName())) { - return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; + if (callingPackage != null && service.getComponent() != null + && callingPackage.equals(service.getComponent().getPackageName())) { + return ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL; } else { return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; } @@ -6794,7 +6792,8 @@ public final class ActiveServices { r.mFgsNotificationShown, durationMs, r.mStartForegroundCount, - ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName)); + ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName), + r.mFgsHasNotificationPermission); } boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 704d73935782..f67e732b47dd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -233,11 +233,11 @@ import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; import android.content.pm.ProviderInfoList; import android.content.pm.ResolveInfo; -import android.content.pm.SELinuxUtil; +import com.android.server.pm.pkg.SELinuxUtil; import android.content.pm.ServiceInfo; import android.content.pm.TestUtilityService; import android.content.pm.UserInfo; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -393,6 +393,7 @@ import com.android.server.graphics.fonts.FontManagerInternal; import com.android.server.job.JobSchedulerInternal; import com.android.server.os.NativeTombstoneManager; import com.android.server.pm.Installer; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.uri.GrantUri; @@ -10359,10 +10360,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (thread != null) { pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **"); pw.flush(); - if (pid == MY_PID) { - PropertyInvalidatedCache.dumpCacheInfo(fd, args); - continue; - } try { TransferPipe tp = new TransferPipe(); try { @@ -13312,6 +13309,14 @@ public class ActivityManagerService extends IActivityManager.Stub } switch (action) { + case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: + UserManagerInternal umInternal = LocalServices.getService( + UserManagerInternal.class); + UserInfo userInfo = umInternal.getUserInfo(userId); + if (userInfo != null && userInfo.isCloneProfile()) { + userId = umInternal.getProfileParentId(userId); + } + break; case Intent.ACTION_UID_REMOVED: case Intent.ACTION_PACKAGE_REMOVED: case Intent.ACTION_PACKAGE_CHANGED: @@ -13378,7 +13383,12 @@ public class ActivityManagerService extends IActivityManager.Stub !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false); final boolean fullUninstall = removed && !replacing; if (removed) { - if (!killProcess) { + if (killProcess) { + forceStopPackageLocked(ssp, UserHandle.getAppId( + intent.getIntExtra(Intent.EXTRA_UID, -1)), + false, true, true, false, fullUninstall, userId, + removed ? "pkg removed" : "pkg changed"); + } else { // Kill any app zygotes always, since they can't fork new // processes with references to the old code forceStopAppZygoteLocked(ssp, UserHandle.getAppId( diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index eb8a4e9508da..e36ea20dea48 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -781,6 +781,54 @@ public final class BroadcastQueue { skip = true; } } + // Check that the receiver does *not* have any excluded permissions + if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) { + for (int i = 0; i < r.excludedPermissions.length; i++) { + String excludedPermission = r.excludedPermissions[i]; + final int perm = mService.checkComponentPermission(excludedPermission, + filter.receiverList.pid, filter.receiverList.uid, -1, true); + + int appOp = AppOpsManager.permissionToOpCode(excludedPermission); + if (appOp != AppOpsManager.OP_NONE) { + // When there is an app op associated with the permission, + // skip when both the permission and the app op are + // granted. + if ((perm == PackageManager.PERMISSION_GRANTED) && ( + mService.getAppOpsManager().checkOpNoThrow(appOp, + filter.receiverList.uid, + filter.packageName) + == AppOpsManager.MODE_ALLOWED)) { + Slog.w(TAG, "Appop Denial: receiving " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " excludes appop " + AppOpsManager.permissionToOp( + excludedPermission) + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + break; + } + } else { + // When there is no app op associated with the permission, + // skip when permission is granted. + if (perm == PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: receiving " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " excludes " + excludedPermission + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + break; + } + } + } + } + // If the broadcast also requires an app op check that as well. if (!skip && r.appOp != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(r.appOp, diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 9b731d58153c..9b32e61763f3 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -174,6 +174,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean mFgsNotificationWasDeferred; // FGS notification was shown before the FGS finishes, or it wasn't deferred in the first place. boolean mFgsNotificationShown; + // Whether FGS package has permissions to show notifications. + boolean mFgsHasNotificationPermission; // allow the service becomes foreground service? Service started from background may not be // allowed to become a foreground service. @@ -590,6 +592,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN userId = UserHandle.getUserId(appInfo.uid); createdFromFg = callerIsFg; updateKeepWarmLocked(); + // initialize notification permission state; this'll be updated whenever there's an attempt + // to post or update a notification, but that doesn't cover the time before the first + // notification + updateFgsHasNotificationPermission(); } public ServiceState getTracker() { @@ -947,6 +953,25 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN return lastStartId; } + private void updateFgsHasNotificationPermission() { + // Do asynchronous communication with notification manager to avoid deadlocks. + final String localPackageName = packageName; + final int appUid = appInfo.uid; + + ams.mHandler.post(new Runnable() { + public void run() { + NotificationManagerInternal nm = LocalServices.getService( + NotificationManagerInternal.class); + if (nm == null) { + return; + } + // Record whether the package has permission to notify the user + mFgsHasNotificationPermission = nm.areNotificationsEnabledForPackage( + localPackageName, appUid); + } + }); + } + public void postNotification() { if (isForeground && foregroundNoti != null && app != null) { final int appUid = appInfo.uid; @@ -968,6 +993,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (nm == null) { return; } + // Record whether the package has permission to notify the user + mFgsHasNotificationPermission = nm.areNotificationsEnabledForPackage( + localPackageName, appUid); Notification localForegroundNoti = _foregroundNoti; try { if (localForegroundNoti.getSmallIcon() == null) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index a7864b9607c8..252584c76696 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -26,10 +26,10 @@ import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; import static android.app.ActivityManager.USER_OP_SUCCESS; -import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; +import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL; import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED; import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; @@ -2153,11 +2153,10 @@ class UserController implements Handler.Callback { callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) { // If the caller does not have either permission, they are always doomed. allow = false; - } else if (allowMode == ALLOW_NON_FULL) { + } else if (allowMode == ALLOW_NON_FULL || allowMode == ALLOW_PROFILES_OR_NON_FULL) { // We are blanket allowing non-full access, you lucky caller! allow = true; - } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE - || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) { // We may or may not allow this depending on whether the two users are // in the same profile. allow = isSameProfileGroup; @@ -2184,12 +2183,13 @@ class UserController implements Handler.Callback { builder.append("; this requires "); builder.append(INTERACT_ACROSS_USERS_FULL); if (allowMode != ALLOW_FULL_ONLY) { - if (allowMode == ALLOW_NON_FULL || isSameProfileGroup) { + if (allowMode == ALLOW_NON_FULL + || allowMode == ALLOW_PROFILES_OR_NON_FULL + || (allowMode == ALLOW_NON_FULL_IN_PROFILE && isSameProfileGroup)) { builder.append(" or "); builder.append(INTERACT_ACROSS_USERS); } - if (isSameProfileGroup - && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + if (isSameProfileGroup && allowMode == ALLOW_PROFILES_OR_NON_FULL) { builder.append(" or "); builder.append(INTERACT_ACROSS_PROFILES); } @@ -2216,19 +2216,14 @@ class UserController implements Handler.Callback { private boolean canInteractWithAcrossProfilesPermission( int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid, String callingPackage) { - if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + if (allowMode != ALLOW_PROFILES_OR_NON_FULL) { return false; } if (!isSameProfileGroup) { return false; } - return PermissionChecker.PERMISSION_GRANTED - == PermissionChecker.checkPermissionForPreflight( - mInjector.getContext(), - INTERACT_ACROSS_PROFILES, - callingPid, - callingUid, - callingPackage); + return mInjector.checkPermissionForPreflight(INTERACT_ACROSS_PROFILES, callingPid, + callingUid, callingPackage); } int unsafeConvertIncomingUser(@UserIdInt int userId) { @@ -2908,7 +2903,7 @@ class UserController implements Handler.Callback { */ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG); mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG, - target.id), USER_JOURNEY_TIMEOUT_MS); + target.id, /* arg2= */ 0), USER_JOURNEY_TIMEOUT_MS); } FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, newSessionId, @@ -3157,6 +3152,12 @@ class UserController implements Handler.Callback { return mService.checkComponentPermission(permission, pid, uid, owningUid, exported); } + boolean checkPermissionForPreflight(String permission, int pid, int uid, String pkg) { + return PermissionChecker.PERMISSION_GRANTED + == PermissionChecker.checkPermissionForPreflight( + getContext(), permission, pid, uid, pkg); + } + protected void startHomeActivity(@UserIdInt int userId, String reason) { mService.mAtmInternal.startHomeActivity(userId, reason); } @@ -3234,7 +3235,7 @@ class UserController implements Handler.Callback { mService.mAtmInternal.clearLockedTasks(reason); } - protected boolean isCallerRecents(int callingUid) { + boolean isCallerRecents(int callingUid) { return mService.mAtmInternal.isCallerRecents(callingUid); } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 0980f4077489..53f651bfdf08 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -39,6 +39,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; @@ -81,6 +82,7 @@ import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; +import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration; import java.io.FileDescriptor; import java.util.List; @@ -113,6 +115,7 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Context mContext; private final Object mLock = new Object(); private final Object mDeviceConfigLock = new Object(); + private final Object mOverrideConfigLock = new Object(); private final Handler mHandler; private final PackageManager mPackageManager; private final IPlatformCompat mPlatformCompat; @@ -122,6 +125,8 @@ public final class GameManagerService extends IGameManagerService.Stub { private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>(); @GuardedBy("mDeviceConfigLock") private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>(); + @GuardedBy("mOverrideConfigLock") + private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>(); public GameManagerService(Context context) { this(context, createServiceThread().getLooper()); @@ -281,13 +286,51 @@ public final class GameManagerService extends IGameManagerService.Stub { return 0; } + public enum FrameRate { + FPS_DEFAULT(0), + FPS_30(30), + FPS_45(45), + FPS_60(60), + FPS_90(90), + FPS_120(120), + FPS_INVALID(-1); + + public final int fps; + + FrameRate(int fps) { + this.fps = fps; + } + } + + // Turn the raw string to the corresponding fps int. + // Return 0 when disabling, -1 for invalid fps. + static int getFpsInt(String raw) { + switch (raw) { + case "30": + return FrameRate.FPS_30.fps; + case "45": + return FrameRate.FPS_45.fps; + case "60": + return FrameRate.FPS_60.fps; + case "90": + return FrameRate.FPS_90.fps; + case "120": + return FrameRate.FPS_120.fps; + case "disable": + case "": + return FrameRate.FPS_DEFAULT.fps; + } + return FrameRate.FPS_INVALID.fps; + } + /** * Called by games to communicate the current state to the platform. * @param packageName The client package name. * @param gameState An object set to the current state. * @param userId The user associated with this state. */ - public void setGameState(String packageName, @NonNull GameState gameState, int userId) { + public void setGameState(String packageName, @NonNull GameState gameState, + @UserIdInt int userId) { if (!isPackageGame(packageName, userId)) { // Restrict to games only. return; @@ -399,11 +442,14 @@ public final class GameManagerService extends IGameManagerService.Stub { public static final String TAG = "GameManagerService_GameModeConfiguration"; public static final String MODE_KEY = "mode"; public static final String SCALING_KEY = "downscaleFactor"; + public static final String FPS_KEY = "fps"; public static final String DEFAULT_SCALING = "1.0"; + public static final String DEFAULT_FPS = ""; public static final String ANGLE_KEY = "useAngle"; private final @GameMode int mGameMode; - private final String mScaling; + private String mScaling; + private String mFps; private final boolean mUseAngle; GameModeConfiguration(KeyValueListParser parser) { @@ -414,6 +460,8 @@ public final class GameManagerService extends IGameManagerService.Stub { // using ANGLE). mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode) ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING); + + mFps = parser.getString(FPS_KEY, DEFAULT_FPS); // We only want to use ANGLE if: // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND // - The app has not opted in to performing the work itself AND @@ -430,14 +478,27 @@ public final class GameManagerService extends IGameManagerService.Stub { return mScaling; } + public int getFps() { + return GameManagerService.getFpsInt(mFps); + } + public boolean getUseAngle() { return mUseAngle; } + public void setScaling(String scaling) { + mScaling = scaling; + } + + public void setFpsStr(String fpsStr) { + mFps = fpsStr; + } + public boolean isValid() { - return mGameMode == GameManager.GAME_MODE_STANDARD + return (mGameMode == GameManager.GAME_MODE_STANDARD || mGameMode == GameManager.GAME_MODE_PERFORMANCE - || mGameMode == GameManager.GAME_MODE_BATTERY; + || mGameMode == GameManager.GAME_MODE_BATTERY) + && !willGamePerformOptimizations(mGameMode); } /** @@ -445,7 +506,7 @@ public final class GameManagerService extends IGameManagerService.Stub { */ public String toString() { return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:" - + mUseAngle + "]"; + + mUseAngle + ",Fps:" + mFps + "]"; } /** @@ -641,16 +702,22 @@ public final class GameManagerService extends IGameManagerService.Stub { @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException { checkPermission(Manifest.permission.MANAGE_GAME_MODE); + GamePackageConfiguration config = null; + synchronized (mOverrideConfigLock) { + config = mOverrideConfigs.get(packageName); + } + if (config == null) { synchronized (mDeviceConfigLock) { - final GamePackageConfiguration config = mConfigs.get(packageName); - if (config == null) { - return new int[]{GameManager.GAME_MODE_UNSUPPORTED}; + config = mConfigs.get(packageName); } - return config.getAvailableGameModes(); } + if (config == null) { + return new int[]{GameManager.GAME_MODE_UNSUPPORTED}; + } + return config.getAvailableGameModes(); } - private @GameMode int getGameModeFromSettings(String packageName, int userId) { + private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) { synchronized (mLock) { if (!mSettings.containsKey(userId)) { Slog.w(TAG, "User ID '" + userId + "' does not have a Game Mode" @@ -743,7 +810,7 @@ public final class GameManagerService extends IGameManagerService.Stub { mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY); } } - updateInterventions(packageName, gameMode); + updateInterventions(packageName, gameMode, userId); } /** @@ -876,41 +943,26 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - private void updateCompatModeDownscale(String packageName, @GameMode int gameMode) { - synchronized (mDeviceConfigLock) { - if (gameMode == GameManager.GAME_MODE_STANDARD - || gameMode == GameManager.GAME_MODE_UNSUPPORTED) { - disableCompatScale(packageName); - return; - } - final GamePackageConfiguration packageConfig = mConfigs.get(packageName); - if (packageConfig == null) { - disableCompatScale(packageName); - Slog.v(TAG, "Package configuration not found for " + packageName); - return; - } - if (DEBUG) { - Slog.v(TAG, dumpDeviceConfigs()); - } - if (packageConfig.willGamePerformOptimizations(gameMode)) { - disableCompatScale(packageName); - return; - } - final GamePackageConfiguration.GameModeConfiguration modeConfig = - packageConfig.getGameModeConfiguration(gameMode); - if (modeConfig == null) { - Slog.i(TAG, "Game mode " + gameMode + " not found for " + packageName); - return; - } - long scaleId = modeConfig.getCompatChangeId(); - if (scaleId == 0) { - Slog.i(TAG, "Invalid downscaling change id " + scaleId + " for " - + packageName); - return; - } + private void updateCompatModeDownscale(GamePackageConfiguration packageConfig, + String packageName, @GameMode int gameMode) { - enableCompatScale(packageName, scaleId); + if (DEBUG) { + Slog.v(TAG, dumpDeviceConfigs()); } + final GamePackageConfiguration.GameModeConfiguration modeConfig = + packageConfig.getGameModeConfiguration(gameMode); + if (modeConfig == null) { + Slog.i(TAG, "Game mode " + gameMode + " not found for " + packageName); + return; + } + long scaleId = modeConfig.getCompatChangeId(); + if (scaleId == 0) { + Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for " + + packageName); + return; + } + + enableCompatScale(packageName, scaleId); } private int modeToBitmask(@GameMode int gameMode) { @@ -927,16 +979,233 @@ public final class GameManagerService extends IGameManagerService.Stub { // ship. } - private void updateInterventions(String packageName, @GameMode int gameMode) { - updateCompatModeDownscale(packageName, gameMode); + + private void updateFps(GamePackageConfiguration packageConfig, String packageName, + @GameMode int gameMode, @UserIdInt int userId) { + final GamePackageConfiguration.GameModeConfiguration modeConfig = + packageConfig.getGameModeConfiguration(gameMode); + if (modeConfig == null) { + Slog.d(TAG, "Game mode " + gameMode + " not found for " + packageName); + return; + } + try { + final float fps = modeConfig.getFps(); + final int uid = mPackageManager.getPackageUidAsUser(packageName, userId); + nativeSetOverrideFrameRate(uid, fps); + } catch (PackageManager.NameNotFoundException e) { + return; + } + } + + + private void updateInterventions(String packageName, + @GameMode int gameMode, @UserIdInt int userId) { + if (gameMode == GameManager.GAME_MODE_STANDARD + || gameMode == GameManager.GAME_MODE_UNSUPPORTED) { + disableCompatScale(packageName); + return; + } + GamePackageConfiguration packageConfig = null; + + synchronized (mOverrideConfigLock) { + packageConfig = mOverrideConfigs.get(packageName); + } + + if (packageConfig == null) { + synchronized (mDeviceConfigLock) { + packageConfig = mConfigs.get(packageName); + } + } + + if (packageConfig == null) { + disableCompatScale(packageName); + Slog.v(TAG, "Package configuration not found for " + packageName); + return; + } + if (packageConfig.willGamePerformOptimizations(gameMode)) { + return; + } + updateCompatModeDownscale(packageConfig, packageName, gameMode); + updateFps(packageConfig, packageName, gameMode, userId); updateUseAngle(packageName, gameMode); } /** + * Set the override Game Mode Configuration. + * Update the config if exists, create one if not. + */ + @VisibleForTesting + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public void setGameModeConfigOverride(String packageName, @UserIdInt int userId, + @GameMode int gameMode, String fpsStr, String scaling) throws SecurityException { + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { + return; + } + } + // Adding override game mode configuration of the given package name + synchronized (mOverrideConfigLock) { + // look for the existing override GamePackageConfiguration + GamePackageConfiguration overrideConfig = mOverrideConfigs.get(packageName); + if (overrideConfig == null) { + overrideConfig = new GamePackageConfiguration(packageName, userId); + mOverrideConfigs.put(packageName, overrideConfig); + } + + // modify GameModeConfiguration intervention settings + GamePackageConfiguration.GameModeConfiguration overrideModeConfig = + overrideConfig.getGameModeConfiguration(gameMode); + + if (fpsStr != null) { + overrideModeConfig.setFpsStr(fpsStr); + } else { + overrideModeConfig.setFpsStr( + GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS); + } + if (scaling != null) { + overrideModeConfig.setScaling(scaling); + } else { + overrideModeConfig.setScaling( + GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING); + } + Slog.i(TAG, "Package Name: " + packageName + + " FPS: " + String.valueOf(overrideModeConfig.getFps()) + + " Scaling: " + overrideModeConfig.getScaling()); + } + setGameMode(packageName, gameMode, userId); + } + + /** + * Reset the overridden gameModeConfiguration of the given mode. + * Remove the override config if game mode is not specified. + */ + @VisibleForTesting + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId, + @GameMode int gameModeToReset) throws SecurityException { + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { + return; + } + } + + // resets GamePackageConfiguration of a given packageName. + // If a gameMode is specified, only reset the GameModeConfiguration of the gameMode. + if (gameModeToReset != -1) { + GamePackageConfiguration overrideConfig = null; + synchronized (mOverrideConfigLock) { + overrideConfig = mOverrideConfigs.get(packageName); + } + + GamePackageConfiguration config = null; + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } + + int[] modes = overrideConfig.getAvailableGameModes(); + + // First check if the mode to reset exists + boolean isGameModeExist = false; + for (int mode : modes) { + if (gameModeToReset == mode) { + isGameModeExist = true; + } + } + if (!isGameModeExist) { + return; + } + + // If the game mode to reset is the only mode other than standard mode, + // The override config is removed. + if (modes.length <= 2) { + synchronized (mOverrideConfigLock) { + mOverrideConfigs.remove(packageName); + } + } else { + // otherwise we reset the mode by copying the original config. + overrideConfig.addModeConfig(config.getGameModeConfiguration(gameModeToReset)); + } + } else { + synchronized (mOverrideConfigLock) { + // remove override config if there is one + mOverrideConfigs.remove(packageName); + } + } + + // Make sure after resetting the game mode is still supported. + // If not, set the game mode to standard + int gameMode = getGameMode(packageName, userId); + int newGameMode = gameMode; + + GamePackageConfiguration config = null; + synchronized (mOverrideConfigLock) { + config = mOverrideConfigs.get(packageName); + } + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } + if (config != null) { + int modesBitfield = config.getAvailableGameModesBitfield(); + // Remove UNSUPPORTED to simplify the logic here, since we really just + // want to check if we support selectable game modes + modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED); + if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) { + if (bitFieldContainsModeBitmask(modesBitfield, + GameManager.GAME_MODE_STANDARD)) { + // If the current set mode isn't supported, + // but we support STANDARD, then set the mode to STANDARD. + newGameMode = GameManager.GAME_MODE_STANDARD; + } else { + // If we don't support any game modes, then set to UNSUPPORTED + newGameMode = GameManager.GAME_MODE_UNSUPPORTED; + } + } + } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) { + // If we have no config for the package, but the configured mode is not + // UNSUPPORTED, then set to UNSUPPORTED + newGameMode = GameManager.GAME_MODE_UNSUPPORTED; + } + if (gameMode != newGameMode) { + setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId); + return; + } + setGameMode(packageName, gameMode, userId); + } + + /** + * Returns the string listing all the interventions currently set to a game. + */ + public String getInterventionList(String packageName) { + GamePackageConfiguration packageConfig = null; + synchronized (mOverrideConfigLock) { + packageConfig = mOverrideConfigs.get(packageName); + } + + if (packageConfig == null) { + synchronized (mDeviceConfigLock) { + packageConfig = mConfigs.get(packageName); + } + } + + StringBuilder listStrSb = new StringBuilder(); + if (packageConfig == null) { + listStrSb.append("\n No intervention found for package ") + .append(packageName); + return listStrSb.toString(); + } + listStrSb.append("\nPackage name: ") + .append(packageName) + .append(packageConfig.toString()); + return listStrSb.toString(); + } + + /** * @hide */ @VisibleForTesting - void updateConfigsForUser(int userId, String ...packageNames) { + void updateConfigsForUser(@UserIdInt int userId, String ...packageNames) { try { synchronized (mDeviceConfigLock) { for (final String packageName : packageNames) { @@ -954,43 +1223,47 @@ public final class GameManagerService extends IGameManagerService.Stub { } } } + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { + return; + } + } for (final String packageName : packageNames) { - if (mSettings.containsKey(userId)) { - int gameMode = getGameMode(packageName, userId); - int newGameMode = gameMode; - // Make sure the user settings and package configs don't conflict. I.e. the - // user setting is set to a mode that no longer available due to config/manifest - // changes. Most of the time we won't have to change anything. - GamePackageConfiguration config; - synchronized (mDeviceConfigLock) { - config = mConfigs.get(packageName); - } - if (config != null) { - int modesBitfield = config.getAvailableGameModesBitfield(); - // Remove UNSUPPORTED to simplify the logic here, since we really just - // want to check if we support selectable game modes - modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED); - if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) { - if (bitFieldContainsModeBitmask(modesBitfield, - GameManager.GAME_MODE_STANDARD)) { - // If the current set mode isn't supported, but we support STANDARD, - // then set the mode to STANDARD. - newGameMode = GameManager.GAME_MODE_STANDARD; - } else { - // If we don't support any game modes, then set to UNSUPPORTED - newGameMode = GameManager.GAME_MODE_UNSUPPORTED; - } + int gameMode = getGameMode(packageName, userId); + int newGameMode = gameMode; + // Make sure the user settings and package configs don't conflict. + // I.e. the user setting is set to a mode that no longer available due to + // config/manifest changes. + // Most of the time we won't have to change anything. + GamePackageConfiguration config = null; + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } + if (config != null) { + int modesBitfield = config.getAvailableGameModesBitfield(); + // Remove UNSUPPORTED to simplify the logic here, since we really just + // want to check if we support selectable game modes + modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED); + if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) { + if (bitFieldContainsModeBitmask(modesBitfield, + GameManager.GAME_MODE_STANDARD)) { + // If the current set mode isn't supported, + // but we support STANDARD, then set the mode to STANDARD. + newGameMode = GameManager.GAME_MODE_STANDARD; + } else { + // If we don't support any game modes, then set to UNSUPPORTED + newGameMode = GameManager.GAME_MODE_UNSUPPORTED; } - } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) { - // If we have no config for the package, but the configured mode is not - // UNSUPPORTED, then set to UNSUPPORTED - newGameMode = GameManager.GAME_MODE_UNSUPPORTED; - } - if (newGameMode != gameMode) { - setGameMode(packageName, newGameMode, userId); } - updateInterventions(packageName, gameMode); + } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) { + // If we have no config for the package, but the configured mode is not + // UNSUPPORTED, then set to UNSUPPORTED + newGameMode = GameManager.GAME_MODE_UNSUPPORTED; } + if (newGameMode != gameMode) { + setGameMode(packageName, newGameMode, userId); + } + updateInterventions(packageName, gameMode, userId); } } catch (Exception e) { Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e); @@ -1011,7 +1284,16 @@ public final class GameManagerService extends IGameManagerService.Stub { */ @VisibleForTesting public GamePackageConfiguration getConfig(String packageName) { - return mConfigs.get(packageName); + GamePackageConfiguration packageConfig = null; + synchronized (mOverrideConfigLock) { + packageConfig = mOverrideConfigs.get(packageName); + } + if (packageConfig == null) { + synchronized (mDeviceConfigLock) { + packageConfig = mConfigs.get(packageName); + } + } + return packageConfig; } private void registerPackageReceiver() { @@ -1047,6 +1329,9 @@ public final class GameManagerService extends IGameManagerService.Stub { break; case ACTION_PACKAGE_REMOVED: disableCompatScale(packageName); + synchronized (mOverrideConfigLock) { + mOverrideConfigs.remove(packageName); + } synchronized (mDeviceConfigLock) { mConfigs.remove(packageName); } @@ -1083,4 +1368,9 @@ public final class GameManagerService extends IGameManagerService.Stub { handlerThread.start(); return handlerThread; } + + /** + * load dynamic library for frame rate overriding JNI calls + */ + private static native void nativeSetOverrideFrameRate(int uid, float frameRate); } diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java index f07d207e7d06..470c320dbd7d 100644 --- a/services/core/java/com/android/server/app/GameManagerShellCommand.java +++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java @@ -34,7 +34,6 @@ import static com.android.server.wm.CompatModePackages.DOWNSCALE_90; import android.app.ActivityManager; import android.app.GameManager; import android.app.IGameManagerService; -import android.compat.Compatibility; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; @@ -42,12 +41,8 @@ import android.os.ServiceManager.ServiceNotFoundException; import android.os.ShellCommand; import android.util.ArraySet; -import com.android.internal.compat.CompatibilityChangeConfig; -import com.android.server.compat.PlatformCompat; - import java.io.PrintWriter; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.Locale; /** * ShellCommands for GameManagerService. @@ -83,42 +78,11 @@ public class GameManagerShellCommand extends ShellCommand { final PrintWriter pw = getOutPrintWriter(); try { switch (cmd) { - case "downscale": { - final String ratio = getNextArgRequired(); - final String packageName = getNextArgRequired(); - - final long changeId = GameManagerService.getCompatChangeId(ratio); - if (changeId == 0 && !ratio.equals("disable")) { - pw.println("Invalid scaling ratio '" + ratio + "'"); - break; - } - - Set<Long> enabled = new ArraySet<>(); - Set<Long> disabled; - if (changeId == 0) { - disabled = DOWNSCALE_CHANGE_IDS; - } else { - enabled.add(DOWNSCALED); - enabled.add(changeId); - disabled = DOWNSCALE_CHANGE_IDS.stream() - .filter(it -> it != DOWNSCALED && it != changeId) - .collect(Collectors.toSet()); - } - - final PlatformCompat platformCompat = (PlatformCompat) - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); - final CompatibilityChangeConfig overrides = - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(enabled, disabled)); - - platformCompat.setOverrides(overrides, packageName); - if (changeId == 0) { - pw.println("Disable downscaling for " + packageName + "."); - } else { - pw.println("Enable downscaling ratio for " + packageName + " to " + ratio); - } - - return 0; + case "set": { + return runSetGameMode(pw); + } + case "reset": { + return runResetGameMode(pw); } case "mode": { /** The "mode" command allows setting a package's current game mode outside of @@ -132,6 +96,9 @@ public class GameManagerShellCommand extends ShellCommand { */ return runGameMode(pw); } + case "list": { + return runGameList(pw); + } default: return handleDefaultCommands(cmd); } @@ -141,6 +108,22 @@ public class GameManagerShellCommand extends ShellCommand { return -1; } + private int runGameList(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + final String packageName = getNextArgRequired(); + + final GameManagerService gameManagerService = (GameManagerService) + ServiceManager.getService(Context.GAME_SERVICE); + + final String listStr = gameManagerService.getInterventionList(packageName); + + if (listStr == null) { + pw.println("No interventions found for " + packageName); + } else { + pw.println(packageName + " interventions: " + listStr); + } + return 0; + } + private int runGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { final String option = getNextOption(); String userIdStr = null; @@ -200,6 +183,172 @@ public class GameManagerShellCommand extends ShellCommand { return 0; } + private int runSetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + String option = getNextArgRequired(); + if (!option.equals("--mode")) { + pw.println("Invalid option '" + option + "'"); + return -1; + } + + final String gameMode = getNextArgRequired(); + + /** + * handling optional input + * "--user", "--downscale" and "--fps" can come in any order + */ + String userIdStr = null; + String fpsStr = null; + String downscaleRatio = null; + while ((option = getNextOption()) != null) { + switch (option) { + case "--user": + if (userIdStr == null) { + userIdStr = getNextArgRequired(); + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + case "--downscale": + if (downscaleRatio == null) { + downscaleRatio = getNextArgRequired(); + if (downscaleRatio != null + && GameManagerService.getCompatChangeId(downscaleRatio) == 0 + && !downscaleRatio.equals("disable")) { + pw.println("Invalid scaling ratio '" + downscaleRatio + "'"); + return -1; + } + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + case "--fps": + if (fpsStr == null) { + fpsStr = getNextArgRequired(); + if (fpsStr != null && GameManagerService.getFpsInt(fpsStr) == -1) { + pw.println("Invalid frame rate '" + fpsStr + "'"); + return -1; + } + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + default: + pw.println("Invalid option '" + option + "'"); + return -1; + } + } + + final String packageName = getNextArgRequired(); + + int userId = userIdStr != null ? Integer.parseInt(userIdStr) + : ActivityManager.getCurrentUser(); + + final GameManagerService gameManagerService = (GameManagerService) + ServiceManager.getService(Context.GAME_SERVICE); + + boolean batteryModeSupported = false; + boolean perfModeSupported = false; + int [] modes = gameManagerService.getAvailableGameModes(packageName); + + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_PERFORMANCE) { + perfModeSupported = true; + } else if (mode == GameManager.GAME_MODE_BATTERY) { + batteryModeSupported = true; + } + } + + switch (gameMode.toLowerCase(Locale.getDefault())) { + case "2": + case "performance": + if (perfModeSupported) { + gameManagerService.setGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio); + } else { + pw.println("Game mode: " + gameMode + " not supported by " + + packageName); + return -1; + } + break; + case "3": + case "battery": + if (batteryModeSupported) { + gameManagerService.setGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio); + } else { + pw.println("Game mode: " + gameMode + " not supported by " + + packageName); + return -1; + } + break; + default: + pw.println("Invalid game mode: " + gameMode); + return -1; + } + return 0; + } + + private int runResetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + String option = null; + String gameMode = null; + String userIdStr = null; + while ((option = getNextOption()) != null) { + switch (option) { + case "--user": + if (userIdStr == null) { + userIdStr = getNextArgRequired(); + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + case "--mode": + if (gameMode == null) { + gameMode = getNextArgRequired(); + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + default: + pw.println("Invalid option '" + option + "'"); + return -1; + } + } + + final String packageName = getNextArgRequired(); + + final GameManagerService gameManagerService = (GameManagerService) + ServiceManager.getService(Context.GAME_SERVICE); + + int userId = userIdStr != null ? Integer.parseInt(userIdStr) + : ActivityManager.getCurrentUser(); + + if (gameMode == null) { + gameManagerService.resetGameModeConfigOverride(packageName, userId, -1); + return 0; + } + + switch (gameMode.toLowerCase(Locale.getDefault())) { + case "2": + case "performance": + gameManagerService.resetGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_PERFORMANCE); + break; + case "3": + case "battery": + gameManagerService.resetGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_BATTERY); + break; + default: + pw.println("Invalid game mode: " + gameMode); + return -1; + } + return 0; + } @Override public void onHelp() { @@ -207,10 +356,25 @@ public class GameManagerShellCommand extends ShellCommand { pw.println("Game manager (game) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65|0.7|0.75|0.8|0.85|0.9|disable] <PACKAGE_NAME>"); - pw.println(" Force app to run at the specified scaling ratio."); + pw.println(" downscale"); + pw.println(" Deprecated. Please use `set` command."); pw.println(" mode [--user <USER_ID>] [1|2|3|standard|performance|battery] <PACKAGE_NAME>"); - pw.println(" Force app to run in the specified game mode, if supported."); - pw.println(" --user <USER_ID>: apply for the given user, the current user is used when unspecified."); + pw.println(" Set app to run in the specified game mode, if supported."); + pw.println(" --user <USER_ID>: apply for the given user,"); + pw.println(" the current user is used when unspecified."); + pw.println(" set --mode [2|3|performance|battery] [intervention configs] <PACKAGE_NAME>"); + pw.println(" Set app to run at given game mode with configs, if supported."); + pw.println(" Intervention configs consists of:"); + pw.println(" --downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65"); + pw.println(" |0.7|0.75|0.8|0.85|0.9|disable]"); + pw.println(" Set app to run at the specified scaling ratio."); + pw.println(" --fps [30|45|60|90|120|disable]"); + pw.println(" Set app to run at the specified fps, if supported."); + pw.println(" reset [--mode [2|3|performance|battery] --user <USER_ID>] <PACKAGE_NAME>"); + pw.println(" Resets the game mode of the app to device configuration."); + pw.println(" --mode [2|3|performance|battery]: apply for the given mode,"); + pw.println(" resets all modes when unspecified."); + pw.println(" --user <USER_ID>: apply for the given user,"); + pw.println(" the current user is used when unspecified."); } } diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java index d5ac03ab7c0d..48e66b6c6aeb 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java @@ -27,6 +27,8 @@ import android.service.games.IGameSessionService; import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; final class GameServiceProviderInstanceFactoryImpl implements GameServiceProviderInstanceFactory { private final Context mContext; @@ -44,6 +46,7 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide BackgroundThread.getExecutor(), new GameClassifierImpl(mContext.getPackageManager()), ActivityTaskManager.getService(), + LocalServices.getService(WindowManagerInternal.class), new GameServiceConnector(mContext, gameServiceProviderConfiguration), new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration)); } diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 3f3f257aedc5..31eb8c1c9429 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -17,22 +17,31 @@ package com.android.server.app; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityTaskManager; import android.app.TaskStackListener; import android.content.ComponentName; -import android.os.IBinder; +import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.service.games.CreateGameSessionRequest; +import android.service.games.CreateGameSessionResult; +import android.service.games.GameSessionViewHostConfiguration; +import android.service.games.GameStartedEvent; import android.service.games.IGameService; +import android.service.games.IGameServiceController; import android.service.games.IGameSession; import android.service.games.IGameSessionService; import android.util.Slog; +import android.view.SurfaceControlViewHost.SurfacePackage; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; +import com.android.server.wm.WindowManagerInternal; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -60,12 +69,30 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan GameServiceProviderInstanceImpl.this.onTaskRemoved(taskId); }); } + + // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider + // to only when the associated task is running. Right now it is possible for a task to + // move into the background and for all associated processes to die and for the Game Session + // provider's GameSessionService to continue to be running. Ideally we could unbind the + // service when this happens. }; + + private final IGameServiceController mGameServiceController = + new IGameServiceController.Stub() { + @Override + public void createGameSession(int taskId) { + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.createGameSession(taskId); + }); + } + }; + private final Object mLock = new Object(); private final UserHandle mUserHandle; private final Executor mBackgroundExecutor; private final GameClassifier mGameClassifier; private final IActivityTaskManager mActivityTaskManager; + private final WindowManagerInternal mWindowManagerInternal; private final ServiceConnector<IGameService> mGameServiceConnector; private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector; @@ -76,16 +103,18 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private volatile boolean mIsRunning; GameServiceProviderInstanceImpl( - UserHandle userHandle, + @NonNull UserHandle userHandle, @NonNull Executor backgroundExecutor, @NonNull GameClassifier gameClassifier, @NonNull IActivityTaskManager activityTaskManager, + @NonNull WindowManagerInternal windowManagerInternal, @NonNull ServiceConnector<IGameService> gameServiceConnector, @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) { mUserHandle = userHandle; mBackgroundExecutor = backgroundExecutor; mGameClassifier = gameClassifier; mActivityTaskManager = activityTaskManager; + mWindowManagerInternal = windowManagerInternal; mGameServiceConnector = gameServiceConnector; mGameSessionServiceConnector = gameSessionServiceConnector; } @@ -114,7 +143,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan // TODO(b/204503192): In cases where the connection to the game service fails retry with // back off mechanism. AndroidFuture<Void> unusedPostConnectedFuture = mGameServiceConnector.post(gameService -> { - gameService.connected(); + gameService.connected(mGameServiceController); }); try { @@ -138,16 +167,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { - IGameSession gameSession = gameSessionRecord.getGameSession(); - if (gameSession == null) { - continue; - } - - try { - gameSession.destroy(); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); - } + destroyGameSessionFromRecord(gameSessionRecord); } mGameSessions.clear(); @@ -168,10 +188,38 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } synchronized (mLock) { - createGameSessionLocked(taskId, componentName); + gameTaskStartedLocked(taskId, componentName); } } + @GuardedBy("mLock") + private void gameTaskStartedLocked(int taskId, @NonNull ComponentName componentName) { + if (DEBUG) { + Slog.i(TAG, "gameStartedLocked() id: " + taskId + " component: " + componentName); + } + + if (!mIsRunning) { + return; + } + + GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); + if (existingGameSessionRecord != null) { + Slog.w(TAG, "Existing game session found for task (id: " + taskId + + ") creation. Ignoring."); + return; + } + + GameSessionRecord gameSessionRecord = GameSessionRecord.awaitingGameSessionRequest( + taskId, componentName); + mGameSessions.put(taskId, gameSessionRecord); + + AndroidFuture<Void> unusedPostGameStartedFuture = mGameServiceConnector.post( + gameService -> { + gameService.gameStarted( + new GameStartedEvent(taskId, componentName.getPackageName())); + }); + } + private void onTaskRemoved(int taskId) { synchronized (mLock) { boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId); @@ -179,98 +227,162 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan return; } - destroyGameSessionLocked(taskId); + removeAndDestroyGameSessionIfNecessaryLocked(taskId); + } + } + + private void createGameSession(int taskId) { + synchronized (mLock) { + createGameSessionLocked(taskId); } } @GuardedBy("mLock") - private void createGameSessionLocked(int sessionId, @NonNull ComponentName componentName) { + private void createGameSessionLocked(int taskId) { if (DEBUG) { - Slog.i(TAG, "createGameSession() id: " + sessionId + " component: " + componentName); + Slog.i(TAG, "createGameSessionLocked() id: " + taskId); } if (!mIsRunning) { return; } - GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId); - if (existingGameSessionRecord != null) { - Slog.w(TAG, "Existing game session found for task (id: " + sessionId + GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); + if (existingGameSessionRecord == null) { + Slog.w(TAG, "No existing game session record found for task (id: " + taskId + ") creation. Ignoring."); return; } + if (!existingGameSessionRecord.isAwaitingGameSessionRequest()) { + Slog.w(TAG, "Existing game session for task (id: " + taskId + + ") is not awaiting game session request. Ignoring."); + return; + } - GameSessionRecord gameSessionRecord = GameSessionRecord.pendingGameSession(sessionId, - componentName); - mGameSessions.put(sessionId, gameSessionRecord); - - // TODO(b/207035150): Allow the game service provider to determine if a game session - // should be created. For now we will assume all games should have a session. - AndroidFuture<IBinder> gameSessionFuture = new AndroidFuture<IBinder>() - .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .whenCompleteAsync((gameSessionIBinder, exception) -> { - IGameSession gameSession = IGameSession.Stub.asInterface(gameSessionIBinder); - if (exception != null || gameSession == null) { - Slog.w(TAG, "Failed to create GameSession: " + gameSessionRecord, - exception); - synchronized (mLock) { - destroyGameSessionLocked(sessionId); - } - return; - } - - synchronized (mLock) { - attachGameSessionLocked(sessionId, gameSession); - } - }, mBackgroundExecutor); + GameSessionViewHostConfiguration gameSessionViewHostConfiguration = + createViewHostConfigurationForTask(taskId); + if (gameSessionViewHostConfiguration == null) { + Slog.w(TAG, "Failed to create view host configuration for task (id" + taskId + + ") creation. Ignoring."); + return; + } + + if (DEBUG) { + Slog.d(TAG, "Determined initial view host configuration for task (id: " + taskId + "): " + + gameSessionViewHostConfiguration); + } + + mGameSessions.put(taskId, existingGameSessionRecord.withGameSessionRequested()); + + AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture = + new AndroidFuture<CreateGameSessionResult>() + .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .whenCompleteAsync((createGameSessionResult, exception) -> { + if (exception != null || createGameSessionResult == null) { + Slog.w(TAG, "Failed to create GameSession: " + + existingGameSessionRecord, + exception); + synchronized (mLock) { + removeAndDestroyGameSessionIfNecessaryLocked(taskId); + } + return; + } + + synchronized (mLock) { + attachGameSessionLocked(taskId, createGameSessionResult); + } + }, mBackgroundExecutor); AndroidFuture<Void> unusedPostCreateGameSessionFuture = mGameSessionServiceConnector.post(gameService -> { CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(sessionId, componentName.getPackageName()); - gameService.create(createGameSessionRequest, gameSessionFuture); + new CreateGameSessionRequest( + taskId, + existingGameSessionRecord.getComponentName().getPackageName()); + gameService.create( + createGameSessionRequest, + gameSessionViewHostConfiguration, + createGameSessionResultFuture); }); } @GuardedBy("mLock") - private void attachGameSessionLocked(int sessionId, @NonNull IGameSession gameSession) { + private void attachGameSessionLocked( + int taskId, + @NonNull CreateGameSessionResult createGameSessionResult) { if (DEBUG) { - Slog.i(TAG, "attachGameSession() id: " + sessionId); + Slog.d(TAG, "attachGameSession() id: " + taskId); } - GameSessionRecord gameSessionRecord = mGameSessions.get(sessionId); + GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); + if (gameSessionRecord == null) { - Slog.w(TAG, "No associated game session record. Destroying id: " + sessionId); + Slog.w(TAG, "No associated game session record. Destroying id: " + taskId); + destroyGameSessionDuringAttach(taskId, createGameSessionResult); + return; + } - try { - gameSession.destroy(); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); - } + if (!gameSessionRecord.isGameSessionRequested()) { + destroyGameSessionDuringAttach(taskId, createGameSessionResult); return; } - mGameSessions.put(sessionId, gameSessionRecord.withGameSession(gameSession)); + try { + mWindowManagerInternal.addTaskOverlay( + taskId, + createGameSessionResult.getSurfacePackage()); + } catch (IllegalArgumentException ex) { + Slog.w(TAG, "Failed to add task overlay. Destroying id: " + taskId); + destroyGameSessionDuringAttach(taskId, createGameSessionResult); + return; + } + + mGameSessions.put(taskId, + gameSessionRecord.withGameSession( + createGameSessionResult.getGameSession(), + createGameSessionResult.getSurfacePackage())); + } + + private void destroyGameSessionDuringAttach( + int taskId, + CreateGameSessionResult createGameSessionResult) { + try { + createGameSessionResult.getGameSession().destroy(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to destroy session: " + taskId); + } } @GuardedBy("mLock") - private void destroyGameSessionLocked(int sessionId) { - // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider - // to only when the associated task is running. Right now it is possible for a task to - // move into the background and for all associated processes to die and for the Game Session - // provider's GameSessionService to continue to be running. Ideally we could unbind the - // service when this happens. + private void removeAndDestroyGameSessionIfNecessaryLocked(int taskId) { if (DEBUG) { - Slog.i(TAG, "destroyGameSession() id: " + sessionId); + Slog.d(TAG, "destroyGameSession() id: " + taskId); } - GameSessionRecord gameSessionRecord = mGameSessions.remove(sessionId); + GameSessionRecord gameSessionRecord = mGameSessions.remove(taskId); if (gameSessionRecord == null) { if (DEBUG) { - Slog.w(TAG, "No game session found for id: " + sessionId); + Slog.w(TAG, "No game session found for id: " + taskId); } return; } + destroyGameSessionFromRecord(gameSessionRecord); + } + + private void destroyGameSessionFromRecord(@NonNull GameSessionRecord gameSessionRecord) { + SurfacePackage surfacePackage = gameSessionRecord.getSurfacePackage(); + if (surfacePackage != null) { + try { + mWindowManagerInternal.removeTaskOverlay( + gameSessionRecord.getTaskId(), + surfacePackage); + } catch (IllegalArgumentException ex) { + Slog.i(TAG, + "Failed to remove task overlay. This is expected if the task is already " + + "destroyed: " + + gameSessionRecord); + } + } IGameSession gameSession = gameSessionRecord.getGameSession(); if (gameSession != null) { @@ -283,7 +395,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan if (mGameSessions.isEmpty()) { if (DEBUG) { - Slog.i(TAG, "No active game sessions. Disconnecting GameSessionService"); + Slog.d(TAG, "No active game sessions. Disconnecting GameSessionService"); } if (mGameSessionServiceConnector != null) { @@ -291,4 +403,41 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } } } + + + @Nullable + private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) { + RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); + if (runningTaskInfo == null) { + return null; + } + + Rect bounds = runningTaskInfo.configuration.windowConfiguration.getBounds(); + return new GameSessionViewHostConfiguration( + runningTaskInfo.displayId, + bounds.width(), + bounds.height()); + } + + @Nullable + private RunningTaskInfo getRunningTaskInfoForTask(int taskId) { + List<RunningTaskInfo> runningTaskInfos; + try { + runningTaskInfos = mActivityTaskManager.getTasks( + /* maxNum= */ Integer.MAX_VALUE, + /* filterOnlyVisibleRecents= */ true, + /* keepIntentExtra= */ false); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to fetch running tasks"); + return null; + } + + for (RunningTaskInfo taskInfo : runningTaskInfos) { + if (taskInfo.taskId == taskId) { + return taskInfo; + } + } + + return null; + } } diff --git a/services/core/java/com/android/server/app/GameSessionRecord.java b/services/core/java/com/android/server/app/GameSessionRecord.java index 329e9e8144e0..a241812f7868 100644 --- a/services/core/java/com/android/server/app/GameSessionRecord.java +++ b/services/core/java/com/android/server/app/GameSessionRecord.java @@ -20,33 +20,92 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.service.games.IGameSession; +import android.view.SurfaceControlViewHost.SurfacePackage; import java.util.Objects; final class GameSessionRecord { + private enum State { + // Game task is running, but GameSession not created. + NO_GAME_SESSION_REQUESTED, + // Game Service provider requested a Game Session and we are in the + // process of creating it. GameSessionRecord.getGameSession() == null; + GAME_SESSION_REQUESTED, + // A Game Session is created and attached. + // GameSessionRecord.getGameSession() != null. + GAME_SESSION_ATTACHED, + } + private final int mTaskId; + private final State mState; private final ComponentName mRootComponentName; @Nullable private final IGameSession mIGameSession; + @Nullable + private final SurfacePackage mSurfacePackage; - static GameSessionRecord pendingGameSession(int taskId, ComponentName rootComponentName) { - return new GameSessionRecord(taskId, rootComponentName, /* gameSession= */ null); + static GameSessionRecord awaitingGameSessionRequest(int taskId, + ComponentName rootComponentName) { + return new GameSessionRecord( + taskId, + State.NO_GAME_SESSION_REQUESTED, + rootComponentName, + /* gameSession= */ null, + /* surfacePackage= */ null); } private GameSessionRecord( int taskId, + @NonNull State state, @NonNull ComponentName rootComponentName, - @Nullable IGameSession gameSession) { + @Nullable IGameSession gameSession, + @Nullable SurfacePackage surfacePackage) { this.mTaskId = taskId; + this.mState = state; this.mRootComponentName = rootComponentName; this.mIGameSession = gameSession; + this.mSurfacePackage = surfacePackage; + } + + public boolean isAwaitingGameSessionRequest() { + return mState == State.NO_GAME_SESSION_REQUESTED; + } + + @NonNull + public GameSessionRecord withGameSessionRequested() { + return new GameSessionRecord( + mTaskId, + State.GAME_SESSION_REQUESTED, + mRootComponentName, + /* gameSession=*/ null, + /* surfacePackage=*/ null); + } + + public boolean isGameSessionRequested() { + return mState == State.GAME_SESSION_REQUESTED; } @NonNull - public GameSessionRecord withGameSession(@NonNull IGameSession gameSession) { + public GameSessionRecord withGameSession( + @NonNull IGameSession gameSession, + @NonNull SurfacePackage surfacePackage) { Objects.requireNonNull(gameSession); - return new GameSessionRecord(mTaskId, mRootComponentName, gameSession); + return new GameSessionRecord(mTaskId, + State.GAME_SESSION_ATTACHED, + mRootComponentName, + gameSession, + surfacePackage); + } + + @NonNull + public int getTaskId() { + return mTaskId; + } + + @NonNull + public ComponentName getComponentName() { + return mRootComponentName; } @Nullable @@ -54,15 +113,24 @@ final class GameSessionRecord { return mIGameSession; } + @Nullable + public SurfacePackage getSurfacePackage() { + return mSurfacePackage; + } + @Override public String toString() { return "GameSessionRecord{" + "mTaskId=" + mTaskId + + ", mState=" + + mState + ", mRootComponentName=" + mRootComponentName + ", mIGameSession=" + mIGameSession + + ", mSurfacePackage=" + + mSurfacePackage + '}'; } @@ -77,12 +145,16 @@ final class GameSessionRecord { } GameSessionRecord that = (GameSessionRecord) o; - return mTaskId == that.mTaskId && mRootComponentName.equals(that.mRootComponentName) - && Objects.equals(mIGameSession, that.mIGameSession); + return mTaskId == that.mTaskId + && mState == that.mState + && mRootComponentName.equals(that.mRootComponentName) + && Objects.equals(mIGameSession, that.mIGameSession) + && Objects.equals(mSurfacePackage, that.mSurfacePackage); } @Override public int hashCode() { - return Objects.hash(mTaskId, mRootComponentName, mIGameSession); + return Objects.hash( + mTaskId, mState, mRootComponentName, mIGameSession, mState, mSurfacePackage); } } diff --git a/services/core/java/com/android/server/app/OWNERS b/services/core/java/com/android/server/app/OWNERS index aaebbfa8e253..221e06ce5f67 100644 --- a/services/core/java/com/android/server/app/OWNERS +++ b/services/core/java/com/android/server/app/OWNERS @@ -1 +1 @@ -per-file GameManager* = file:/GAME_MANAGER_OWNERS +per-file Game* = file:/GAME_MANAGER_OWNERS diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 3c557d0cc35c..40fda4cbec5e 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -113,7 +113,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; import android.content.pm.UserInfo; -import android.content.pm.parsing.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedAttribution; import android.database.ContentObserver; import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION; import android.net.Uri; diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 2fcdd6145a64..565e29561d19 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1219,6 +1219,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mDeviceInventory.onSetBtActiveDevice((BtDeviceInfo) msg.obj, mAudioService.getBluetoothContextualVolumeStream()); } + break; case MSG_BT_HEADSET_CNCT_FAILED: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 023a11e9ad0f..0961fcb31ace 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1124,6 +1124,12 @@ public class AudioDeviceInventory { private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device, String eventSource) { if (device != AudioSystem.DEVICE_NONE) { + + /* Audio Policy sees Le Audio similar to A2DP. Let's make sure + * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set + */ + mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); + AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, address, name, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 69765d2fca2b..0ea936e35a37 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2611,6 +2611,16 @@ public class AudioService extends IAudioService.Stub return getDevicesForAttributesInt(attributes); } + /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes) + * This method is similar with AudioService#getDevicesForAttributes, + * only it doesn't enforce permissions because it is used by an unprivileged public API + * instead of the system API. + */ + public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected( + @NonNull AudioAttributes attributes) { + return getDevicesForAttributesInt(attributes); + } + /** * @see AudioManager#isMusicActive() * @param remotely true if query is for remote playback (cast), false for local playback. @@ -4374,12 +4384,6 @@ public class AudioService extends IAudioService.Stub if (!mHasVibrator) { return false; } - final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0; - if (hapticsDisabled) { - return false; - } - if (effect == null) { return false; } @@ -8541,7 +8545,7 @@ public class AudioService extends IAudioService.Stub /** @see Spatializer#getSpatializerCompatibleAudioDevices() */ public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() { - enforceModifyAudioRoutingPermission(); + enforceModifyDefaultAudioEffectsPermission(); return mSpatializerHelper.getCompatibleAudioDevices(); } @@ -9398,6 +9402,7 @@ public class AudioService extends IAudioService.Stub pw.println("mHasSpatializerEffect:" + mHasSpatializerEffect); pw.println("isSpatializerEnabled:" + isSpatializerEnabled()); pw.println("isSpatialAudioEnabled:" + isSpatialAudioEnabled()); + mSpatializerHelper.dump(pw); mAudioSystem.dump(pw); } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 9273a5d5cf9c..6ec983620698 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -102,8 +102,6 @@ public class BtHelper { /*package*/ static final int SCO_MODE_UNDEFINED = -1; // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0; - // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) - private static final int SCO_MODE_RAW = 1; // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) private static final int SCO_MODE_VR = 2; // max valid SCO audio mode values @@ -122,8 +120,6 @@ public class BtHelper { return "SCO_MODE_UNDEFINED"; case SCO_MODE_VIRTUAL_CALL: return "SCO_MODE_VIRTUAL_CALL"; - case SCO_MODE_RAW: - return "SCO_MODE_RAW"; case SCO_MODE_VR: return "SCO_MODE_VR"; default: @@ -812,8 +808,6 @@ public class BtHelper { private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode) { switch (scoAudioMode) { - case SCO_MODE_RAW: - return bluetoothHeadset.disconnectAudio(); case SCO_MODE_VIRTUAL_CALL: return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); case SCO_MODE_VR: @@ -826,8 +820,6 @@ public class BtHelper { private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode) { switch (scoAudioMode) { - case SCO_MODE_RAW: - return bluetoothHeadset.connectAudio(); case SCO_MODE_VIRTUAL_CALL: return bluetoothHeadset.startScoUsingVirtualVoiceCall(); case SCO_MODE_VR: diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 406b2dd2486d..74c899980d86 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -252,6 +252,9 @@ public final class PlaybackActivityMonitor AudioAttributes.FLAG_BYPASS_MUTE; private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) { + if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID) { + return; + } if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED || apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE) diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index b47ea4f7a4b8..e6789d5e1d49 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -39,6 +39,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -84,7 +85,7 @@ public class SpatializerHelper { private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; - private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; private int mSpatOutput = 0; private @Nullable ISpatializer mSpat; private @Nullable SpatializerCallback mSpatCallback; @@ -681,12 +682,13 @@ public class SpatializerHelper { return; } try { - if (mode != mDesiredHeadTrackingMode) { - mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); + if (mDesiredHeadTrackingMode != mode) { mDesiredHeadTrackingMode = mode; dispatchDesiredHeadTrackingMode(mode); } - + if (mode != headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode())) { + mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); + } } catch (RemoteException e) { Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e); } @@ -937,6 +939,7 @@ public class SpatializerHelper { } catch (Exception e) { Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e); } + setDesiredHeadTrackingMode(mDesiredHeadTrackingMode); } //------------------------------------------------------ @@ -983,4 +986,22 @@ public class SpatializerHelper { throw(new IllegalArgumentException("Unexpected spatializer level:" + level)); } } + + void dump(PrintWriter pw) { + pw.println("SpatializerHelper:"); + pw.println("\tmState:" + mState); + pw.println("\tmSpatLevel:" + mSpatLevel); + pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel); + pw.println("\tmActualHeadTrackingMode:" + + Spatializer.headtrackingModeToString(mActualHeadTrackingMode)); + pw.println("\tmDesiredHeadTrackingMode:" + + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode)); + String modesString = ""; + int[] modes = getSupportedHeadTrackingModes(); + for (int mode : modes) { + modesString += Spatializer.headtrackingModeToString(mode) + " "; + } + pw.println("\tsupported head tracking modes:" + modesString); + pw.println("\tmSpatOutput:" + mSpatOutput); + } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java new file mode 100644 index 000000000000..c3471bd1d771 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -0,0 +1,126 @@ +/* + * 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.biometrics.log; + +import android.hardware.biometrics.BiometricsProtoEnums; +import android.util.Slog; + +import com.android.internal.util.FrameworkStatsLog; + +/** + * Wrapper for {@link FrameworkStatsLog} to isolate the testable parts. + */ +public class BiometricFrameworkStatsLogger { + + private static final String TAG = "BiometricFrameworkStatsLogger"; + + private static final BiometricFrameworkStatsLogger sInstance = + new BiometricFrameworkStatsLogger(); + + private BiometricFrameworkStatsLogger() {} + + public static BiometricFrameworkStatsLogger getInstance() { + return sInstance; + } + + /** {@see FrameworkStatsLog.BIOMETRIC_ACQUIRED}. */ + public void acquired( + int statsModality, int statsAction, int statsClient, boolean isDebug, + int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED, + statsModality, + targetUserId, + isCrypto, + statsAction, + statsClient, + acquiredInfo, + vendorCode, + isDebug, + -1 /* sensorId */); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ + public void authenticate( + int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, + boolean authenticated, int authState, boolean requireConfirmation, boolean isCrypto, + int targetUserId, boolean isBiometricPrompt, float ambientLightLux) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, + statsModality, + targetUserId, + isCrypto, + statsClient, + requireConfirmation, + authState, + sanitizeLatency(latency), + isDebug, + -1 /* sensorId */, + ambientLightLux); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */ + public void enroll(int statsModality, int statsAction, int statsClient, + int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED, + statsModality, + targetUserId, + sanitizeLatency(latency), + enrollSuccessful, + -1, /* sensorId */ + ambientLightLux); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */ + public void error( + int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, + int error, int vendorCode, boolean isCrypto, int targetUserId) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED, + statsModality, + targetUserId, + isCrypto, + statsAction, + statsClient, + error, + vendorCode, + isDebug, + sanitizeLatency(latency), + -1 /* sensorId */); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */ + public void reportUnknownTemplateEnrolledHal(int statsModality) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, + statsModality, + BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_HAL, + -1 /* sensorId */); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */ + public void reportUnknownTemplateEnrolledFramework(int statsModality) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, + statsModality, + BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_FRAMEWORK, + -1 /* sensorId */); + } + + private long sanitizeLatency(long latency) { + if (latency < 0) { + Slog.w(TAG, "found a negative latency : " + latency); + return -1; + } + return latency; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java index b4c82f2ed799..d029af38c683 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.biometrics.sensors; +package com.android.server.biometrics.log; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,71 +29,28 @@ import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.Utils; /** - * Abstract class that adds logging functionality to the ClientMonitor classes. + * Logger for all reported Biometric framework events. */ -public abstract class LoggableMonitor { +public class BiometricLogger { - public static final String TAG = "Biometrics/LoggableMonitor"; + public static final String TAG = "BiometricLogger"; public static final boolean DEBUG = false; - final int mStatsModality; + private final int mStatsModality; private final int mStatsAction; private final int mStatsClient; + private final BiometricFrameworkStatsLogger mSink; @NonNull private final SensorManager mSensorManager; + private long mFirstAcquireTimeMs; private boolean mLightSensorEnabled = false; private boolean mShouldLogMetrics = true; - /** - * Probe for loggable attributes that can be continuously monitored, such as ambient light. - * - * Disable probes when the sensors are in states that are not interesting for monitoring - * purposes to save power. - */ - protected interface Probe { - /** Ensure the probe is actively sampling for new data. */ - void enable(); - /** Stop sampling data. */ - void disable(); - } - - /** - * Client monitor callback that exposes a probe. - * - * Disables the probe when the operation completes. - */ - protected static class CallbackWithProbe<T extends Probe> - implements BaseClientMonitor.Callback { - private final boolean mStartWithClient; - private final T mProbe; - - public CallbackWithProbe(@NonNull T probe, boolean startWithClient) { - mProbe = probe; - mStartWithClient = startWithClient; - } - - @Override - public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - if (mStartWithClient) { - mProbe.enable(); - } - } - - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { - mProbe.disable(); - } - - @NonNull - public T getProbe() { - return mProbe; - } - } - private class ALSProbe implements Probe { @Override public void enable() { @@ -128,26 +85,30 @@ public abstract class LoggableMonitor { * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants. * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants. */ - public LoggableMonitor(@NonNull Context context, int statsModality, int statsAction, - int statsClient) { + public BiometricLogger( + @NonNull Context context, int statsModality, int statsAction, int statsClient) { + this(statsModality, statsAction, statsClient, + BiometricFrameworkStatsLogger.getInstance(), + context.getSystemService(SensorManager.class)); + } + + @VisibleForTesting + BiometricLogger( + int statsModality, int statsAction, int statsClient, + BiometricFrameworkStatsLogger logSink, SensorManager sensorManager) { mStatsModality = statsModality; mStatsAction = statsAction; mStatsClient = statsClient; - mSensorManager = context.getSystemService(SensorManager.class); + mSink = logSink; + mSensorManager = sensorManager; } - /** - * Only valid for AuthenticationClient. - * @return true if the client is authenticating for a crypto operation. - */ - protected boolean isCryptoOperation() { - return false; - } - - protected void setShouldLog(boolean shouldLog) { - mShouldLogMetrics = shouldLog; + /** Disable logging metrics and only log critical events, such as system health issues. */ + public void disableMetrics() { + mShouldLogMetrics = false; } + /** {@link BiometricsProtoEnums} CLIENT_* constants */ public int getStatsClient() { return mStatsClient; } @@ -171,8 +132,9 @@ public abstract class LoggableMonitor { return shouldSkipLogging; } - protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode, - int targetUserId) { + /** Log an acquisition event. */ + public void logOnAcquired(Context context, + int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) { if (!mShouldLogMetrics) { return; } @@ -192,7 +154,7 @@ public abstract class LoggableMonitor { if (DEBUG) { Slog.v(TAG, "Acquired! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCryptoOperation() + + ", IsCrypto: " + isCrypto + ", Action: " + mStatsAction + ", Client: " + mStatsClient + ", AcquiredInfo: " + acquiredInfo @@ -203,19 +165,14 @@ public abstract class LoggableMonitor { return; } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED, - mStatsModality, - targetUserId, - isCryptoOperation(), - mStatsAction, - mStatsClient, - acquiredInfo, - vendorCode, + mSink.acquired(mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), - -1 /* sensorId */); + acquiredInfo, vendorCode, isCrypto, targetUserId); } - protected final void logOnError(Context context, int error, int vendorCode, int targetUserId) { + /** Log an error during an operation. */ + public void logOnError(Context context, + int error, int vendorCode, boolean isCrypto, int targetUserId) { if (!mShouldLogMetrics) { return; } @@ -226,7 +183,7 @@ public abstract class LoggableMonitor { if (DEBUG) { Slog.v(TAG, "Error! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCryptoOperation() + + ", IsCrypto: " + isCrypto + ", Action: " + mStatsAction + ", Client: " + mStatsClient + ", Error: " + error @@ -240,21 +197,15 @@ public abstract class LoggableMonitor { return; } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED, - mStatsModality, - targetUserId, - isCryptoOperation(), - mStatsAction, - mStatsClient, - error, - vendorCode, - Utils.isDebugEnabled(context, targetUserId), - sanitizeLatency(latency), - -1 /* sensorId */); + mSink.error(mStatsModality, mStatsAction, mStatsClient, + Utils.isDebugEnabled(context, targetUserId), latency, + error, vendorCode, isCrypto, targetUserId); } - protected final void logOnAuthenticated(Context context, boolean authenticated, - boolean requireConfirmation, int targetUserId, boolean isBiometricPrompt) { + /** Log authentication attempt. */ + public void logOnAuthenticated(Context context, + boolean authenticated, boolean requireConfirmation, boolean isCrypto, + int targetUserId, boolean isBiometricPrompt) { if (!mShouldLogMetrics) { return; } @@ -279,7 +230,7 @@ public abstract class LoggableMonitor { if (DEBUG) { Slog.v(TAG, "Authenticated! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCryptoOperation() + + ", IsCrypto: " + isCrypto + ", Client: " + mStatsClient + ", RequireConfirmation: " + requireConfirmation + ", State: " + authState @@ -293,20 +244,14 @@ public abstract class LoggableMonitor { return; } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, - mStatsModality, - targetUserId, - isCryptoOperation(), - mStatsClient, - requireConfirmation, - authState, - sanitizeLatency(latency), + mSink.authenticate(mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), - -1 /* sensorId */, - mLastAmbientLux /* ambientLightLux */); + latency, authenticated, authState, requireConfirmation, isCrypto, + targetUserId, isBiometricPrompt, mLastAmbientLux); } - protected final void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) { + /** Log enrollment outcome. */ + public void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) { if (!mShouldLogMetrics) { return; } @@ -326,25 +271,30 @@ public abstract class LoggableMonitor { return; } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED, - mStatsModality, - targetUserId, - sanitizeLatency(latency), - enrollSuccessful, - -1, /* sensorId */ - mLastAmbientLux /* ambientLightLux */); + mSink.enroll(mStatsModality, mStatsAction, mStatsClient, + targetUserId, latency, enrollSuccessful, mLastAmbientLux); } - private long sanitizeLatency(long latency) { - if (latency < 0) { - Slog.w(TAG, "found a negative latency : " + latency); - return -1; + /** Report unexpected enrollment reported by the HAL. */ + public void logUnknownEnrollmentInHal() { + if (shouldSkipLogging()) { + return; } - return latency; + + mSink.reportUnknownTemplateEnrolledHal(mStatsModality); + } + + /** Report unknown enrollment in framework settings */ + public void logUnknownEnrollmentInFramework() { + if (shouldSkipLogging()) { + return; + } + + mSink.reportUnknownTemplateEnrolledFramework(mStatsModality); } /** - * Get a callback to start/stop ALS capture when client runs. + * Get a callback to start/stop ALS capture when a client runs. * * If the probe should not run for the entire operation, do not set startWithClient and * start/stop the problem when needed. @@ -352,7 +302,7 @@ public abstract class LoggableMonitor { * @param startWithClient if probe should start automatically when the operation starts. */ @NonNull - protected CallbackWithProbe<Probe> createALSCallback(boolean startWithClient) { + public CallbackWithProbe<Probe> createALSCallback(boolean startWithClient) { return new CallbackWithProbe<>(new ALSProbe(), startWithClient); } diff --git a/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java b/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java new file mode 100644 index 000000000000..c985d5d2e597 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.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.biometrics.log; + +import android.annotation.NonNull; + +import com.android.server.biometrics.sensors.BaseClientMonitor; + +/** + * Client monitor callback that exposes a probe. + * + * Disables the probe when the operation completes. + * + * @param <T> probe type + */ +public class CallbackWithProbe<T extends Probe> implements BaseClientMonitor.Callback { + private final boolean mStartWithClient; + private final T mProbe; + + public CallbackWithProbe(@NonNull T probe, boolean startWithClient) { + mProbe = probe; + mStartWithClient = startWithClient; + } + + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + if (mStartWithClient) { + mProbe.enable(); + } + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { + mProbe.disable(); + } + + @NonNull + public T getProbe() { + return mProbe; + } +} diff --git a/services/core/java/com/android/server/biometrics/log/Probe.java b/services/core/java/com/android/server/biometrics/log/Probe.java new file mode 100644 index 000000000000..9e6fc6b8b8b2 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/Probe.java @@ -0,0 +1,30 @@ +/* + * 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.biometrics.log; + +/** + * Probe for loggable attributes that can be continuously monitored, such as ambient light. + * + * Disable probes when the sensors are in states that are not interesting for monitoring + * purposes to save power. + */ +public interface Probe { + /** Ensure the probe is actively sampling for new data. */ + void enable(); + /** Stop sampling data. */ + void disable(); +} diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index 6f7176816ddb..e29caa8461cf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -105,7 +105,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement // that do not handle lockout under the HAL. In these cases, ensure that the framework only // sends errors once per ClientMonitor. if (mShouldSendErrorToClient) { - logOnError(getContext(), errorCode, vendorCode, getTargetUserId()); + getLogger().logOnError(getContext(), errorCode, vendorCode, + isCryptoOperation(), getTargetUserId()); try { if (getListener() != null) { mShouldSendErrorToClient = false; @@ -163,7 +164,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement protected final void onAcquiredInternal(int acquiredInfo, int vendorCode, boolean shouldSend) { - super.logOnAcquired(getContext(), acquiredInfo, vendorCode, getTargetUserId()); + getLogger().logOnAcquired(getContext(), acquiredInfo, vendorCode, + isCryptoOperation(), getTargetUserId()); if (DEBUG) { Slog.v(TAG, "Acquired: " + acquiredInfo + " " + vendorCode + ", shouldSend: " + shouldSend); diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 358263df916b..0eb5aaf143c4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -180,8 +180,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> @Override public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> hardwareAuthToken) { - super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation, - getTargetUserId(), isBiometricPrompt()); + getLogger().logOnAuthenticated(getContext(), authenticated, mRequireConfirmation, + isCryptoOperation(), getTargetUserId(), isBiometricPrompt()); final ClientMonitorCallbackConverter listener = getListener(); diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index 26bbb403f39f..1248c8b6d4d9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -27,18 +27,18 @@ import android.os.RemoteException; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.log.BiometricLogger; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; /** - * Abstract base class for keeping track and dispatching events from the biometric's HAL to the + * Abstract base class for keeping track and dispatching events from the biometric's HAL to * the current client. Subclasses are responsible for coordinating the interaction with * the biometric's HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.). */ -public abstract class BaseClientMonitor extends LoggableMonitor - implements IBinder.DeathRecipient { +public abstract class BaseClientMonitor implements IBinder.DeathRecipient { private static final String TAG = "Biometrics/ClientMonitor"; protected static final boolean DEBUG = true; @@ -108,6 +108,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor private final int mTargetUserId; @NonNull private final String mOwner; private final int mSensorId; // sensorId as configured by the framework + @NonNull private final BiometricLogger mLogger; @Nullable private IBinder mToken; private long mRequestId; @@ -160,7 +161,14 @@ public abstract class BaseClientMonitor extends LoggableMonitor @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction, int statsClient) { - super(context, statsModality, statsAction, statsClient); + this(context, token, listener, userId, owner, cookie, sensorId, + new BiometricLogger(context, statsModality, statsAction, statsClient)); + } + + @VisibleForTesting + BaseClientMonitor(@NonNull Context context, + @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, + @NonNull String owner, int cookie, int sensorId, @NonNull BiometricLogger logger) { mSequentialId = sCount++; mContext = context; mToken = token; @@ -170,6 +178,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor mOwner = owner; mCookie = cookie; mSensorId = sensorId; + mLogger = logger; try { if (token != null) { @@ -180,10 +189,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor } } - public int getCookie() { - return mCookie; - } - /** * Starts the ClientMonitor's lifecycle. * @param callback invoked when the operation is complete (succeeds, fails, etc) @@ -257,6 +262,20 @@ public abstract class BaseClientMonitor extends LoggableMonitor } } + /** + * Only valid for AuthenticationClient. + * @return true if the client is authenticating for a crypto operation. + */ + protected boolean isCryptoOperation() { + return false; + } + + /** Logger for this client */ + @NonNull + public BiometricLogger getLogger() { + return mLogger; + } + public final Context getContext() { return mContext; } @@ -281,6 +300,11 @@ public abstract class BaseClientMonitor extends LoggableMonitor return mSensorId; } + /** Cookie set when this monitor was created. */ + public int getCookie() { + return mCookie; + } + /** Unique request id. */ public final long getRequestId() { return mRequestId; diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 1f91c4d6803e..39c5944d65c7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -25,6 +25,7 @@ import android.hardware.biometrics.IBiometricService; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Slog; @@ -232,17 +233,14 @@ public class BiometricScheduler { * Creates a new scheduler. * * @param tag for the specific instance of the scheduler. Should be unique. - * @param handler handler for callbacks (all methods of this class must be called on the - * thread associated with this handler) * @param sensorType the sensorType that this scheduler is handling. * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures * (such as fingerprint swipe). */ public BiometricScheduler(@NonNull String tag, - @NonNull Handler handler, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - this(tag, handler, sensorType, gestureAvailabilityDispatcher, + this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance()); @@ -376,8 +374,9 @@ public class BiometricScheduler { // send ERROR_CANCELED and skip the operation. if (clientMonitor.interruptsPrecedingClients()) { for (BiometricSchedulerOperation operation : mPendingOperations) { - Slog.d(getTag(), "New client, marking pending op as canceling: " + operation); - operation.markCanceling(); + if (operation.markCanceling()) { + Slog.d(getTag(), "New client, marking pending op as canceling: " + operation); + } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java index a8cce153dc70..e8b50d90b586 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java @@ -225,12 +225,13 @@ public class BiometricSchedulerOperation { Slog.v(TAG, "Aborted: " + this); } - /** Flags this operation as canceled, but does not cancel it until started. */ - public void markCanceling() { + /** Flags this operation as canceled, if possible, but does not cancel it until started. */ + public boolean markCanceling() { if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) { mState = STATE_WAITING_IN_QUEUE_CANCELING; - Slog.v(TAG, "Marked cancelling: " + this); + return true; } + return false; } /** @@ -280,6 +281,7 @@ public class BiometricSchedulerOperation { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { + Slog.d(TAG, "[Finished / destroy]: " + clientMonitor); mClientMonitor.destroy(); mState = STATE_FINISHED; } diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 2826e0c97305..c83323a3eb3f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -89,7 +89,8 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En if (remaining == 0) { mBiometricUtils.addBiometricForUser(getContext(), getTargetUserId(), identifier); - logOnEnrolled(getTargetUserId(), System.currentTimeMillis() - mEnrollmentStartTimeMs, + getLogger().logOnEnrolled(getTargetUserId(), + System.currentTimeMillis() - mEnrollmentStartTimeMs, true /* enrollSuccessful */); mCallback.onClientFinished(this, true /* success */); } @@ -116,7 +117,8 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En */ @Override public void onError(int error, int vendorCode) { - logOnEnrolled(getTargetUserId(), System.currentTimeMillis() - mEnrollmentStartTimeMs, + getLogger().logOnEnrolled(getTargetUserId(), + System.currentTimeMillis() - mEnrollmentStartTimeMs, false /* enrollSuccessful */); super.onError(error, vendorCode); } diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java index 579dfd69ec66..82a843727793 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java @@ -23,7 +23,6 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.util.Slog; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.BiometricsProto; import java.util.ArrayList; @@ -128,10 +127,9 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(), template.mIdentifier.getBiometricId(), template.mUserId, getContext().getPackageName(), mBiometricUtils, getSensorId(), mAuthenticatorIds); - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, - mStatsModality, - BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_HAL, - -1 /* sensorId */); + + getLogger().logUnknownEnrollmentInHal(); + mCurrentTask.start(mRemoveCallback); } diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java index 7f6903a17b32..ced464e478e5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java @@ -23,7 +23,6 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.util.Slog; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.BiometricsProto; import java.util.ArrayList; @@ -116,10 +115,8 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> + identifier.getBiometricId() + " " + identifier.getName()); mUtils.removeBiometricForUser(getContext(), getTargetUserId(), identifier.getBiometricId()); - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, - mStatsModality, - BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_FRAMEWORK, - -1 /* sensorId */); + + getLogger().logUnknownEnrollmentInFramework(); } mEnrolledList.clear(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index 19eaa178c7c9..603cc22968a9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.IBiometricService; import android.os.Handler; +import android.os.Looper; import android.os.ServiceManager; import android.os.UserHandle; import android.util.Slog; @@ -85,7 +86,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } @VisibleForTesting - UserAwareBiometricScheduler(@NonNull String tag, + public UserAwareBiometricScheduler(@NonNull String tag, @NonNull Handler handler, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @@ -101,12 +102,11 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } public UserAwareBiometricScheduler(@NonNull String tag, - @NonNull Handler handler, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull CurrentUserRetriever currentUserRetriever, @NonNull UserSwitchCallback userSwitchCallback) { - this(tag, handler, sensorType, gestureAvailabilityDispatcher, + this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance()); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 4131ae127ab2..757a52cb8d8c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -105,7 +105,8 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @NonNull @Override protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + return new CompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override @@ -241,7 +242,8 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT; - logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId()); + getLogger().logOnError(getContext(), error, 0 /* vendorCode */, + isCryptoOperation(), getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); @@ -256,7 +258,8 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; - logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId()); + getLogger().logOnError(getContext(), error, 0 /* vendorCode */, + isCryptoOperation(), getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index aae4fbe9b0d7..b5f89b497273 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -111,7 +111,7 @@ public class FaceEnrollClient extends EnrollClient<ISession> { @Override protected Callback wrapCallbackForStart(@NonNull Callback callback) { return new CompositeCallback(mPreviewHandleDeleterCallback, - createALSCallback(true /* startWithClient */), callback); + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 39270430c21d..206b8f0779e8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -494,7 +494,7 @@ public class Sensor { mToken = new Binder(); mHandler = handler; mSensorProperties = sensorProperties; - mScheduler = new UserAwareBiometricScheduler(tag, mHandler, + mScheduler = new UserAwareBiometricScheduler(tag, BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, new UserAwareBiometricScheduler.UserSwitchCallback() { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java index 525e508f2b6d..15d6a8978dcb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java @@ -17,6 +17,7 @@ package com.android.server.biometrics.sensors.face.aidl; import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.face.EnrollmentStageConfig; import android.hardware.biometrics.face.Error; import android.hardware.biometrics.face.IFace; @@ -188,6 +189,24 @@ public class TestHal extends IFace.Stub { Slog.w(TAG, "close"); cb.onSessionClosed(); } + + @Override + public ICancellationSignal authenticateWithContext( + long operationId, OperationContext context) { + return authenticate(operationId); + } + + @Override + public ICancellationSignal enrollWithContext( + HardwareAuthToken hat, byte enrollmentType, byte[] features, + NativeHandle previewSurface, OperationContext context) { + return enroll(hat, enrollmentType, features, previewSurface); + } + + @Override + public ICancellationSignal detectInteractionWithContext(OperationContext context) { + return detectInteraction(); + } }; } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 493c0a05e379..e957794372aa 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -363,7 +363,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull LockoutResetDispatcher lockoutResetDispatcher) { final Handler handler = new Handler(Looper.getMainLooper()); return new Face10(context, sensorProps, lockoutResetDispatcher, handler, - new BiometricScheduler(TAG, handler, BiometricScheduler.SENSOR_TYPE_FACE, + new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityTracker */)); } @@ -896,6 +896,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { boolean success) { if (success) { mCurrentUserId = targetUserId; + } else { + Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); } } }); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 7548d2871a15..80faf3ea17c5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -95,7 +95,8 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { @NonNull @Override protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + return new CompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java index 31e5c86103fb..5c69d6fb65c9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java @@ -70,7 +70,8 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { @NonNull @Override protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + return new CompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index e4d5fba3a471..96f485394c70 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -33,6 +33,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.CallbackWithProbe; +import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -80,7 +82,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp mLockoutCache = lockoutCache; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; - mALSProbeCallback = createALSCallback(false /* startWithClient */); + mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */); } @Override @@ -233,7 +235,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; - logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId()); + getLogger().logOnError(getContext(), error, 0 /* vendorCode */, + isCryptoOperation(), getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); @@ -251,7 +254,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; - logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId()); + getLogger().logOnError(getContext(), error, 0 /* vendorCode */, + isCryptoOperation(), getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 67507ccbbbfe..e3f26df1a457 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -76,14 +76,15 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { - setShouldLog(false); + getLogger().disableMetrics(); } } @NonNull @Override protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + return new CompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 256761a61a72..59e4b582ca84 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -449,7 +449,7 @@ class Sensor { mHandler = handler; mSensorProperties = sensorProperties; mLockoutCache = new LockoutCache(); - mScheduler = new UserAwareBiometricScheduler(tag, handler, + mScheduler = new UserAwareBiometricScheduler(tag, BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties), gestureAvailabilityDispatcher, () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java index e7719239d844..1eb153cc8b3c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java @@ -17,10 +17,12 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.Error; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.ISessionCallback; +import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; @@ -182,6 +184,34 @@ public class TestHal extends IFingerprint.Stub { public void onUiReady() { Slog.w(TAG, "onUiReady"); } + + @Override + public ICancellationSignal authenticateWithContext( + long operationId, OperationContext context) { + return authenticate(operationId); + } + + @Override + public ICancellationSignal enrollWithContext( + HardwareAuthToken hat, OperationContext context) { + return enroll(hat); + } + + @Override + public ICancellationSignal detectInteractionWithContext(OperationContext context) { + return detectInteraction(); + } + + @Override + public void onPointerDownWithContext(PointerContext context) { + onPointerDown( + context.pointerId, context.x, context.y, context.minor, context.major); + } + + @Override + public void onPointerUpWithContext(PointerContext context) { + onPointerUp(context.pointerId); + } }; } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index d352cda609e3..6feb5fa418bb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -360,7 +360,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { final BiometricScheduler scheduler = - new BiometricScheduler(TAG, handler, + new BiometricScheduler(TAG, BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps), gestureAvailabilityDispatcher); final HalResultController controller = new HalResultController(sensorProps.sensorId, @@ -490,19 +490,25 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty(); final FingerprintUpdateActiveUserClient client = new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, - mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId, - hasEnrolled, mAuthenticatorIds, force); + mContext.getOpPackageName(), mSensorProperties.sensorId, + this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force); mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { if (success) { mCurrentUserId = targetUserId; + } else { + Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); } } }); } + private int getCurrentUser() { + return mCurrentUserId; + } + @Override public boolean containsSensor(int sensorId) { return mSensorProperties.sensorId == sensorId; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 20dab5552df9..38fe73fc9b88 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -138,8 +138,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - super(tag, handler, BiometricScheduler.SENSOR_TYPE_FP_OTHER, - gestureAvailabilityDispatcher); + super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher); } void init(@NonNull Fingerprint21UdfpsMock fingerprint21) { @@ -365,7 +364,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage final ClientMonitorCallbackConverter listener = client.getListener(); final String opPackageName = client.getOwnerString(); final boolean restricted = authClient.isRestricted(); - final int statsClient = client.getStatsClient(); + final int statsClient = client.getLogger().getStatsClient(); final boolean isKeyguard = authClient.isKeyguard(); // Don't actually send cancel() to the HAL, since successful auth already finishes diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 3058e2508f5f..d9b290febd9d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -32,6 +32,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.CallbackWithProbe; +import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -80,7 +82,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi mLockoutFrameworkImpl = lockoutTracker; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; - mALSProbeCallback = createALSCallback(false /* startWithClient */); + mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index b854fb300ece..f1dec6648740 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -127,8 +127,8 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> hardwareAuthToken) { - logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */, - getTargetUserId(), false /* isBiometricPrompt */); + getLogger().logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */, + isCryptoOperation(), getTargetUserId(), false /* isBiometricPrompt */); // Do not distinguish between success/failures. vibrateSuccess(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index cc50bdfb59ae..dd92e3ed3026 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -69,14 +69,15 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { - setShouldLog(false); + getLogger().disableMetrics(); } } @NonNull @Override protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + return new CompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java index fd38bdd1201e..a2c18923c00e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java @@ -31,6 +31,7 @@ import com.android.server.biometrics.sensors.HalClientMonitor; import java.io.File; import java.util.Map; +import java.util.function.Supplier; /** * Sets the HAL's current active user, and updates the framework's authenticatorId cache. @@ -40,7 +41,7 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr private static final String TAG = "FingerprintUpdateActiveUserClient"; private static final String FP_DATA_DIR = "fpdata"; - private final int mCurrentUserId; + private final Supplier<Integer> mCurrentUserId; private final boolean mForceUpdateAuthenticatorId; private final boolean mHasEnrolledBiometrics; private final Map<Integer, Long> mAuthenticatorIds; @@ -48,8 +49,9 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr FingerprintUpdateActiveUserClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId, - @NonNull String owner, int sensorId, int currentUserId, boolean hasEnrolledBiometrics, - @NonNull Map<Integer, Long> authenticatorIds, boolean forceUpdateAuthenticatorId) { + @NonNull String owner, int sensorId, Supplier<Integer> currentUserId, + boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds, + boolean forceUpdateAuthenticatorId) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); @@ -63,7 +65,7 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr public void start(@NonNull Callback callback) { super.start(callback); - if (mCurrentUserId == getTargetUserId() && !mForceUpdateAuthenticatorId) { + if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) { Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning"); callback.onClientFinished(this, true /* success */); return; @@ -109,8 +111,10 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr @Override protected void startHalOperation() { try { - getFreshDaemon().setActiveGroup(getTargetUserId(), mDirectory.getAbsolutePath()); - mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics + final int targetId = getTargetUserId(); + Slog.d(TAG, "Setting active user: " + targetId); + getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath()); + mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics ? getFreshDaemon().getAuthenticatorId() : 0L); mCallback.onClientFinished(this, true /* success */); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java index 8c9389101141..6654c0c2304d 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java @@ -189,7 +189,8 @@ class ProgramInfoCache { removed.add(id); } } - if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.isComplete()) { + if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.isComplete() + && !chunk.isPurge()) { return null; } mComplete = chunk.isComplete(); @@ -239,9 +240,10 @@ class ProgramInfoCache { } // Determine number of chunks we need to send. - int numChunks = 0; + int numChunks = purge ? 1 : 0; if (modified != null) { - numChunks = roundUpFraction(modified.size(), maxNumModifiedPerChunk); + numChunks = Math.max(numChunks, + roundUpFraction(modified.size(), maxNumModifiedPerChunk)); } if (removed != null) { numChunks = Math.max(numChunks, roundUpFraction(removed.size(), maxNumRemovedPerChunk)); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 3120dc58eebd..33a26baea90e 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -46,6 +46,8 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.hardware.display.DisplayManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; import android.media.AudioManager; import android.nfc.INfcAdapter; import android.os.Binder; @@ -335,6 +337,16 @@ public class CameraServiceProxy extends SystemService switchUserLocked(mLastUser); } break; + case UsbManager.ACTION_USB_DEVICE_ATTACHED: + case UsbManager.ACTION_USB_DEVICE_DETACHED: + synchronized (mLock) { + UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (device != null) { + notifyUsbDeviceHotplugLocked(device, + action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)); + } + } + break; default: break; // do nothing } @@ -645,6 +657,8 @@ public class CameraServiceProxy extends SystemService filter.addAction(Intent.ACTION_USER_INFO_CHANGED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); mContext.registerReceiver(mIntentReceiver, filter); publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy); @@ -788,6 +802,7 @@ public class CameraServiceProxy extends SystemService streamProtos[i].histogramType = streamStats.getHistogramType(); streamProtos[i].histogramBins = streamStats.getHistogramBins(); streamProtos[i].histogramCounts = streamStats.getHistogramCounts(); + streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile(); if (CameraServiceProxy.DEBUG) { String histogramTypeName = @@ -807,7 +822,8 @@ public class CameraServiceProxy extends SystemService + ", histogramBins " + Arrays.toString(streamProtos[i].histogramBins) + ", histogramCounts " - + Arrays.toString(streamProtos[i].histogramCounts)); + + Arrays.toString(streamProtos[i].histogramCounts) + + ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile); } } } @@ -961,6 +977,32 @@ public class CameraServiceProxy extends SystemService return true; } + private boolean notifyUsbDeviceHotplugLocked(@NonNull UsbDevice device, boolean attached) { + // Only handle external USB camera devices + if (device.getHasVideoCapture()) { + // Forward the usb hotplug event to the native camera service running in the + // cameraserver + // process. + ICameraService cameraService = getCameraServiceRawLocked(); + if (cameraService == null) { + Slog.w(TAG, "Could not notify cameraserver, camera service not available."); + return false; + } + + try { + int eventType = attached ? ICameraService.EVENT_USB_DEVICE_ATTACHED + : ICameraService.EVENT_USB_DEVICE_DETACHED; + mCameraServiceRaw.notifySystemEvent(eventType, new int[]{device.getDeviceId()}); + } catch (RemoteException e) { + Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e); + // Not much we can do if camera service is dead. + return false; + } + return true; + } + return false; + } + private void updateActivityCount(CameraSessionStats cameraState) { String cameraId = cameraState.getCameraId(); int newCameraState = cameraState.getNewCameraState(); diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index e12034333554..5b76695ab0da 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -81,6 +81,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -127,6 +128,7 @@ public class ClipboardService extends SystemService { private final IUriGrantsManager mUgm; private final UriGrantsManagerInternal mUgmInternal; private final WindowManagerInternal mWm; + private final VirtualDeviceManagerInternal mVdm; private final IUserManager mUm; private final PackageManager mPm; private final AppOpsManager mAppOps; @@ -158,6 +160,8 @@ public class ClipboardService extends SystemService { mUgm = UriGrantsManager.getService(); mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); mWm = LocalServices.getService(WindowManagerInternal.class); + // Can be null; not all products have CDM + VirtualDeviceManager + mVdm = LocalServices.getService(VirtualDeviceManagerInternal.class); mPm = getContext().getPackageManager(); mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE); mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); @@ -614,9 +618,6 @@ public class ClipboardService extends SystemService { mEmulatorClipboardMonitor.accept(clip); final int userId = UserHandle.getUserId(uid); - if (clip != null) { - startClassificationLocked(clip, userId); - } // Update this user setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage); @@ -672,6 +673,17 @@ public class ClipboardService extends SystemService { @GuardedBy("mLock") private void setPrimaryClipInternalLocked(PerUserClipboard clipboard, @Nullable ClipData clip, int uid, @Nullable String sourcePackage) { + final int userId = UserHandle.getUserId(uid); + if (clip != null) { + startClassificationLocked(clip, userId); + } + + setPrimaryClipInternalNoClassifyLocked(clipboard, clip, uid, sourcePackage); + } + + @GuardedBy("mLock") + private void setPrimaryClipInternalNoClassifyLocked(PerUserClipboard clipboard, + @Nullable ClipData clip, int uid, @Nullable String sourcePackage) { revokeUris(clipboard); clipboard.activePermissionOwners.clear(); if (clip == null && clipboard.primaryClip == null) { @@ -965,6 +977,13 @@ public class ClipboardService extends SystemService { // First, verify package ownership to ensure use below is safe. mAppOps.checkPackage(uid, callingPackage); + // Nothing in a virtual session is permitted to touch clipboard contents + if (mVdm != null && mVdm.isAppRunningOnAnyVirtualDevice(uid)) { + Slog.w(TAG, "Clipboard access denied to " + uid + "/" + callingPackage + + " within a virtual device session"); + return false; + } + // Shell can access the clipboard for testing purposes. if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND, callingPackage) == PackageManager.PERMISSION_GRANTED) { diff --git a/services/core/java/com/android/server/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java deleted file mode 100644 index 600313bbad64..000000000000 --- a/services/core/java/com/android/server/communal/CommunalManagerService.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.communal; - -import android.Manifest; -import android.annotation.RequiresPermission; -import android.app.communal.ICommunalManager; -import android.content.Context; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.SystemService; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * System service for handling Communal Mode state. - */ -public final class CommunalManagerService extends SystemService { - private final Context mContext; - private final AtomicBoolean mCommunalViewIsShowing = new AtomicBoolean(false); - private final BinderService mBinderService; - - public CommunalManagerService(Context context) { - super(context); - mContext = context; - mBinderService = new BinderService(); - } - - @VisibleForTesting - BinderService getBinderServiceInstance() { - return mBinderService; - } - - @Override - public void onStart() { - publishBinderService(Context.COMMUNAL_SERVICE, mBinderService); - } - - private final class BinderService extends ICommunalManager.Stub { - /** - * Sets whether or not we are in communal mode. - */ - @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE) - @Override - public void setCommunalViewShowing(boolean isShowing) { - mContext.enforceCallingPermission(Manifest.permission.WRITE_COMMUNAL_STATE, - Manifest.permission.WRITE_COMMUNAL_STATE - + "permission required to modify communal state."); - if (mCommunalViewIsShowing.get() == isShowing) { - return; - } - mCommunalViewIsShowing.set(isShowing); - } - } -} diff --git a/services/core/java/com/android/server/communal/OWNERS b/services/core/java/com/android/server/communal/OWNERS deleted file mode 100644 index b02883da854a..000000000000 --- a/services/core/java/com/android/server/communal/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -brycelee@google.com -justinkoh@google.com -lusilva@google.com -xilei@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/communal/TEST_MAPPING b/services/core/java/com/android/server/communal/TEST_MAPPING deleted file mode 100644 index 026e9bb91aee..000000000000 --- a/services/core/java/com/android/server/communal/TEST_MAPPING +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presubmit": [ - { - "name": "FrameworksMockingServicesTests", - "options": [ - { - "include-filter": "com.android.server.communal" - } - ] - } - ] -}
\ No newline at end of file diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 39fa3f200aab..135276e789c1 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -50,6 +50,12 @@ public abstract class VirtualDeviceManagerInternal { public abstract void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId); /** + * Returns the flags that should be added to any virtual displays created on this virtual + * device. + */ + public abstract int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice); + + /** * Returns true if the given {@code uid} is the owner of any virtual devices that are * currently active. */ diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java index 880dbf6421dd..fe002ce00d32 100644 --- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java +++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java @@ -25,6 +25,7 @@ import static android.provider.DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES; import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS; import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES; +import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import android.annotation.NonNull; @@ -157,17 +158,17 @@ public final class AppCompatOverridesService { Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove = new ArrayMap<>(); for (String packageName : packageNames) { - Long versionCode = getVersionCodeOrNull(packageName); - if (versionCode == null) { - // Package isn't installed yet. - continue; - } - Set<Long> changeIdsToSkip = packageToChangeIdsToSkip.getOrDefault(packageName, emptySet()); - Map<Long, PackageOverride> overridesToAdd = mOverridesParser.parsePackageOverrides( - properties.getString(packageName, /* defaultValue= */ ""), packageName, - versionCode, changeIdsToSkip); + + Map<Long, PackageOverride> overridesToAdd = emptyMap(); + Long versionCode = getVersionCodeOrNull(packageName); + if (versionCode != null) { + // Only if package installed add overrides, otherwise just remove. + overridesToAdd = mOverridesParser.parsePackageOverrides( + properties.getString(packageName, /* defaultValue= */ ""), packageName, + versionCode, changeIdsToSkip); + } if (!overridesToAdd.isEmpty()) { packageNameToOverridesToAdd.put(packageName, new CompatibilityOverrideConfig(overridesToAdd)); diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java index cc9efbc64c02..fce673765020 100644 --- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java @@ -199,6 +199,7 @@ public class MultipathPolicyTracker { private final NetworkTemplate mNetworkTemplate; private final UsageCallback mUsageCallback; private NetworkCapabilities mNetworkCapabilities; + private final NetworkStatsManager mStatsManager; public MultipathTracker(Network network, NetworkCapabilities nc) { this.network = network; @@ -238,6 +239,13 @@ public class MultipathPolicyTracker { updateMultipathBudget(); } }; + mStatsManager = mContext.getSystemService(NetworkStatsManager.class); + // Query stats from NetworkStatsService will trigger a poll by default. + // But since MultipathPolicyTracker listens NPMS events that triggered by + // stats updated event, and will query stats + // after the event. A polling -> updated -> query -> polling loop will be introduced + // if polls on open. Hence, set flag to false to prevent a polling loop. + mStatsManager.setPollOnOpen(false); updateMultipathBudget(); } @@ -262,8 +270,7 @@ public class MultipathPolicyTracker { private long getNetworkTotalBytes(long start, long end) { try { final android.app.usage.NetworkStats.Bucket ret = - mContext.getSystemService(NetworkStatsManager.class) - .querySummaryForDevice(mNetworkTemplate, start, end); + mStatsManager.querySummaryForDevice(mNetworkTemplate, start, end); return ret.getRxBytes() + ret.getTxBytes(); } catch (RuntimeException e) { Log.w(TAG, "Failed to get data usage: " + e); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 6a716cbc2816..066c263fa83b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1223,8 +1223,11 @@ public class Vpn { for (RouteInfo route : mConfig.routes) { lp.addRoute(route); InetAddress address = route.getDestination().getAddress(); - allowIPv4 |= address instanceof Inet4Address; - allowIPv6 |= address instanceof Inet6Address; + + if (route.getType() == RouteInfo.RTN_UNICAST) { + allowIPv4 |= address instanceof Inet4Address; + allowIPv6 |= address instanceof Inet6Address; + } } } diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index 7fe24ff1f069..78d55b92eb80 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -43,14 +43,14 @@ import java.util.Objects; */ public final class DeviceState { /** - * Flag that indicates sticky requests should be cancelled when this device state becomes the + * Flag that indicates override requests should be cancelled when this device state becomes the * base device state. */ - public static final int FLAG_CANCEL_STICKY_REQUESTS = 1 << 0; + public static final int FLAG_CANCEL_OVERRIDE_REQUESTS = 1 << 0; /** @hide */ @IntDef(prefix = {"FLAG_"}, flag = true, value = { - FLAG_CANCEL_STICKY_REQUESTS, + FLAG_CANCEL_OVERRIDE_REQUESTS, }) @Retention(RetentionPolicy.SOURCE) public @interface DeviceStateFlags {} @@ -114,4 +114,10 @@ public final class DeviceState { public int hashCode() { return Objects.hash(mIdentifier, mName, mFlags); } + + /** Checks if a specific flag is set + */ + public boolean hasFlag(int flagToCheckFor) { + return (mFlags & flagToCheckFor) == flagToCheckFor; + } } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 792feea01e27..709af91262c2 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.CONTROL_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; +import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED; @@ -273,14 +274,14 @@ public final class DeviceStateManagerService extends SystemService { synchronized (mLock) { final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked(); - // Whether or not at least one device state has the flag FLAG_CANCEL_STICKY_REQUESTS + // Whether or not at least one device state has the flag FLAG_CANCEL_OVERRIDE_REQUESTS // set. If set to true, the OverrideRequestController will be configured to allow sticky // requests. boolean hasTerminalDeviceState = false; mDeviceStates.clear(); for (int i = 0; i < supportedDeviceStates.length; i++) { DeviceState state = supportedDeviceStates[i]; - if ((state.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) { + if (state.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) { hasTerminalDeviceState = true; } mDeviceStates.put(state.getIdentifier(), state); @@ -345,8 +346,8 @@ public final class DeviceStateManagerService extends SystemService { } mBaseState = Optional.of(baseState); - if ((baseState.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) { - mOverrideRequestController.cancelStickyRequests(); + if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) { + mOverrideRequestController.cancelOverrideRequests(); } mOverrideRequestController.handleBaseStateChanged(); updatePendingStateLocked(); diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index 05c9eb2c5bbe..36cb4162accc 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -153,6 +153,16 @@ final class OverrideRequestController { } /** + * Cancels all override requests, this could be due to the device being put + * into a hardware state that declares the flag "FLAG_CANCEL_OVERRIDE_REQUESTS" + */ + void cancelOverrideRequests() { + mTmpRequestsToCancel.clear(); + mTmpRequestsToCancel.addAll(mRequests); + cancelRequestsLocked(mTmpRequestsToCancel); + } + + /** * Returns {@code true} if this controller is current managing a request with the specified * {@code token}, {@code false} otherwise. */ diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index c04032fede2f..f4c36c6b6ec2 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -54,6 +54,10 @@ class AutomaticBrightnessController { private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; + public static final int AUTO_BRIGHTNESS_ENABLED = 1; + public static final int AUTO_BRIGHTNESS_DISABLED = 2; + public static final int AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE = 3; + // How long the current sensor reading is assumed to be valid beyond the current time. // This provides a bit of prediction, as well as ensures that the weight for the last sample is // non-zero, which in turn ensures that the total weight is non-zero. @@ -70,13 +74,6 @@ class AutomaticBrightnessController { private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; private static final int MSG_RUN_UPDATE = 6; - // Length of the ambient light horizon used to calculate the long term estimate of ambient - // light. - private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000; - - // Length of the ambient light horizon used to calculate short-term estimate of ambient light. - private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; - // Callbacks for requesting updates to the display's power state private final Callbacks mCallbacks; @@ -121,8 +118,10 @@ class AutomaticBrightnessController { // and only then decide whether to change brightness. private final boolean mResetAmbientLuxAfterWarmUpConfig; - // Period of time in which to consider light samples in milliseconds. - private final int mAmbientLightHorizon; + // Period of time in which to consider light samples for a short/long-term estimate of ambient + // light in milliseconds. + private final int mAmbientLightHorizonLong; + private final int mAmbientLightHorizonShort; // The intercept used for the weighting calculation. This is used in order to keep all possible // weighting values positive. @@ -214,7 +213,9 @@ class AutomaticBrightnessController { private IActivityTaskManager mActivityTaskManager; private PackageManager mPackageManager; private Context mContext; + private int mState = AUTO_BRIGHTNESS_DISABLED; + private Clock mClock; private final Injector mInjector; AutomaticBrightnessController(Callbacks callbacks, Looper looper, @@ -226,14 +227,16 @@ class AutomaticBrightnessController { boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, Context context, HighBrightnessModeController hbmController, - BrightnessMappingStrategy idleModeBrightnessMapper) { + BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, + int ambientLightHorizonLong) { this(new Injector(), callbacks, looper, sensorManager, lightSensor, interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, context, - hbmController, idleModeBrightnessMapper + hbmController, idleModeBrightnessMapper, ambientLightHorizonShort, + ambientLightHorizonLong ); } @@ -247,8 +250,10 @@ class AutomaticBrightnessController { boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, Context context, HighBrightnessModeController hbmController, - BrightnessMappingStrategy idleModeBrightnessMapper) { + BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, + int ambientLightHorizonLong) { mInjector = injector; + mClock = injector.createClock(); mContext = context; mCallbacks = callbacks; mSensorManager = sensorManager; @@ -263,15 +268,16 @@ class AutomaticBrightnessController { mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; - mAmbientLightHorizon = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; - mWeightingIntercept = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; + mAmbientLightHorizonLong = ambientLightHorizonLong; + mAmbientLightHorizonShort = ambientLightHorizonShort; + mWeightingIntercept = ambientLightHorizonLong; mAmbientBrightnessThresholds = ambientBrightnessThresholds; mScreenBrightnessThresholds = screenBrightnessThresholds; mShortTermModelValid = true; mShortTermModelAnchor = -1; mHandler = new AutomaticBrightnessHandler(looper); mAmbientLightRingBuffer = - new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon); + new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock); if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { mLightSensor = lightSensor; @@ -331,10 +337,11 @@ class AutomaticBrightnessController { return mCurrentBrightnessMapper.getAutoBrightnessAdjustment(); } - public void configure(boolean enable, @Nullable BrightnessConfiguration configuration, + public void configure(int state, @Nullable BrightnessConfiguration configuration, float brightness, boolean userChangedBrightness, float adjustment, boolean userChangedAutoBrightnessAdjustment, int displayPolicy) { - mHbmController.setAutoBrightnessEnabled(enable); + mState = state; + mHbmController.setAutoBrightnessEnabled(mState); // While dozing, the application processor may be suspended which will prevent us from // receiving new information from the light sensor. On some devices, we may be able to // switch to a wake-up light sensor instead but for now we will simply disable the sensor @@ -346,6 +353,7 @@ class AutomaticBrightnessController { if (userChangedAutoBrightnessAdjustment) { changed |= setAutoBrightnessAdjustment(adjustment); } + final boolean enable = mState == AUTO_BRIGHTNESS_ENABLED; if (userChangedBrightness && enable) { // Update the brightness curve with the new user control point. It's critical this // happens after we update the autobrightness adjustment since it may reset it. @@ -390,6 +398,11 @@ class AutomaticBrightnessController { mHandler.sendEmptyMessage(MSG_RUN_UPDATE); } + @VisibleForTesting + float getAmbientLux() { + return mAmbientLux; + } + private boolean setDisplayPolicy(int policy) { if (mDisplayPolicy == policy) { return false; @@ -459,6 +472,7 @@ class AutomaticBrightnessController { public void dump(PrintWriter pw) { pw.println(); pw.println("Automatic Brightness Controller Configuration:"); + pw.println(" mState=" + configStateToString(mState)); pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mDozeScaleFactor=" + mDozeScaleFactor); @@ -468,7 +482,8 @@ class AutomaticBrightnessController { pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); - pw.println(" mAmbientLightHorizon=" + mAmbientLightHorizon); + pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); + pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); pw.println(" mWeightingIntercept=" + mWeightingIntercept); pw.println(); @@ -520,11 +535,24 @@ class AutomaticBrightnessController { mScreenBrightnessThresholds.dump(pw); } + private String configStateToString(int state) { + switch (state) { + case AUTO_BRIGHTNESS_ENABLED: + return "AUTO_BRIGHTNESS_ENABLED"; + case AUTO_BRIGHTNESS_DISABLED: + return "AUTO_BRIGHTNESS_DISABLED"; + case AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE: + return "AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE"; + default: + return String.valueOf(state); + } + } + private boolean setLightSensorEnabled(boolean enable) { if (enable) { if (!mLightSensorEnabled) { mLightSensorEnabled = true; - mLightSensorEnableTime = SystemClock.uptimeMillis(); + mLightSensorEnableTime = mClock.uptimeMillis(); mCurrentLightSensorRate = mInitialLightSensorRate; registerForegroundAppUpdater(); mSensorManager.registerListener(mLightSensorListener, mLightSensor, @@ -559,7 +587,7 @@ class AutomaticBrightnessController { private void applyLightSensorMeasurement(long time, float lux) { mRecentLightSamples++; - mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon); + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); mAmbientLightRingBuffer.push(time, lux); // Remember this sample value. @@ -700,8 +728,8 @@ class AutomaticBrightnessController { } private void updateAmbientLux() { - long time = SystemClock.uptimeMillis(); - mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon); + long time = mClock.uptimeMillis(); + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); updateAmbientLux(time); } @@ -721,7 +749,7 @@ class AutomaticBrightnessController { timeWhenSensorWarmedUp); return; } - setAmbientLux(calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS)); + setAmbientLux(calculateAmbientLux(time, mAmbientLightHorizonShort)); mAmbientLuxValid = true; if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Initializing: " + @@ -741,8 +769,8 @@ class AutomaticBrightnessController { // proposed ambient light value since the slow value might be sufficiently far enough away // from the fast value to cause a recalculation while its actually just converging on // the fast value still. - float slowAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_LONG_HORIZON_MILLIS); - float fastAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS); + float slowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong); + float fastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort); if ((slowAmbientLux >= mAmbientBrighteningThreshold && fastAmbientLux >= mAmbientBrighteningThreshold @@ -1023,7 +1051,7 @@ class AutomaticBrightnessController { @Override public void onSensorChanged(SensorEvent event) { if (mLightSensorEnabled) { - final long time = SystemClock.uptimeMillis(); + final long time = mClock.uptimeMillis(); final float lux = event.values[0]; handleLightSensorEvent(time, lux); } @@ -1049,6 +1077,15 @@ class AutomaticBrightnessController { void updateBrightness(); } + /** Functional interface for providing time. */ + @VisibleForTesting + interface Clock { + /** + * Returns current time in milliseconds since boot, not counting time spent in deep sleep. + */ + long uptimeMillis(); + } + /** * A ring buffer of ambient light measurements sorted by time. * @@ -1068,14 +1105,16 @@ class AutomaticBrightnessController { private int mStart; private int mEnd; private int mCount; + Clock mClock; - public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) { + public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon, Clock clock) { if (lightSensorRate <= 0) { throw new IllegalArgumentException("lightSensorRate must be above 0"); } mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate); mRingLux = new float[mCapacity]; mRingTime = new long[mCapacity]; + mClock = clock; } public float getLux(int index) { @@ -1160,7 +1199,7 @@ class AutomaticBrightnessController { StringBuilder buf = new StringBuilder(); buf.append('['); for (int i = 0; i < mCount; i++) { - final long next = i + 1 < mCount ? getTime(i + 1) : SystemClock.uptimeMillis(); + final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis(); if (i != 0) { buf.append(", "); } @@ -1189,5 +1228,9 @@ class AutomaticBrightnessController { public Handler getBackgroundThreadHandler() { return BackgroundThread.getHandler(); } + + Clock createClock() { + return SystemClock::uptimeMillis; + } } } diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index def9685b1c1f..d0ce9ef47c35 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -37,6 +37,8 @@ import java.io.PrintWriter; * </p> */ abstract class DisplayDevice { + private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build(); + private final DisplayAdapter mDisplayAdapter; private final IBinder mDisplayToken; private final String mUniqueId; @@ -213,6 +215,13 @@ abstract class DisplayDevice { public void setUserPreferredDisplayModeLocked(Display.Mode mode) { } /** + * Returns the user preferred display mode. + */ + public Display.Mode getUserPreferredDisplayModeLocked() { + return EMPTY_DISPLAY_MODE; + } + + /** * Sets the requested color mode. */ public void setRequestedColorModeLocked(int colorMode) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a9e1647446cb..3df2422071b8 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -52,6 +52,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -86,6 +87,13 @@ public class DisplayDeviceConfig { private static final float NITS_INVALID = -1; + // Length of the ambient light horizon used to calculate the long term estimate of ambient + // light. + private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000; + + // Length of the ambient light horizon used to calculate short-term estimate of ambient light. + private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; + private final Context mContext; // The details of the ambient light sensor associated with this display. @@ -120,6 +128,8 @@ public class DisplayDeviceConfig { private float mBrightnessRampFastIncrease = Float.NaN; private float mBrightnessRampSlowDecrease = Float.NaN; private float mBrightnessRampSlowIncrease = Float.NaN; + private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; + private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS; private Spline mBrightnessToBacklightSpline; private Spline mBacklightToBrightnessSpline; private Spline mBacklightToNitsSpline; @@ -346,6 +356,14 @@ public class DisplayDeviceConfig { return mBrightnessRampSlowIncrease; } + public int getAmbientHorizonLong() { + return mAmbientHorizonLong; + } + + public int getAmbientHorizonShort() { + return mAmbientHorizonShort; + } + SensorData getAmbientLightSensor() { return mAmbientLightSensor; } @@ -405,6 +423,8 @@ public class DisplayDeviceConfig { + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease + + ", mAmbientHorizonLong=" + mAmbientHorizonLong + + ", mAmbientHorizonShort=" + mAmbientHorizonShort + ", mAmbientLightSensor=" + mAmbientLightSensor + ", mProximitySensor=" + mProximitySensor + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray()) @@ -461,6 +481,7 @@ public class DisplayDeviceConfig { loadBrightnessRamps(config); loadAmbientLightSensorFromDdc(config); loadProxSensorFromDdc(config); + loadAmbientHorizonFromDdc(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } @@ -869,6 +890,17 @@ public class DisplayDeviceConfig { } } + private void loadAmbientHorizonFromDdc(DisplayConfiguration config) { + final BigInteger configLongHorizon = config.getAmbientLightHorizonLong(); + if (configLongHorizon != null) { + mAmbientHorizonLong = configLongHorizon.intValue(); + } + final BigInteger configShortHorizon = config.getAmbientLightHorizonShort(); + if (configShortHorizon != null) { + mAmbientHorizonShort = configShortHorizon.intValue(); + } + } + static class SensorData { public String type; public String name; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index be889e47db01..3feffc6ae7df 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -873,11 +873,11 @@ public final class DisplayManagerService extends SystemService { private void updateUserPreferredDisplayModeSettingsLocked() { final float refreshRate = Settings.Global.getFloat(mContext.getContentResolver(), - Settings.Global.USER_PREFERRED_REFRESH_RATE, 0.0f); + Settings.Global.USER_PREFERRED_REFRESH_RATE, Display.INVALID_DISPLAY_REFRESH_RATE); final int height = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, -1); + Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, Display.INVALID_DISPLAY_HEIGHT); final int width = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, -1); + Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, Display.INVALID_DISPLAY_WIDTH); Display.Mode mode = new Display.Mode(width, height, refreshRate); mUserPreferredMode = isResolutionAndRefreshRateValid(mode) ? mode : null; } @@ -1250,6 +1250,14 @@ public final class DisplayManagerService extends SystemService { } final Surface surface = virtualDisplayConfig.getSurface(); int flags = virtualDisplayConfig.getFlags(); + if (virtualDevice != null) { + final VirtualDeviceManagerInternal vdm = + getLocalService(VirtualDeviceManagerInternal.class); + if (!vdm.isValidVirtualDevice(virtualDevice)) { + throw new SecurityException("Invalid virtual device"); + } + flags |= vdm.getBaseVirtualDisplayFlags(virtualDevice); + } if (surface != null && surface.isSingleBuffered()) { throw new IllegalArgumentException("Surface can't be single-buffered"); @@ -1282,14 +1290,6 @@ public final class DisplayManagerService extends SystemService { } } - if (virtualDevice != null) { - final VirtualDeviceManagerInternal vdm = - getLocalService(VirtualDeviceManagerInternal.class); - if (!vdm.isValidVirtualDevice(virtualDevice)) { - throw new SecurityException("Invalid virtual device"); - } - } - if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { if (!canProjectVideo(projection)) { @@ -1560,8 +1560,11 @@ public final class DisplayManagerService extends SystemService { } if (mUserPreferredMode != null) { device.setUserPreferredDisplayModeLocked(mUserPreferredMode); + } else { + configurePreferredDisplayModeLocked(display); } addDisplayPowerControllerLocked(display); + mDisplayStates.append(displayId, Display.STATE_UNKNOWN); final float brightnessDefault = display.getDisplayInfoLocked().brightnessDefault; @@ -1688,6 +1691,24 @@ public final class DisplayManagerService extends SystemService { } } + private void configurePreferredDisplayModeLocked(LogicalDisplay display) { + final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); + final Point userPreferredResolution = + mPersistentDataStore.getUserPreferredResolution(device); + final float refreshRate = mPersistentDataStore.getUserPreferredRefreshRate(device); + if (userPreferredResolution == null && Float.isNaN(refreshRate)) { + return; + } + Display.Mode.Builder modeBuilder = new Display.Mode.Builder(); + if (userPreferredResolution != null) { + modeBuilder.setResolution(userPreferredResolution.x, userPreferredResolution.y); + } + if (!Float.isNaN(refreshRate)) { + modeBuilder.setRefreshRate(refreshRate); + } + device.setUserPreferredDisplayModeLocked(modeBuilder.build()); + } + // If we've never recorded stable device stats for this device before and they aren't // explicitly configured, go ahead and record the stable device stats now based on the status // of the default display at first boot. @@ -1731,36 +1752,79 @@ public final class DisplayManagerService extends SystemService { return mWideColorSpace.getId(); } - void setUserPreferredDisplayModeInternal(Display.Mode mode) { + void setUserPreferredDisplayModeInternal(int displayId, Display.Mode mode) { synchronized (mSyncRoot) { - if (Objects.equals(mUserPreferredMode, mode)) { + if (Objects.equals(mUserPreferredMode, mode) && displayId == Display.INVALID_DISPLAY) { return; } - if (mode != null && !isResolutionAndRefreshRateValid(mode)) { + if (mode != null && !isResolutionAndRefreshRateValid(mode) + && displayId == Display.INVALID_DISPLAY) { throw new IllegalArgumentException("width, height and refresh rate of mode should " - + "be greater than 0"); + + "be greater than 0 when setting the global user preferred display mode."); } - mUserPreferredMode = mode; - final int resolutionHeight = mode == null ? -1 : mode.getPhysicalHeight(); - final int resolutionWidth = mode == null ? -1 : mode.getPhysicalWidth(); - final float refreshRate = mode == null ? 0.0f : mode.getRefreshRate(); - Settings.Global.putFloat(mContext.getContentResolver(), - Settings.Global.USER_PREFERRED_REFRESH_RATE, refreshRate); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, resolutionHeight); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth); - mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> { - device.setUserPreferredDisplayModeLocked(mode); - }); + final int resolutionHeight = mode == null ? Display.INVALID_DISPLAY_HEIGHT + : mode.getPhysicalHeight(); + final int resolutionWidth = mode == null ? Display.INVALID_DISPLAY_WIDTH + : mode.getPhysicalWidth(); + final float refreshRate = mode == null ? Display.INVALID_DISPLAY_REFRESH_RATE + : mode.getRefreshRate(); + + storeModeInPersistentDataStoreLocked( + displayId, resolutionWidth, resolutionHeight, refreshRate); + if (displayId != Display.INVALID_DISPLAY) { + setUserPreferredModeForDisplayLocked(displayId, mode); + } else { + mUserPreferredMode = mode; + storeModeInGlobalSettingsLocked( + resolutionWidth, resolutionHeight, refreshRate, mode); + } + } + } + + private void storeModeInPersistentDataStoreLocked(int displayId, int resolutionWidth, + int resolutionHeight, float refreshRate) { + DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId); + if (displayDevice == null) { + return; + } + mPersistentDataStore.setUserPreferredResolution( + displayDevice, resolutionWidth, resolutionHeight); + mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate); + } + + private void setUserPreferredModeForDisplayLocked(int displayId, Display.Mode mode) { + DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId); + if (displayDevice == null) { + return; } + displayDevice.setUserPreferredDisplayModeLocked(mode); } - private Display.Mode getUserPreferredDisplayModeInternal() { + private void storeModeInGlobalSettingsLocked( + int resolutionWidth, int resolutionHeight, float refreshRate, Display.Mode mode) { + Settings.Global.putFloat(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_REFRESH_RATE, refreshRate); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, resolutionHeight); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth); + mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> { + device.setUserPreferredDisplayModeLocked(mode); + }); + } + + Display.Mode getUserPreferredDisplayModeInternal(int displayId) { synchronized (mSyncRoot) { - return mUserPreferredMode; + if (displayId == Display.INVALID_DISPLAY) { + return mUserPreferredMode; + } + DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId); + if (displayDevice == null) { + return null; + } + return displayDevice.getUserPreferredDisplayModeLocked(); } } @@ -2373,7 +2437,7 @@ public final class DisplayManagerService extends SystemService { pw.println(" mMinimumBrightnessCurve=" + mMinimumBrightnessCurve); if (mUserPreferredMode != null) { - pw.println(mUserPreferredMode.toString()); + pw.println(mUserPreferredMode); } pw.println(); @@ -3379,23 +3443,23 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call - public void setUserPreferredDisplayMode(Display.Mode mode) { + public void setUserPreferredDisplayMode(int displayId, Display.Mode mode) { mContext.enforceCallingOrSelfPermission( Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE, "Permission required to set the user preferred display mode."); final long token = Binder.clearCallingIdentity(); try { - setUserPreferredDisplayModeInternal(mode); + setUserPreferredDisplayModeInternal(displayId, mode); } finally { Binder.restoreCallingIdentity(token); } } @Override // Binder call - public Display.Mode getUserPreferredDisplayMode() { + public Display.Mode getUserPreferredDisplayMode(int displayId) { final long token = Binder.clearCallingIdentity(); try { - return getUserPreferredDisplayModeInternal(); + return getUserPreferredDisplayModeInternal(displayId); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 9a7ddcb2ff91..a9a1f08c140a 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -113,13 +113,18 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" constrain-launcher-metrics [true|false]"); pw.println(" Sets if Display#getRealSize and getRealMetrics should be constrained for "); pw.println(" Launcher."); - pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE"); + pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE " + + "DISPLAY_ID (optional)"); pw.println(" Sets the user preferred display mode which has fields WIDTH, HEIGHT and " - + "REFRESH-RATE"); - pw.println(" clear-user-preferred-display-mode"); - pw.println(" Clears the user preferred display mode"); - pw.println(" get-user-preferred-display-mode"); - pw.println(" Returns the user preferred display mode or null id no mode is set by user"); + + "REFRESH-RATE. If DISPLAY_ID is passed, the mode change is applied to display" + + "with id = DISPLAY_ID, else mode change is applied globally."); + pw.println(" clear-user-preferred-display-mode DISPLAY_ID (optional)"); + pw.println(" Clears the user preferred display mode. If DISPLAY_ID is passed, the mode" + + " is cleared for display with id = DISPLAY_ID, else mode is cleared globally."); + pw.println(" get-user-preferred-display-mode DISPLAY_ID (optional)"); + pw.println(" Returns the user preferred display mode or null if no mode is set by user." + + "If DISPLAY_ID is passed, the mode for display with id = DISPLAY_ID is " + + "returned, else global display mode is returned."); pw.println(" set-match-content-frame-rate-pref PREFERENCE"); pw.println(" Sets the match content frame rate preference as PREFERENCE "); pw.println(" get-match-content-frame-rate-pref"); @@ -235,28 +240,54 @@ class DisplayManagerShellCommand extends ShellCommand { getErrPrintWriter().println("Error: invalid format of width, height or refresh rate"); return 1; } - if (width < 0 || height < 0 || refreshRate <= 0.0f) { - getErrPrintWriter().println("Error: invalid value of width, height or refresh rate"); + if ((width < 0 || height < 0) && refreshRate <= 0.0f) { + getErrPrintWriter().println("Error: invalid value of resolution (width, height)" + + " and refresh rate"); return 1; } - final Context context = mService.getContext(); - final DisplayManager dm = context.getSystemService(DisplayManager.class); - dm.setUserPreferredDisplayMode(new Display.Mode(width, height, refreshRate)); + final String displayIdText = getNextArg(); + int displayId = Display.INVALID_DISPLAY; + if (displayIdText != null) { + try { + displayId = Integer.parseInt(displayIdText); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: invalid format of display ID"); + return 1; + } + } + mService.setUserPreferredDisplayModeInternal( + displayId, new Display.Mode(width, height, refreshRate)); return 0; } private int clearUserPreferredDisplayMode() { - final Context context = mService.getContext(); - final DisplayManager dm = context.getSystemService(DisplayManager.class); - dm.clearUserPreferredDisplayMode(); + final String displayIdText = getNextArg(); + int displayId = Display.INVALID_DISPLAY; + if (displayIdText != null) { + try { + displayId = Integer.parseInt(displayIdText); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: invalid format of display ID"); + return 1; + } + } + mService.setUserPreferredDisplayModeInternal(displayId, null); return 0; } private int getUserPreferredDisplayMode() { - final Context context = mService.getContext(); - final DisplayManager dm = context.getSystemService(DisplayManager.class); - final Display.Mode mode = dm.getUserPreferredDisplayMode(); + final String displayIdText = getNextArg(); + int displayId = Display.INVALID_DISPLAY; + if (displayIdText != null) { + try { + displayId = Integer.parseInt(displayIdText); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: invalid format of display ID"); + return 1; + } + } + final Display.Mode mode = mService.getUserPreferredDisplayModeInternal(displayId); if (mode == null) { getOutPrintWriter().println("User preferred display mode: null"); return 0; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index c4d02c7abafb..34f915e41cd7 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -62,6 +62,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; @@ -127,6 +128,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_STOP = 9; private static final int MSG_UPDATE_BRIGHTNESS = 10; private static final int MSG_UPDATE_RBC = 11; + private static final int MSG_STATSD_HBM_BRIGHTNESS = 12; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -136,6 +138,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0; private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250; + private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500; + // Trigger proximity if distance is less than 5 cm. private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f; @@ -356,6 +360,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private float mBrightnessRampRateSlowDecrease; private float mBrightnessRampRateSlowIncrease; + // Report HBM brightness change to StatsD + private int mDisplayStatsId; + private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN; // Whether or not to skip the initial brightness ramps into STATE_ON. private final boolean mSkipScreenOnBrightnessRamp; @@ -466,6 +473,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call TAG = "DisplayPowerController[" + mDisplayId + "]"; mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + mDisplayStatsId = mUniqueDisplayId.hashCode(); mHandler = new DisplayControllerHandler(handler.getLooper()); if (mDisplayId == Display.DEFAULT_DISPLAY) { @@ -763,6 +771,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mDisplayDevice != device) { mDisplayDevice = device; mUniqueDisplayId = uniqueId; + mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; loadFromDisplayDeviceConfig(token, info); updatePowerState(); @@ -816,7 +825,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call loadNitsRange(mContext.getResources()); setUpAutoBrightness(mContext.getResources(), mHandler); reloadReduceBrightColours(); - mHbmController.resetHbmData(info.width, info.height, token, + mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, mDisplayDeviceConfig.getHighBrightnessModeData()); } @@ -948,7 +957,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, mContext, - mHbmController, mIdleModeBrightnessMapper); + mHbmController, mIdleModeBrightnessMapper, + mDisplayDeviceConfig.getAmbientHorizonShort(), + mDisplayDeviceConfig.getAmbientHorizonLong()); } else { mUseSoftwareAutoBrightnessConfig = false; } @@ -1004,7 +1015,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } }; - private final RampAnimator.Listener mRampAnimatorListener = this::sendUpdatePowerState; + private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() { + @Override + public void onAnimationEnd() { + sendUpdatePowerState(); + + final float brightness = mPowerState.getScreenBrightness(); + reportStats(brightness); + } + }; /** Clean up all resources that are accessed via the {@link #mHandler} thread. */ private void cleanupHandlerThreadAfterStop() { @@ -1179,6 +1198,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) && Float.isNaN(brightnessState) && mAutomaticBrightnessController != null; + final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness + && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze); + final int autoBrightnessState = autoBrightnessEnabled + ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED + : autoBrightnessDisabledDueToDisplayOff + ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE + : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED; final boolean userSetBrightnessChanged = updateUserSetScreenBrightness(); @@ -1229,7 +1255,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Configure auto-brightness. if (mAutomaticBrightnessController != null) { hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints(); - mAutomaticBrightnessController.configure(autoBrightnessEnabled, + mAutomaticBrightnessController.configure(autoBrightnessState, mBrightnessConfiguration, mLastUserSetScreenBrightness, userSetBrightnessChanged, autoBrightnessAdjustment, @@ -1626,11 +1652,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); final IBinder displayToken = mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked(); + final String displayUniqueId = + mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); final DisplayDeviceConfig.HighBrightnessModeData hbmData = ddConfig != null ? ddConfig.getHighBrightnessModeData() : null; final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken, - PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, + displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, () -> { sendUpdatePowerStateLocked(); postBrightnessChangeRunnable(); @@ -2462,6 +2490,42 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + private void reportStats(float brightness) { + float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX; + synchronized(mCachedBrightnessInfo) { + if (mCachedBrightnessInfo.hbmTransitionPoint == null) { + return; + } + hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value; + } + + final boolean aboveTransition = brightness > hbmTransitionPoint; + final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint; + + if (aboveTransition || oldAboveTransition) { + mLastStatsBrightness = brightness; + mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS); + if (aboveTransition != oldAboveTransition) { + // report immediately + logHbmBrightnessStats(brightness, mDisplayStatsId); + } else { + // delay for rate limiting + Message msg = mHandler.obtainMessage(); + msg.what = MSG_STATSD_HBM_BRIGHTNESS; + msg.arg1 = Float.floatToIntBits(brightness); + msg.arg2 = mDisplayStatsId; + mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS); + } + } + } + + private final void logHbmBrightnessStats(float brightness, int displayStatsId) { + synchronized (mHandler) { + FrameworkStatsLog.write( + FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness); + } + } + private final class DisplayControllerHandler extends Handler { public DisplayControllerHandler(Looper looper) { super(looper, null, true /*async*/); @@ -2526,6 +2590,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final int justActivated = msg.arg2; handleRbcChanged(strengthChanged == 1, justActivated == 1); break; + + case MSG_STATSD_HBM_BRIGHTNESS: + logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2); + break; } } } diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 1e1cfeb5b9dd..b3be894b9510 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -37,6 +37,7 @@ import android.util.TimeUtils; import android.view.SurfaceControlHdrLayerInfoListener; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; import com.android.server.display.DisplayManagerService.Clock; @@ -80,6 +81,7 @@ class HighBrightnessModeController { private boolean mIsInAllowedAmbientRange = false; private boolean mIsTimeAvailable = false; private boolean mIsAutoBrightnessEnabled = false; + private boolean mIsAutoBrightnessOffByState = false; private float mBrightness; private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; private boolean mIsHdrLayerPresent = false; @@ -88,6 +90,8 @@ class HighBrightnessModeController { private int mWidth; private int mHeight; private float mAmbientLux; + private int mDisplayStatsId; + private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; /** * If HBM is currently running, this is the start time for the current HBM session. @@ -102,15 +106,15 @@ class HighBrightnessModeController { private LinkedList<HbmEvent> mEvents = new LinkedList<>(); HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, - float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, - Runnable hbmChangeCallback, Context context) { - this(new Injector(), handler, width, height, displayToken, brightnessMin, brightnessMax, - hbmData, hbmChangeCallback, context); + String displayUniqueId, float brightnessMin, float brightnessMax, + HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context) { + this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin, + brightnessMax, hbmData, hbmChangeCallback, context); } @VisibleForTesting HighBrightnessModeController(Injector injector, Handler handler, int width, int height, - IBinder displayToken, float brightnessMin, float brightnessMax, + IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context) { mInjector = injector; @@ -126,10 +130,13 @@ class HighBrightnessModeController { mRecalcRunnable = this::recalculateTimeAllowance; mHdrListener = new HdrListener(); - resetHbmData(width, height, displayToken, hbmData); + resetHbmData(width, height, displayToken, displayUniqueId, hbmData); } - void setAutoBrightnessEnabled(boolean isEnabled) { + void setAutoBrightnessEnabled(int state) { + final boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; + mIsAutoBrightnessOffByState = + state == AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE; if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) { return; } @@ -231,10 +238,12 @@ class HighBrightnessModeController { mSettingsObserver.stopObserving(); } - void resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData) { + void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, + HighBrightnessModeData hbmData) { mWidth = width; mHeight = height; mHbmData = hbmData; + mDisplayStatsId = displayUniqueId.hashCode(); unregisterHdrListener(); mSkinThermalStatusObserver.stopObserving(); @@ -270,11 +279,13 @@ class HighBrightnessModeController { pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode) + (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR ? "(" + getHdrBrightnessValue() + ")" : "")); + pw.println(" mHbmStatsState=" + hbmStatsStateToString(mHbmStatsState)); pw.println(" mHbmData=" + mHbmData); pw.println(" mAmbientLux=" + mAmbientLux + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)")); pw.println(" mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange); pw.println(" mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled); + pw.println(" mIsAutoBrightnessOffByState=" + mIsAutoBrightnessOffByState); pw.println(" mIsHdrLayerPresent=" + mIsHdrLayerPresent); pw.println(" mBrightnessMin=" + mBrightnessMin); pw.println(" mBrightnessMax=" + mBrightnessMax); @@ -435,12 +446,71 @@ class HighBrightnessModeController { private void updateHbmMode() { int newHbmMode = calculateHighBrightnessMode(); + updateHbmStats(mHbmMode, newHbmMode); if (mHbmMode != newHbmMode) { mHbmMode = newHbmMode; mHbmChangeCallback.run(); } } + private void updateHbmStats(int mode, int newMode) { + int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; + if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + && getHdrBrightnessValue() > mHbmData.transitionPoint) { + state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR; + } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) { + state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT; + } + if (state == mHbmStatsState) { + return; + } + mHbmStatsState = state; + + int reason = + FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN; + boolean oldHbmSv = (mode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT); + boolean newHbmSv = (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT); + if (oldHbmSv && !newHbmSv) { + // If more than one conditions are flipped and turn off HBM sunlight + // visibility, only one condition will be reported to make it simple. + if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) { + reason = FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF; + } else if (!mIsAutoBrightnessEnabled) { + reason = FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_AUTOBRIGHTNESS_OFF; + } else if (!mIsInAllowedAmbientRange) { + reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP; + } else if (!mIsTimeAvailable) { + reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT; + } else if (!mIsThermalStatusWithinLimit) { + reason = FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT; + } else if (mIsHdrLayerPresent) { + reason = FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING; + } else if (mIsBlockedByLowPowerMode) { + reason = FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON; + } + } + + mInjector.reportHbmStateChange(mDisplayStatsId, state, reason); + } + + private String hbmStatsStateToString(int hbmStatsState) { + switch (hbmStatsState) { + case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF: + return "HBM_OFF"; + case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR: + return "HBM_ON_HDR"; + case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT: + return "HBM_ON_SUNLIGHT"; + default: + return String.valueOf(hbmStatsState); + } + } + private int calculateHighBrightnessMode() { if (!deviceSupportsHbm()) { return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; @@ -642,5 +712,10 @@ class HighBrightnessModeController { return IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } + + public void reportHbmStateChange(int display, int state, int reason) { + FrameworkStatsLog.write( + FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason); + } } } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 300f59ee1dd4..84de8229f37b 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -847,6 +847,10 @@ final class LocalDisplayAdapter extends DisplayAdapter { public void setUserPreferredDisplayModeLocked(Display.Mode mode) { final int oldModeId = getPreferredModeId(); mUserPreferredMode = mode; + if (mode != null && (mode.isRefreshRateSet() ^ mode.isResolutionSet())) { + mUserPreferredMode = findMode(mode.getPhysicalWidth(), + mode.getPhysicalHeight(), mode.getRefreshRate()); + } mUserPreferredModeId = findUserPreferredModeIdLocked(mode); if (oldModeId != getPreferredModeId()) { @@ -855,6 +859,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { } @Override + public Display.Mode getUserPreferredDisplayModeLocked() { + return mUserPreferredMode; + } + + @Override public void setRequestedColorModeLocked(int colorMode) { requestColorModeLocked(colorMode); } @@ -1062,6 +1071,18 @@ final class LocalDisplayAdapter extends DisplayAdapter { return matchingModeId; } + // Returns a mode with resolution (width, height) and/or refreshRate. If any one of the + // resolution or refresh-rate is valid, a mode having the valid parameters is returned. + private Display.Mode findMode(int width, int height, float refreshRate) { + for (int i = 0; i < mSupportedModes.size(); i++) { + Display.Mode supportedMode = mSupportedModes.valueAt(i).mMode; + if (supportedMode.matchesIfValid(width, height, refreshRate)) { + return supportedMode; + } + } + return null; + } + private int findUserPreferredModeIdLocked(Display.Mode userPreferredMode) { if (userPreferredMode != null) { for (int i = 0; i < mSupportedModes.size(); i++) { diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index 4b0d43b3d1d4..2eba080e369e 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -291,6 +291,54 @@ final class PersistentDataStore { return false; } + public boolean setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate) { + final String displayDeviceUniqueId = displayDevice.getUniqueId(); + if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) { + return false; + } + DisplayState state = getDisplayState(displayDevice.getUniqueId(), true); + if (state.setRefreshRate(refreshRate)) { + setDirty(); + return true; + } + return false; + } + + public float getUserPreferredRefreshRate(DisplayDevice device) { + if (device == null || !device.hasStableUniqueId()) { + return Float.NaN; + } + final DisplayState state = getDisplayState(device.getUniqueId(), false); + if (state == null) { + return Float.NaN; + } + return state.getRefreshRate(); + } + + public boolean setUserPreferredResolution(DisplayDevice displayDevice, int width, int height) { + final String displayDeviceUniqueId = displayDevice.getUniqueId(); + if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) { + return false; + } + DisplayState state = getDisplayState(displayDevice.getUniqueId(), true); + if (state.setResolution(width, height)) { + setDirty(); + return true; + } + return false; + } + + public Point getUserPreferredResolution(DisplayDevice displayDevice) { + if (displayDevice == null || !displayDevice.hasStableUniqueId()) { + return null; + } + final DisplayState state = getDisplayState(displayDevice.getUniqueId(), false); + if (state == null) { + return null; + } + return state.getResolution(); + } + public Point getStableDisplaySize() { loadIfNeeded(); return mStableDeviceValues.getDisplaySize(); @@ -536,6 +584,9 @@ final class PersistentDataStore { private static final class DisplayState { private int mColorMode; private float mBrightness; + private int mWidth; + private int mHeight; + private float mRefreshRate; // Brightness configuration by user private BrightnessConfigurations mDisplayBrightnessConfigurations = @@ -576,6 +627,31 @@ final class PersistentDataStore { return mDisplayBrightnessConfigurations.mConfigurations.get(userSerial); } + public boolean setResolution(int width, int height) { + if (width == mWidth && height == mHeight) { + return false; + } + mWidth = width; + mHeight = height; + return true; + } + + public Point getResolution() { + return new Point(mWidth, mHeight); + } + + public boolean setRefreshRate(float refreshRate) { + if (refreshRate == mRefreshRate) { + return false; + } + mRefreshRate = refreshRate; + return true; + } + + public float getRefreshRate() { + return mRefreshRate; + } + public void loadFromXml(TypedXmlPullParser parser) throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 58308d8f1343..751f2db99528 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -81,7 +81,7 @@ final class Constants { public static final int ADDR_BROADCAST = 15; /** Logical address used to indicate it is not initialized or invalid. */ - public static final int ADDR_INVALID = -1; + public static final int ADDR_INVALID = HdmiDeviceInfo.ADDR_INVALID; /** Logical address used to indicate the source comes from internal device. */ public static final int ADDR_INTERNAL = HdmiDeviceInfo.ADDR_INTERNAL; @@ -199,6 +199,7 @@ final class Constants { static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72; static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A; static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D; + static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73; static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E; static final int MESSAGE_ROUTING_CHANGE = 0x80; static final int MESSAGE_ROUTING_INFORMATION = 0x81; @@ -243,7 +244,7 @@ final class Constants { static final int MESSAGE_CDC_MESSAGE = 0xF8; static final int MESSAGE_ABORT = 0xFF; - static final int UNKNOWN_VENDOR_ID = 0xFFFFFF; + static final int VENDOR_ID_UNKNOWN = HdmiDeviceInfo.VENDOR_ID_UNKNOWN; static final int TRUE = 1; static final int FALSE = 0; @@ -389,6 +390,17 @@ final class Constants { static final int UNKNOWN_VOLUME = -1; + // This constant is used in two operands in the CEC spec. + // + // CEC 1.4: [Audio Volume Status] (part of [Audio Status]) - operand for <Report Audio Status> + // Indicates that the current audio volume status is unknown. + // + // CEC 2.1a: [Audio Volume Level] - operand for <Set Audio Volume Level> + // Part of the Absolute Volume Control feature. Indicates that no change shall be made to the + // volume level of the recipient. This allows <Set Audio Volume Level> to be sent to determine + // whether the recipient supports Absolute Volume Control. + static final int AUDIO_VOLUME_STATUS_UNKNOWN = 0x7F; + // States of property PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON // to decide if turn on the system audio control when power on the device @IntDef({ diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index 8980de12a31f..e827866368fe 100755 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -77,7 +77,7 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; private int mPortId = Constants.INVALID_PORT_ID; - private int mVendorId = Constants.UNKNOWN_VENDOR_ID; + private int mVendorId = Constants.VENDOR_ID_UNKNOWN; private int mPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; private String mDisplayName = ""; private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE; @@ -87,8 +87,15 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { } private HdmiDeviceInfo toHdmiDeviceInfo() { - return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType, - mVendorId, mDisplayName, mPowerStatus); + return HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mLogicalAddress) + .setPhysicalAddress(mPhysicalAddress) + .setPortId(mPortId) + .setVendorId(mVendorId) + .setDeviceType(mDeviceType) + .setDisplayName(mDisplayName) + .setDevicePowerStatus(mPowerStatus) + .build(); } } diff --git a/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java b/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java new file mode 100644 index 000000000000..45425657c0f6 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java @@ -0,0 +1,79 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; +import android.hardware.tv.cec.V1_0.SendMessageResult; + +/** + * Sends <Give Features> to a target device. This action succeeds if the device responds with + * <Report Features> within {@link HdmiConfig.TIMEOUT_MS}. + * + * This action does not update the CEC network directly; an incoming <Report Features> message + * should be handled separately by {@link HdmiCecNetwork}. + */ +public class GiveFeaturesAction extends HdmiCecFeatureAction { + private static final String TAG = "GiveFeaturesAction"; + + private static final int STATE_WAITING_FOR_REPORT_FEATURES = 1; + + private final int mTargetAddress; + + public GiveFeaturesAction(HdmiCecLocalDevice source, int targetAddress, + IHdmiControlCallback callback) { + super(source, callback); + + mTargetAddress = targetAddress; + } + + boolean start() { + sendCommand(HdmiCecMessageBuilder.buildGiveFeatures(getSourceAddress(), mTargetAddress), + result -> { + if (result == SendMessageResult.SUCCESS) { + mState = STATE_WAITING_FOR_REPORT_FEATURES; + addTimer(STATE_WAITING_FOR_REPORT_FEATURES, HdmiConfig.TIMEOUT_MS); + } else { + finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } + }); + return true; + } + + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAITING_FOR_REPORT_FEATURES) { + return false; + } + if (cmd instanceof ReportFeaturesMessage) { + return handleReportFeatures((ReportFeaturesMessage) cmd); + } + return false; + } + + private boolean handleReportFeatures(ReportFeaturesMessage cmd) { + if (cmd.getSource() == mTargetAddress) { + // No need to update the network, since it should already have processed this message. + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + return true; + } + return false; + } + + void handleTimerEvent(int state) { + finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index cc864307daf1..1a568c30c899 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -47,6 +47,7 @@ import libcore.util.EmptyArray; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.LinkedList; import java.util.List; @@ -694,7 +695,19 @@ final class HdmiCecController { @ServiceThreadOnly private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { assertRunOnServiceThread(); - HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body); + + if (body.length == 0) { + Slog.e(TAG, "Message with empty body received."); + return; + } + + HdmiCecMessage command = HdmiCecMessage.build(srcAddress, dstAddress, body[0], + Arrays.copyOfRange(body, 1, body.length)); + + if (command.getValidationResult() != HdmiCecMessageValidator.OK) { + Slog.e(TAG, "Invalid message received: " + command); + } + HdmiLogger.debug("[R]:" + command); addCecMessageToHistory(true /* isReceived */, command); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 3aa2f420d11f..c674ffebfe92 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; import android.annotation.CallSuper; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -634,7 +635,34 @@ abstract class HdmiCecLocalDevice { protected abstract List<Integer> getRcFeatures(); - protected abstract List<Integer> getDeviceFeatures(); + /** + * Computes the set of supported device features. To update local state with changes in + * the set of supported device features, use {@link #getDeviceFeatures} instead. + */ + protected DeviceFeatures computeDeviceFeatures() { + return DeviceFeatures.NO_FEATURES_SUPPORTED; + } + + /** + * Computes the set of supported device features, and updates local state to match. + */ + private void updateDeviceFeatures() { + synchronized (mLock) { + setDeviceInfo(getDeviceInfo().toBuilder() + .setDeviceFeatures(computeDeviceFeatures()) + .build()); + } + } + + /** + * Computes and returns the set of supported device features. Updates local state to match. + */ + protected final DeviceFeatures getDeviceFeatures() { + updateDeviceFeatures(); + synchronized (mLock) { + return getDeviceInfo().getDeviceFeatures(); + } + } @Constants.HandleMessageResult protected int handleGiveFeatures(HdmiCecMessage message) { @@ -655,11 +683,17 @@ abstract class HdmiCecLocalDevice { int rcProfile = getRcProfile(); List<Integer> rcFeatures = getRcFeatures(); - List<Integer> deviceFeatures = getDeviceFeatures(); + DeviceFeatures deviceFeatures = getDeviceFeatures(); + + + int logicalAddress; + synchronized (mLock) { + logicalAddress = mDeviceInfo.getLogicalAddress(); + } mService.sendCecCommand( - HdmiCecMessageBuilder.buildReportFeatures( - mDeviceInfo.getLogicalAddress(), + ReportFeaturesMessage.build( + logicalAddress, mService.getCecVersion(), localDeviceTypes, rcProfile, @@ -922,6 +956,7 @@ abstract class HdmiCecLocalDevice { final void handleAddressAllocated(int logicalAddress, int reason) { assertRunOnServiceThread(); mPreferredAddress = logicalAddress; + updateDeviceFeatures(); if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) { reportFeatures(); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 7e71589302f1..2ef3ebf5d07e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -15,6 +15,9 @@ */ package com.android.server.hdmi; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; + import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; @@ -22,6 +25,7 @@ import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONT import android.annotation.Nullable; import android.content.ActivityNotFoundException; import android.content.Intent; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -177,14 +181,12 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } @Override - protected List<Integer> getDeviceFeatures() { - List<Integer> deviceFeatures = new ArrayList<>(); - - if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { - deviceFeatures.add(Constants.DEVICE_FEATURE_SOURCE_SUPPORTS_ARC_RX); - } + protected DeviceFeatures computeDeviceFeatures() { + boolean arcSupport = SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true); - return deviceFeatures; + return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setArcRxSupport(arcSupport ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED) + .build(); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 4f5524948aa1..90b4f76fec10 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -28,8 +28,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.Constants.LocalActivePort; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; -import com.google.android.collect.Lists; - import java.util.ArrayList; import java.util.List; @@ -358,11 +356,6 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { return features; } - @Override - protected List<Integer> getDeviceFeatures() { - return Lists.newArrayList(); - } - // Active source claiming needs to be handled in Service // since service can decide who will be the active source when the device supports // multiple device types in this method. diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index c2ed24a1136d..afcd3dde0633 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE; import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION; import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE; @@ -31,6 +33,7 @@ import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGI import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; import android.annotation.Nullable; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -346,7 +349,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (info == null) { // No CEC/MHL device is present at the port. Attempt to switch to // the hardware port itself for non-CEC devices that may be connected. - info = new HdmiDeviceInfo(path, getActivePortId()); + info = HdmiDeviceInfo.hardwarePort(path, getActivePortId()); } } mService.invokeInputChangeListener(info); @@ -1548,9 +1551,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @Override - protected List<Integer> getDeviceFeatures() { - List<Integer> deviceFeatures = new ArrayList<>(); - + protected DeviceFeatures computeDeviceFeatures() { boolean hasArcPort = false; List<HdmiPortInfo> ports = mService.getPortInfo(); for (HdmiPortInfo port : ports) { @@ -1559,11 +1560,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { break; } } - if (hasArcPort) { - deviceFeatures.add(Constants.DEVICE_FEATURE_SINK_SUPPORTS_ARC_TX); - } - deviceFeatures.add(Constants.DEVICE_FEATURE_TV_SUPPORTS_RECORD_TV_SCREEN); - return deviceFeatures; + + return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setRecordTvScreenSupport(FEATURE_SUPPORTED) + .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED) + .build(); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java index e3292a35bf6b..290cae50f2cb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult; + import android.annotation.Nullable; import com.android.server.hdmi.Constants.FeatureOpcode; @@ -26,11 +28,13 @@ import java.util.Arrays; import java.util.Objects; /** - * A class to encapsulate HDMI-CEC message used for the devices connected via - * HDMI cable to communicate with one another. A message is defined by its - * source and destination address, command (or opcode), and optional parameters. + * Encapsulates the data that defines an HDMI-CEC message: source and destination address, + * command (or opcode), and optional parameters. Also stores the result of validating the message. + * + * Subclasses of this class represent specific messages that have been validated, and expose their + * parsed parameters. */ -public final class HdmiCecMessage { +public class HdmiCecMessage { public static final byte[] EMPTY_PARAM = EmptyArray.BYTE; private final int mSource; @@ -39,24 +43,60 @@ public final class HdmiCecMessage { private final int mOpcode; private final byte[] mParams; + private final int mValidationResult; + /** - * Constructor. + * Constructor that allows the caller to provide the validation result. + * Must only be called by subclasses; other callers should use {@link #build}. */ - public HdmiCecMessage(int source, int destination, int opcode, byte[] params) { + protected HdmiCecMessage(int source, int destination, int opcode, byte[] params, + @ValidationResult int validationResult) { mSource = source; mDestination = destination; mOpcode = opcode & 0xFF; mParams = Arrays.copyOf(params, params.length); + mValidationResult = validationResult; + } + + private HdmiCecMessage(int source, int destination, int opcode, byte[] params) { + this(source, destination, opcode, params, + HdmiCecMessageValidator.validate(source, destination, opcode & 0xFF, params)); + } + + /** + * Constructs and validates a message. The result of validation will be accessible via + * {@link #getValidationResult}. + * + * Intended for parsing incoming messages, as it takes raw bytes as message parameters. + * + * If the opcode has its own subclass of this one, this method will instead validate and build + * the message using the logic in that class. If successful, it will return a validated + * instance of that class that exposes parsed parameters. + */ + static HdmiCecMessage build(int source, int destination, int opcode, byte[] params) { + switch (opcode & 0xFF) { + case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL: + return SetAudioVolumeLevelMessage.build(source, destination, params); + case Constants.MESSAGE_REPORT_FEATURES: + return ReportFeaturesMessage.build(source, destination, params); + default: + return new HdmiCecMessage(source, destination, opcode & 0xFF, params); + } + } + + static HdmiCecMessage build(int source, int destination, int opcode) { + return new HdmiCecMessage(source, destination, opcode, EMPTY_PARAM); } @Override public boolean equals(@Nullable Object message) { if (message instanceof HdmiCecMessage) { HdmiCecMessage that = (HdmiCecMessage) message; - return this.mSource == that.getSource() && - this.mDestination == that.getDestination() && - this.mOpcode == that.getOpcode() && - Arrays.equals(this.mParams, that.getParams()); + return this.mSource == that.getSource() + && this.mDestination == that.getDestination() + && this.mOpcode == that.getOpcode() + && Arrays.equals(this.mParams, that.getParams()) + && this.mValidationResult == that.getValidationResult(); } return false; } @@ -111,6 +151,13 @@ public final class HdmiCecMessage { return mParams; } + /** + * Returns the validation result of the message. + */ + public int getValidationResult() { + return mValidationResult; + } + @Override public String toString() { StringBuilder s = new StringBuilder(); @@ -129,9 +176,30 @@ public final class HdmiCecMessage { } } } + if (mValidationResult != HdmiCecMessageValidator.OK) { + s.append(String.format(" <Validation error: %s>", + validationResultToString(mValidationResult))); + } return s.toString(); } + private static String validationResultToString(@ValidationResult int validationResult) { + switch (validationResult) { + case HdmiCecMessageValidator.OK: + return "ok"; + case HdmiCecMessageValidator.ERROR_SOURCE: + return "invalid source"; + case HdmiCecMessageValidator.ERROR_DESTINATION: + return "invalid destination"; + case HdmiCecMessageValidator.ERROR_PARAMETER: + return "invalid parameters"; + case HdmiCecMessageValidator.ERROR_PARAMETER_SHORT: + return "short parameters"; + default: + return "unknown error"; + } + } + private static String opcodeToString(@FeatureOpcode int opcode) { switch (opcode) { case Constants.MESSAGE_FEATURE_ABORT: diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 1c8f21f6526e..adcff4c1111f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -16,17 +16,15 @@ package com.android.server.hdmi; -import android.hardware.hdmi.HdmiControlManager; -import android.hardware.hdmi.HdmiDeviceInfo; - import com.android.server.hdmi.Constants.AudioCodec; import java.io.UnsupportedEncodingException; -import java.util.Arrays; -import java.util.List; /** * A helper class to build {@link HdmiCecMessage} from various cec commands. + * + * If a message type has its own specific subclass of {@link HdmiCecMessage}, + * its static factory method is instead declared in that subclass. */ public class HdmiCecMessageBuilder { private static final int OSD_NAME_MAX_LENGTH = 14; @@ -34,20 +32,6 @@ public class HdmiCecMessageBuilder { private HdmiCecMessageBuilder() {} /** - * Build {@link HdmiCecMessage} from raw data. - * - * @param src source address of command - * @param dest destination address of command - * @param body body of message. It includes opcode. - * @return newly created {@link HdmiCecMessage} - */ - static HdmiCecMessage of(int src, int dest, byte[] body) { - byte opcode = body[0]; - byte params[] = Arrays.copyOfRange(body, 1, body.length); - return new HdmiCecMessage(src, dest, opcode, params); - } - - /** * Build <Feature Abort> command. <Feature Abort> consists of * 1 byte original opcode and 1 byte reason fields with basic fields. * @@ -63,7 +47,7 @@ public class HdmiCecMessageBuilder { (byte) (originalOpcode & 0xFF), (byte) (reason & 0xFF), }; - return buildCommand(src, dest, Constants.MESSAGE_FEATURE_ABORT, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_FEATURE_ABORT, params); } /** @@ -74,7 +58,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGivePhysicalAddress(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS); } /** @@ -85,7 +69,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveOsdNameCommand(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_OSD_NAME); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_OSD_NAME); } /** @@ -96,7 +80,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveDeviceVendorIdCommand(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID); } /** @@ -121,7 +105,7 @@ public class HdmiCecMessageBuilder { (byte) (normalized.charAt(2) & 0xFF), }; // <Set Menu Language> is broadcast message. - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_SET_MENU_LANGUAGE, params); } @@ -141,7 +125,7 @@ public class HdmiCecMessageBuilder { } catch (UnsupportedEncodingException e) { return null; } - return buildCommand(src, dest, Constants.MESSAGE_SET_OSD_NAME, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_OSD_NAME, params); } /** @@ -164,7 +148,7 @@ public class HdmiCecMessageBuilder { (byte) (deviceType & 0xFF) }; // <Report Physical Address> is broadcast message. - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS, params); } @@ -185,7 +169,7 @@ public class HdmiCecMessageBuilder { (byte) (vendorId & 0xFF) }; // <Device Vendor Id> is broadcast message. - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_DEVICE_VENDOR_ID, params); } @@ -202,7 +186,7 @@ public class HdmiCecMessageBuilder { byte[] params = new byte[] { (byte) (version & 0xFF) }; - return buildCommand(src, dest, Constants.MESSAGE_CEC_VERSION, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CEC_VERSION, params); } /** @@ -213,7 +197,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRequestArcInitiation(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_REQUEST_ARC_INITIATION); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REQUEST_ARC_INITIATION); } /** @@ -224,7 +208,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildInitiateArc(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_INITIATE_ARC); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_INITIATE_ARC); } /** @@ -235,7 +219,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildTerminateArc(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_TERMINATE_ARC); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_TERMINATE_ARC); } /** @@ -246,7 +230,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRequestArcTermination(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_REQUEST_ARC_TERMINATION); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REQUEST_ARC_TERMINATION); } /** @@ -257,7 +241,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildReportArcInitiated(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_INITIATED); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_ARC_INITIATED); } /** @@ -268,7 +252,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildReportArcTerminated(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_TERMINATED); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_ARC_TERMINATED); } @@ -286,7 +270,8 @@ public class HdmiCecMessageBuilder { for (int i = 0; i < params.length ; i++){ params[i] = (byte) (audioFormats[i] & 0xff); } - return buildCommand(src, dest, Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, params); + return HdmiCecMessage.build( + src, dest, Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, params); } @@ -298,7 +283,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildTextViewOn(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_TEXT_VIEW_ON); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_TEXT_VIEW_ON); } /** @@ -308,7 +293,8 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRequestActiveSource(int src) { - return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REQUEST_ACTIVE_SOURCE); + return HdmiCecMessage.build( + src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REQUEST_ACTIVE_SOURCE); } /** @@ -319,7 +305,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildActiveSource(int src, int physicalAddress) { - return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ACTIVE_SOURCE, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ACTIVE_SOURCE, physicalAddressToParam(physicalAddress)); } @@ -331,7 +317,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) { - return buildCommand(src, Constants.ADDR_TV, + return HdmiCecMessage.build(src, Constants.ADDR_TV, Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressToParam(physicalAddress)); } @@ -345,7 +331,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildSetStreamPath(int src, int streamPath) { - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_SET_STREAM_PATH, physicalAddressToParam(streamPath)); } @@ -364,7 +350,7 @@ public class HdmiCecMessageBuilder { (byte) ((oldPath >> 8) & 0xFF), (byte) (oldPath & 0xFF), (byte) ((newPath >> 8) & 0xFF), (byte) (newPath & 0xFF) }; - return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_CHANGE, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_CHANGE, param); } @@ -378,7 +364,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRoutingInformation(int src, int physicalAddress) { - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_INFORMATION, physicalAddressToParam(physicalAddress)); } @@ -390,7 +376,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveDevicePowerStatus(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS); } /** @@ -405,7 +391,7 @@ public class HdmiCecMessageBuilder { byte[] param = new byte[] { (byte) (powerStatus & 0xFF) }; - return buildCommand(src, dest, Constants.MESSAGE_REPORT_POWER_STATUS, param); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_POWER_STATUS, param); } /** @@ -420,7 +406,7 @@ public class HdmiCecMessageBuilder { byte[] param = new byte[] { (byte) (menuStatus & 0xFF) }; - return buildCommand(src, dest, Constants.MESSAGE_MENU_STATUS, param); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_MENU_STATUS, param); } /** @@ -435,10 +421,10 @@ public class HdmiCecMessageBuilder { static HdmiCecMessage buildSystemAudioModeRequest(int src, int avr, int avrPhysicalAddress, boolean enableSystemAudio) { if (enableSystemAudio) { - return buildCommand(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, + return HdmiCecMessage.build(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, physicalAddressToParam(avrPhysicalAddress)); } else { - return buildCommand(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST); + return HdmiCecMessage.build(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST); } } @@ -479,7 +465,8 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildReportShortAudioDescriptor(int src, int des, byte[] sadBytes) { - return buildCommand(src, des, Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR, sadBytes); + return HdmiCecMessage.build( + src, des, Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR, sadBytes); } /** @@ -490,7 +477,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveAudioStatus(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_AUDIO_STATUS); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_AUDIO_STATUS); } /** @@ -505,7 +492,7 @@ public class HdmiCecMessageBuilder { static HdmiCecMessage buildReportAudioStatus(int src, int dest, int volume, boolean mute) { byte status = (byte) ((byte) (mute ? 1 << 7 : 0) | ((byte) volume & 0x7F)); byte[] params = new byte[] { status }; - return buildCommand(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params); } /** @@ -529,7 +516,8 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildUserControlPressed(int src, int dest, byte[] commandParam) { - return buildCommand(src, dest, Constants.MESSAGE_USER_CONTROL_PRESSED, commandParam); + return HdmiCecMessage.build( + src, dest, Constants.MESSAGE_USER_CONTROL_PRESSED, commandParam); } /** @@ -540,7 +528,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildUserControlReleased(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_USER_CONTROL_RELEASED); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_USER_CONTROL_RELEASED); } /** @@ -551,7 +539,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveSystemAudioModeStatus(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS); } /** @@ -562,7 +550,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ public static HdmiCecMessage buildStandby(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_STANDBY); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_STANDBY); } /** @@ -574,7 +562,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildVendorCommand(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_VENDOR_COMMAND, params); } /** @@ -593,7 +581,7 @@ public class HdmiCecMessageBuilder { params[1] = (byte) ((vendorId >> 8) & 0xFF); params[2] = (byte) (vendorId & 0xFF); System.arraycopy(operands, 0, params, 3, operands.length); - return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, params); } /** @@ -605,7 +593,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRecordOn(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_RECORD_ON, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_RECORD_ON, params); } /** @@ -616,7 +604,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRecordOff(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_RECORD_OFF); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_RECORD_OFF); } /** @@ -628,7 +616,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildSetDigitalTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params); } /** @@ -640,7 +628,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildSetAnalogueTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params); } /** @@ -652,7 +640,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildSetExternalTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params); } /** @@ -664,7 +652,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildClearDigitalTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params); } /** @@ -676,7 +664,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildClearAnalogueTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params); } /** @@ -688,73 +676,16 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildClearExternalTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params); } static HdmiCecMessage buildGiveFeatures(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_FEATURES); - } - - static HdmiCecMessage buildReportFeatures(int src, - @HdmiControlManager.HdmiCecVersion int cecVersion, - List<Integer> allDeviceTypes, @Constants.RcProfile int rcProfile, - List<Integer> rcFeatures, - List<Integer> deviceFeatures) { - byte cecVersionByte = (byte) (cecVersion & 0xFF); - byte deviceTypes = 0; - for (Integer deviceType : allDeviceTypes) { - deviceTypes |= 1 << hdmiDeviceInfoDeviceTypeToShiftValue(deviceType); - } - - byte rcProfileByte = 0; - rcProfileByte |= rcProfile << 6; - if (rcProfile == Constants.RC_PROFILE_SOURCE) { - for (@Constants.RcProfileSource Integer rcFeature : rcFeatures) { - rcProfileByte |= 1 << rcFeature; - } - } else { - @Constants.RcProfileTv byte rcProfileTv = (byte) (rcFeatures.get(0) & 0xFFFF); - rcProfileByte |= rcProfileTv; - } - - byte deviceFeaturesByte = 0; - for (@Constants.DeviceFeature Integer deviceFeature : deviceFeatures) { - deviceFeaturesByte |= 1 << deviceFeature; - } - - byte[] params = {cecVersionByte, deviceTypes, rcProfileByte, deviceFeaturesByte}; - return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REPORT_FEATURES, - params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_FEATURES); } /***** Please ADD new buildXXX() methods above. ******/ /** - * Build a {@link HdmiCecMessage} without extra parameter. - * - * @param src source address of command - * @param dest destination address of command - * @param opcode opcode for a message - * @return newly created {@link HdmiCecMessage} - */ - private static HdmiCecMessage buildCommand(int src, int dest, int opcode) { - return new HdmiCecMessage(src, dest, opcode, HdmiCecMessage.EMPTY_PARAM); - } - - /** - * Build a {@link HdmiCecMessage} with given values. - * - * @param src source address of command - * @param dest destination address of command - * @param opcode opcode for a message - * @param params extra parameters for command - * @return newly created {@link HdmiCecMessage} - */ - private static HdmiCecMessage buildCommand(int src, int dest, int opcode, byte[] params) { - return new HdmiCecMessage(src, dest, opcode, params); - } - - /** * Build a {@link HdmiCecMessage} with a boolean param and other given values. * * @param src source address of command @@ -768,7 +699,7 @@ public class HdmiCecMessageBuilder { byte[] params = new byte[]{ param ? (byte) 0b1 : 0b0 }; - return buildCommand(src, des, opcode, params); + return HdmiCecMessage.build(src, des, opcode, params); } private static byte[] physicalAddressToParam(int physicalAddress) { @@ -777,24 +708,4 @@ public class HdmiCecMessageBuilder { (byte) (physicalAddress & 0xFF) }; } - - @Constants.DeviceType - private static int hdmiDeviceInfoDeviceTypeToShiftValue(int deviceType) { - switch (deviceType) { - case HdmiDeviceInfo.DEVICE_TV: - return Constants.ALL_DEVICE_TYPES_TV; - case HdmiDeviceInfo.DEVICE_RECORDER: - return Constants.ALL_DEVICE_TYPES_RECORDER; - case HdmiDeviceInfo.DEVICE_TUNER: - return Constants.ALL_DEVICE_TYPES_TUNER; - case HdmiDeviceInfo.DEVICE_PLAYBACK: - return Constants.ALL_DEVICE_TYPES_PLAYBACK; - case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM: - return Constants.ALL_DEVICE_TYPES_AUDIO_SYSTEM; - case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH: - return Constants.ALL_DEVICE_TYPES_SWITCH; - default: - throw new IllegalArgumentException("Unhandled device type: " + deviceType); - } - } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index 8a727c6ffb24..220a438d55ee 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -16,23 +16,34 @@ package com.android.server.hdmi; +import android.annotation.IntDef; import android.hardware.hdmi.HdmiDeviceInfo; import android.util.SparseArray; /** - * A helper class to validates {@link HdmiCecMessage}. + * A helper class to validate {@link HdmiCecMessage}. + * + * If a message type has its own specific subclass of {@link HdmiCecMessage}, + * validation is performed in that subclass instead. */ public class HdmiCecMessageValidator { private static final String TAG = "HdmiCecMessageValidator"; + @IntDef({ + OK, + ERROR_SOURCE, + ERROR_DESTINATION, + ERROR_PARAMETER, + ERROR_PARAMETER_SHORT, + }) + public @interface ValidationResult {}; + static final int OK = 0; static final int ERROR_SOURCE = 1; static final int ERROR_DESTINATION = 2; static final int ERROR_PARAMETER = 3; static final int ERROR_PARAMETER_SHORT = 4; - private final HdmiControlService mService; - interface ParameterValidator { /** * @return errorCode errorCode can be {@link #OK}, {@link #ERROR_PARAMETER} or @@ -42,13 +53,13 @@ public class HdmiCecMessageValidator { } // Only the direct addressing is allowed. - private static final int DEST_DIRECT = 1 << 0; + public static final int DEST_DIRECT = 1 << 0; // Only the broadcast addressing is allowed. - private static final int DEST_BROADCAST = 1 << 1; + public static final int DEST_BROADCAST = 1 << 1; // Both the direct and the broadcast addressing are allowed. - private static final int DEST_ALL = DEST_DIRECT | DEST_BROADCAST; + public static final int DEST_ALL = DEST_DIRECT | DEST_BROADCAST; // True if the messages from address 15 (unregistered) are allowed. - private static final int SRC_UNREGISTERED = 1 << 2; + public static final int SRC_UNREGISTERED = 1 << 2; private static class ValidationInfo { public final ParameterValidator parameterValidator; @@ -60,11 +71,11 @@ public class HdmiCecMessageValidator { } } - final SparseArray<ValidationInfo> mValidationInfo = new SparseArray<>(); + private HdmiCecMessageValidator() {} - public HdmiCecMessageValidator(HdmiControlService service) { - mService = service; + private static final SparseArray<ValidationInfo> sValidationInfo = new SparseArray<>(); + static { // Messages related to the physical address. PhysicalAddressValidator physicalAddressValidator = new PhysicalAddressValidator(); addValidationInfo(Constants.MESSAGE_ACTIVE_SOURCE, @@ -234,8 +245,6 @@ public class HdmiCecMessageValidator { // Messages for Feature Discovery. addValidationInfo(Constants.MESSAGE_GIVE_FEATURES, noneValidator, DEST_DIRECT | SRC_UNREGISTERED); - addValidationInfo(Constants.MESSAGE_REPORT_FEATURES, new VariableLengthValidator(4, 14), - DEST_BROADCAST); // Messages for Dynamic Auto Lipsync addValidationInfo(Constants.MESSAGE_REQUEST_CURRENT_LATENCY, physicalAddressValidator, @@ -250,59 +259,62 @@ public class HdmiCecMessageValidator { DEST_BROADCAST | SRC_UNREGISTERED); } - private void addValidationInfo(int opcode, ParameterValidator validator, int addrType) { - mValidationInfo.append(opcode, new ValidationInfo(validator, addrType)); + private static void addValidationInfo(int opcode, ParameterValidator validator, int addrType) { + sValidationInfo.append(opcode, new ValidationInfo(validator, addrType)); } - int isValid(HdmiCecMessage message, boolean isMessageReceived) { - int opcode = message.getOpcode(); - ValidationInfo info = mValidationInfo.get(opcode); + /** + * Validates all parameters of a HDMI-CEC message using static information stored in this class. + */ + @ValidationResult + static int validate(int source, int destination, int opcode, byte[] params) { + ValidationInfo info = sValidationInfo.get(opcode); + if (info == null) { - HdmiLogger.warning("No validation information for the message: " + message); + HdmiLogger.warning("No validation information for the opcode: " + opcode); return OK; } - // Check the source field. - if (message.getSource() == Constants.ADDR_UNREGISTERED && - (info.addressType & SRC_UNREGISTERED) == 0) { - HdmiLogger.warning("Unexpected source: " + message); - return ERROR_SOURCE; + int addressValidationResult = validateAddress(source, destination, info.addressType); + if (addressValidationResult != OK) { + return addressValidationResult; } - if (isMessageReceived) { - // Check if the source's logical address and local device's logical - // address are the same. - for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { - synchronized (device.mLock) { - if (message.getSource() == device.getDeviceInfo().getLogicalAddress() - && message.getSource() != Constants.ADDR_UNREGISTERED) { - HdmiLogger.warning( - "Unexpected source: message sent from device itself, " + message); - return ERROR_SOURCE; - } - } - } + // Validate parameters + int errorCode = info.parameterValidator.isValid(params); + if (errorCode != OK) { + return errorCode; + } + + return OK; + } + + /** + * Validates the source and destination addresses of a HDMI-CEC message according to input + * address type. Allows address validation logic to be expressed concisely without depending + * on static information in this class. + * @param source Source address to validate + * @param destination Destination address to validate + * @param addressType Rules for validating the addresses - e.g. {@link #DEST_BROADCAST} + */ + @ValidationResult + static int validateAddress(int source, int destination, int addressType) { + // Check the source field. + if (source == Constants.ADDR_UNREGISTERED + && (addressType & SRC_UNREGISTERED) == 0) { + return ERROR_SOURCE; } // Check the destination field. - if (message.getDestination() == Constants.ADDR_BROADCAST) { - if ((info.addressType & DEST_BROADCAST) == 0) { - HdmiLogger.warning("Unexpected broadcast message: " + message); + if (destination == Constants.ADDR_BROADCAST) { + if ((addressType & DEST_BROADCAST) == 0) { return ERROR_DESTINATION; } } else { // Direct addressing. - if ((info.addressType & DEST_DIRECT) == 0) { - HdmiLogger.warning("Unexpected direct message: " + message); + if ((addressType & DEST_DIRECT) == 0) { return ERROR_DESTINATION; } } - - // Check the parameter type. - int errorCode = info.parameterValidator.isValid(message.getParams()); - if (errorCode != OK) { - HdmiLogger.warning("Unexpected parameters: " + message); - return errorCode; - } return OK; } @@ -336,7 +348,7 @@ public class HdmiCecMessageValidator { } } - private boolean isValidPhysicalAddress(byte[] params, int offset) { + private static boolean isValidPhysicalAddress(byte[] params, int offset) { int physicalAddress = HdmiUtils.twoBytesToInt(params, offset); while (physicalAddress != 0) { int maskedAddress = physicalAddress & 0xF000; @@ -345,19 +357,6 @@ public class HdmiCecMessageValidator { return false; } } - - if (!mService.isTvDevice()) { - // If the device is not TV, we can't convert path to port-id, so stop here. - return true; - } - int path = HdmiUtils.twoBytesToInt(params, offset); - if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == mService.getPhysicalAddress()) { - return true; - } - int portId = mService.pathToPortId(path); - if (portId == Constants.INVALID_PORT_ID) { - return false; - } return true; } @@ -380,7 +379,7 @@ public class HdmiCecMessageValidator { return success ? OK : ERROR_PARAMETER; } - private boolean isWithinRange(int value, int min, int max) { + private static boolean isWithinRange(int value, int min, int max) { value = value & 0xFF; return (value >= min && value <= max); } @@ -392,7 +391,7 @@ public class HdmiCecMessageValidator { * @param value Display Control * @return true if the Display Control is valid */ - private boolean isValidDisplayControl(int value) { + private static boolean isValidDisplayControl(int value) { value = value & 0xFF; return (value == 0x00 || value == 0x40 || value == 0x80 || value == 0xC0); } @@ -407,7 +406,7 @@ public class HdmiCecMessageValidator { * @param maxLength Maximum length of string to be evaluated * @return true if the given type is valid */ - private boolean isValidAsciiString(byte[] params, int offset, int maxLength) { + private static boolean isValidAsciiString(byte[] params, int offset, int maxLength) { for (int i = offset; i < params.length && i < maxLength; i++) { if (!isWithinRange(params[i], 0x20, 0x7E)) { return false; @@ -423,7 +422,7 @@ public class HdmiCecMessageValidator { * @param value day of month * @return true if the day of month is valid */ - private boolean isValidDayOfMonth(int value) { + private static boolean isValidDayOfMonth(int value) { return isWithinRange(value, 1, 31); } @@ -434,7 +433,7 @@ public class HdmiCecMessageValidator { * @param value month of year * @return true if the month of year is valid */ - private boolean isValidMonthOfYear(int value) { + private static boolean isValidMonthOfYear(int value) { return isWithinRange(value, 1, 12); } @@ -445,7 +444,7 @@ public class HdmiCecMessageValidator { * @param value hour * @return true if the hour is valid */ - private boolean isValidHour(int value) { + private static boolean isValidHour(int value) { return isWithinRange(value, 0, 23); } @@ -456,7 +455,7 @@ public class HdmiCecMessageValidator { * @param value minute * @return true if the minute is valid */ - private boolean isValidMinute(int value) { + private static boolean isValidMinute(int value) { return isWithinRange(value, 0, 59); } @@ -467,7 +466,7 @@ public class HdmiCecMessageValidator { * @param value duration hours * @return true if the duration hours is valid */ - private boolean isValidDurationHours(int value) { + private static boolean isValidDurationHours(int value) { return isWithinRange(value, 0, 99); } @@ -478,7 +477,7 @@ public class HdmiCecMessageValidator { * @param value recording sequence * @return true if the given recording sequence is valid */ - private boolean isValidRecordingSequence(int value) { + private static boolean isValidRecordingSequence(int value) { value = value & 0xFF; // Validate bit 7 is set to zero if ((value & 0x80) != 0x00) { @@ -496,7 +495,7 @@ public class HdmiCecMessageValidator { * @param value analogue broadcast type * @return true if the analogue broadcast type is valid */ - private boolean isValidAnalogueBroadcastType(int value) { + private static boolean isValidAnalogueBroadcastType(int value) { return isWithinRange(value, 0x00, 0x02); } @@ -508,7 +507,7 @@ public class HdmiCecMessageValidator { * @param value analogue frequency * @return true if the analogue frequency is valid */ - private boolean isValidAnalogueFrequency(int value) { + private static boolean isValidAnalogueFrequency(int value) { value = value & 0xFFFF; return (value != 0x000 && value != 0xFFFF); } @@ -520,7 +519,7 @@ public class HdmiCecMessageValidator { * @param value broadcast system * @return true if the broadcast system is valid */ - private boolean isValidBroadcastSystem(int value) { + private static boolean isValidBroadcastSystem(int value) { return isWithinRange(value, 0, 31); } @@ -531,7 +530,7 @@ public class HdmiCecMessageValidator { * @param value Digital Broadcast System * @return true if the Digital Broadcast System is ARIB type */ - private boolean isAribDbs(int value) { + private static boolean isAribDbs(int value) { return (value == 0x00 || isWithinRange(value, 0x08, 0x0A)); } @@ -542,7 +541,7 @@ public class HdmiCecMessageValidator { * @param value Digital Broadcast System * @return true if the Digital Broadcast System is ATSC type */ - private boolean isAtscDbs(int value) { + private static boolean isAtscDbs(int value) { return (value == 0x01 || isWithinRange(value, 0x10, 0x12)); } @@ -553,7 +552,7 @@ public class HdmiCecMessageValidator { * @param value Digital Broadcast System * @return true if the Digital Broadcast System is DVB type */ - private boolean isDvbDbs(int value) { + private static boolean isDvbDbs(int value) { return (value == 0x02 || isWithinRange(value, 0x18, 0x1B)); } @@ -565,7 +564,7 @@ public class HdmiCecMessageValidator { * @param value Digital Broadcast System * @return true if the Digital Broadcast System is valid */ - private boolean isValidDigitalBroadcastSystem(int value) { + private static boolean isValidDigitalBroadcastSystem(int value) { return (isAribDbs(value) || isAtscDbs(value) || isDvbDbs(value)); } @@ -578,7 +577,7 @@ public class HdmiCecMessageValidator { * @param offset start offset of Channel Identifier * @return true if the Channel Identifier is valid */ - private boolean isValidChannelIdentifier(byte[] params, int offset) { + private static boolean isValidChannelIdentifier(byte[] params, int offset) { // First 6 bits contain Channel Number Format int channelNumberFormat = params[offset] & 0xFC; if (channelNumberFormat == 0x04) { @@ -600,7 +599,7 @@ public class HdmiCecMessageValidator { * @param offset start offset of Digital Service Identification * @return true if the Digital Service Identification is valid */ - private boolean isValidDigitalServiceIdentification(byte[] params, int offset) { + private static boolean isValidDigitalServiceIdentification(byte[] params, int offset) { // MSB contains Service Identification Method int serviceIdentificationMethod = params[offset] & 0x80; // Last 7 bits contains Digital Broadcast System @@ -634,7 +633,7 @@ public class HdmiCecMessageValidator { * @param value External Plug * @return true if the External Plug is valid */ - private boolean isValidExternalPlug(int value) { + private static boolean isValidExternalPlug(int value) { return isWithinRange(value, 1, 255); } @@ -645,7 +644,7 @@ public class HdmiCecMessageValidator { * @param value External Source Specifier * @return true if the External Source is valid */ - private boolean isValidExternalSource(byte[] params, int offset) { + private static boolean isValidExternalSource(byte[] params, int offset) { int externalSourceSpecifier = params[offset]; offset = offset + 1; if (externalSourceSpecifier == 0x04) { @@ -661,15 +660,15 @@ public class HdmiCecMessageValidator { return false; } - private boolean isValidProgrammedInfo(int programedInfo) { + private static boolean isValidProgrammedInfo(int programedInfo) { return (isWithinRange(programedInfo, 0x00, 0x0B)); } - private boolean isValidNotProgrammedErrorInfo(int nonProgramedErrorInfo) { + private static boolean isValidNotProgrammedErrorInfo(int nonProgramedErrorInfo) { return (isWithinRange(nonProgramedErrorInfo, 0x00, 0x0E)); } - private boolean isValidTimerStatusData(byte[] params, int offset) { + private static boolean isValidTimerStatusData(byte[] params, int offset) { int programedIndicator = params[offset] & 0x10; boolean durationAvailable = false; if (programedIndicator == 0x10) { @@ -708,7 +707,7 @@ public class HdmiCecMessageValidator { * @param value Play mode * @return true if the Play mode is valid */ - private boolean isValidPlayMode(int value) { + private static boolean isValidPlayMode(int value) { return (isWithinRange(value, 0x05, 0x07) || isWithinRange(value, 0x09, 0x0B) || isWithinRange(value, 0x15, 0x17) @@ -725,7 +724,7 @@ public class HdmiCecMessageValidator { * @param value UI Broadcast type * @return true if the UI Broadcast type is valid */ - private boolean isValidUiBroadcastType(int value) { + private static boolean isValidUiBroadcastType(int value) { return ((value == 0x00) || (value == 0x01) || (value == 0x10) @@ -749,7 +748,7 @@ public class HdmiCecMessageValidator { * @param value UI Sound Presenation Control * @return true if the UI Sound Presenation Control is valid */ - private boolean isValidUiSoundPresenationControl(int value) { + private static boolean isValidUiSoundPresenationControl(int value) { value = value & 0xFF; return ((value == 0x20) || (value == 0x30) @@ -768,7 +767,7 @@ public class HdmiCecMessageValidator { * @param params Tuner device info * @return true if the Tuner device info is valid */ - private boolean isValidTunerDeviceInfo(byte[] params) { + private static boolean isValidTunerDeviceInfo(byte[] params) { int tunerDisplayInfo = params[0] & 0x7F; if (tunerDisplayInfo == 0x00) { // Displaying digital tuner @@ -789,7 +788,7 @@ public class HdmiCecMessageValidator { return false; } - private class PhysicalAddressValidator implements ParameterValidator { + private static class PhysicalAddressValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 2) { @@ -799,7 +798,7 @@ public class HdmiCecMessageValidator { } } - private class SystemAudioModeRequestValidator extends PhysicalAddressValidator { + private static class SystemAudioModeRequestValidator extends PhysicalAddressValidator { @Override public int isValid(byte[] params) { // TV can send <System Audio Mode Request> with no parameters to terminate system audio. @@ -810,7 +809,7 @@ public class HdmiCecMessageValidator { } } - private class ReportPhysicalAddressValidator implements ParameterValidator { + private static class ReportPhysicalAddressValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 3) { @@ -820,7 +819,7 @@ public class HdmiCecMessageValidator { } } - private class RoutingChangeValidator implements ParameterValidator { + private static class RoutingChangeValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 4) { @@ -836,7 +835,7 @@ public class HdmiCecMessageValidator { * A valid parameter should lie within the range description of Record Status Info defined in * CEC 1.4 Specification : Operand Descriptions (Section 17) */ - private class RecordStatusInfoValidator implements ParameterValidator { + private static class RecordStatusInfoValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -855,7 +854,7 @@ public class HdmiCecMessageValidator { * A valid parameter should lie within the range description of ASCII defined in CEC 1.4 * Specification : Operand Descriptions (Section 17) */ - private class AsciiValidator implements ParameterValidator { + private static class AsciiValidator implements ParameterValidator { private final int mMinLength; private final int mMaxLength; @@ -885,7 +884,7 @@ public class HdmiCecMessageValidator { * A valid parameter should lie within the range description of ASCII defined in CEC 1.4 * Specification : Operand Descriptions (Section 17) */ - private class OsdStringValidator implements ParameterValidator { + private static class OsdStringValidator implements ParameterValidator { @Override public int isValid(byte[] params) { // If the length is longer than expected, we assume it's OK since the parameter can be @@ -902,7 +901,7 @@ public class HdmiCecMessageValidator { } /** Check if the given parameters are one byte parameters and within range. */ - private class OneByteRangeValidator implements ParameterValidator { + private static class OneByteRangeValidator implements ParameterValidator { private final int mMinValue, mMaxValue; OneByteRangeValidator(int minValue, int maxValue) { @@ -924,7 +923,7 @@ public class HdmiCecMessageValidator { * adhere to message description of Analogue Timer defined in CEC 1.4 Specification : Message * Descriptions for Timer Programming Feature (CEC Table 12) */ - private class AnalogueTimerValidator implements ParameterValidator { + private static class AnalogueTimerValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 11) { @@ -950,7 +949,7 @@ public class HdmiCecMessageValidator { * to message description of Digital Timer defined in CEC 1.4 Specification : Message * Descriptions for Timer Programming Feature (CEC Table 12) */ - private class DigitalTimerValidator implements ParameterValidator { + private static class DigitalTimerValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 11) { @@ -974,7 +973,7 @@ public class HdmiCecMessageValidator { * adhere to message description of External Timer defined in CEC 1.4 Specification : Message * Descriptions for Timer Programming Feature (CEC Table 12) */ - private class ExternalTimerValidator implements ParameterValidator { + private static class ExternalTimerValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 9) { @@ -997,7 +996,7 @@ public class HdmiCecMessageValidator { * within the range description defined in CEC 1.4 Specification : Operand Descriptions * (Section 17) */ - private class TimerClearedStatusValidator implements ParameterValidator { + private static class TimerClearedStatusValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -1011,7 +1010,7 @@ public class HdmiCecMessageValidator { * Check if the given timer status data parameter is valid. A valid parameter should lie within * the range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17) */ - private class TimerStatusValidator implements ParameterValidator { + private static class TimerStatusValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -1025,7 +1024,7 @@ public class HdmiCecMessageValidator { * Check if the given play mode parameter is valid. A valid parameter should lie within the * range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17) */ - private class PlayModeValidator implements ParameterValidator { + private static class PlayModeValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -1040,7 +1039,7 @@ public class HdmiCecMessageValidator { * within the range description defined in CEC 1.4 Specification : Operand Descriptions * (Section 17) */ - private class SelectAnalogueServiceValidator implements ParameterValidator { + private static class SelectAnalogueServiceValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 4) { @@ -1057,7 +1056,7 @@ public class HdmiCecMessageValidator { * within the range description defined in CEC 1.4 Specification : Operand Descriptions * (Section 17) */ - private class SelectDigitalServiceValidator implements ParameterValidator { + private static class SelectDigitalServiceValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 4) { @@ -1072,7 +1071,7 @@ public class HdmiCecMessageValidator { * within the range description defined in CEC 1.4 Specification : Operand Descriptions (Section * 17) */ - private class TunerDeviceStatusValidator implements ParameterValidator { + private static class TunerDeviceStatusValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -1083,7 +1082,7 @@ public class HdmiCecMessageValidator { } /** Check if the given user control press parameter is valid. */ - private class UserControlPressedValidator implements ParameterValidator { + private static class UserControlPressedValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java index 225785a4401d..64971746dce9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java @@ -57,7 +57,8 @@ import java.util.concurrent.ArrayBlockingQueue; * This class should not take any active action in sending CEC messages. * * Note that the information cached in this class is not guaranteed to be up-to-date, especially OSD - * names, power states can be outdated. + * names, power states can be outdated. For local devices, more up-to-date information can be + * accessed through {@link HdmiCecLocalDevice#getDeviceInfo()}. */ @VisibleForTesting public class HdmiCecNetwork { @@ -390,7 +391,7 @@ public class HdmiCecNetwork { return; } - updateCecDevice(HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus)); + updateCecDevice(info.toBuilder().setDevicePowerStatus(newPowerStatus).build()); } /** @@ -427,7 +428,8 @@ public class HdmiCecNetwork { for (HdmiPortInfo info : cecPortInfo) { portIdMap.put(info.getAddress(), info.getId()); portInfoMap.put(info.getId(), info); - portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); + portDeviceMap.put(info.getId(), + HdmiDeviceInfo.hardwarePort(info.getAddress(), info.getId())); } mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); @@ -496,13 +498,19 @@ public class HdmiCecNetwork { // Add device by logical address if it's not already known int sourceAddress = message.getSource(); if (getCecDeviceInfo(sourceAddress) == null) { - HdmiDeviceInfo newDevice = new HdmiDeviceInfo(sourceAddress, - HdmiDeviceInfo.PATH_INVALID, HdmiDeviceInfo.PORT_INVALID, - HdmiDeviceInfo.DEVICE_RESERVED, Constants.UNKNOWN_VENDOR_ID, - HdmiUtils.getDefaultDeviceName(sourceAddress)); + HdmiDeviceInfo newDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(sourceAddress) + .setDisplayName(HdmiUtils.getDefaultDeviceName(sourceAddress)) + .build(); addCecDevice(newDevice); } + // If a message type has its own class, all valid messages of that type + // will be represented by an instance of that class. + if (message instanceof ReportFeaturesMessage) { + handleReportFeatures((ReportFeaturesMessage) message); + } + switch (message.getOpcode()) { case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: handleReportPhysicalAddress(message); @@ -519,18 +527,20 @@ public class HdmiCecNetwork { case Constants.MESSAGE_CEC_VERSION: handleCecVersion(message); break; - case Constants.MESSAGE_REPORT_FEATURES: - handleReportFeatures(message); - break; } } @ServiceThreadOnly - private void handleReportFeatures(HdmiCecMessage message) { + private void handleReportFeatures(ReportFeaturesMessage message) { assertRunOnServiceThread(); - int version = Byte.toUnsignedInt(message.getParams()[0]); - updateDeviceCecVersion(message.getSource(), version); + HdmiDeviceInfo currentDeviceInfo = getCecDeviceInfo(message.getSource()); + HdmiDeviceInfo newDeviceInfo = currentDeviceInfo.toBuilder() + .setCecVersion(message.getCecVersion()) + .updateDeviceFeatures(message.getDeviceFeatures()) + .build(); + + updateCecDevice(newDeviceInfo); } @ServiceThreadOnly @@ -554,11 +564,11 @@ public class HdmiCecNetwork { if (deviceInfo == null) { Slog.i(TAG, "Unknown source device info for <Report Physical Address> " + message); } else { - HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - physicalAddress, - physicalAddressToPortId(physicalAddress), type, deviceInfo.getVendorId(), - deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(), - deviceInfo.getCecVersion()); + HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() + .setPhysicalAddress(physicalAddress) + .setPortId(physicalAddressToPortId(physicalAddress)) + .setDeviceType(type) + .build(); updateCecDevice(updatedDeviceInfo); } } @@ -588,11 +598,9 @@ public class HdmiCecNetwork { return; } - HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), deviceInfo.getDeviceType(), - deviceInfo.getVendorId(), - deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(), - hdmiCecVersion); + HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() + .setCecVersion(hdmiCecVersion) + .build(); updateCecDevice(updatedDeviceInfo); } @@ -623,10 +631,11 @@ public class HdmiCecNetwork { Slog.d(TAG, "Updating device OSD name from " + deviceInfo.getDisplayName() + " to " + osdName); - updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), - deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName, - deviceInfo.getDevicePowerStatus(), deviceInfo.getCecVersion())); + + HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() + .setDisplayName(osdName) + .build(); + updateCecDevice(updatedDeviceInfo); } @ServiceThreadOnly @@ -639,11 +648,9 @@ public class HdmiCecNetwork { if (deviceInfo == null) { Slog.i(TAG, "Unknown source device info for <Device Vendor ID> " + message); } else { - HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), - deviceInfo.getPortId(), deviceInfo.getDeviceType(), vendorId, - deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(), - deviceInfo.getCecVersion()); + HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() + .setVendorId(vendorId) + .build(); updateCecDevice(updatedDeviceInfo); } } @@ -723,10 +730,7 @@ public class HdmiCecNetwork { /** * Returns the {@link HdmiDeviceInfo} instance whose physical address matches - * - * - * - * qq * the given routing path. CEC devices use routing path for its physical address to + * the given routing path. CEC devices use routing path for its physical address to * describe the hierarchy of the devices in the network. * * @param path routing path or physical address diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 6dd9aa029ded..8391e0b4e19a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -372,8 +372,6 @@ public class HdmiControlService extends SystemService { @Nullable private HdmiCecController mCecController; - private HdmiCecMessageValidator mMessageValidator; - private HdmiCecPowerStatusController mPowerStatusController; @ServiceThreadOnly @@ -606,9 +604,6 @@ public class HdmiControlService extends SystemService { mMhlDevices = Collections.emptyList(); mHdmiCecNetwork.initPortInfo(); - if (mMessageValidator == null) { - mMessageValidator = new HdmiCecMessageValidator(this); - } mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, new HdmiCecConfig.SettingChangeListener() { @Override @@ -1086,11 +1081,6 @@ public class HdmiControlService extends SystemService { } @VisibleForTesting - void setMessageValidator(HdmiCecMessageValidator messageValidator) { - mMessageValidator = messageValidator; - } - - @VisibleForTesting void setCecMessageBuffer(CecMessageBuffer cecMessageBuffer) { this.mCecMessageBuffer = cecMessageBuffer; } @@ -1182,7 +1172,8 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { assertRunOnServiceThread(); - if (mMessageValidator.isValid(command, false) == HdmiCecMessageValidator.OK) { + if (command.getValidationResult() == HdmiCecMessageValidator.OK + && verifyPhysicalAddresses(command)) { mCecController.sendCommand(command, callback); } else { HdmiLogger.error("Invalid message type:" + command); @@ -1210,20 +1201,99 @@ public class HdmiControlService extends SystemService { mCecController.maySendFeatureAbortCommand(command, reason); } + /** + * Returns whether all of the physical addresses in a message could exist in this CEC network. + */ + boolean verifyPhysicalAddresses(HdmiCecMessage message) { + byte[] params = message.getParams(); + switch (message.getOpcode()) { + case Constants.MESSAGE_ROUTING_CHANGE: + return verifyPhysicalAddress(params, 0) + && verifyPhysicalAddress(params, 2); + case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST: + return params.length == 0 || verifyPhysicalAddress(params, 0); + case Constants.MESSAGE_ACTIVE_SOURCE: + case Constants.MESSAGE_INACTIVE_SOURCE: + case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: + case Constants.MESSAGE_ROUTING_INFORMATION: + case Constants.MESSAGE_SET_STREAM_PATH: + return verifyPhysicalAddress(params, 0); + case Constants.MESSAGE_CLEAR_EXTERNAL_TIMER: + case Constants.MESSAGE_SET_EXTERNAL_TIMER: + return verifyExternalSourcePhysicalAddress(params, 7); + default: + return true; + } + } + + /** + * Returns whether a given physical address could exist in this CEC network. + * For a TV, the physical address must either be the address of the TV itself, + * or the address of a device connected to one of its ports (possibly indirectly). + */ + private boolean verifyPhysicalAddress(byte[] params, int offset) { + if (!isTvDevice()) { + // If the device is not TV, we can't convert path to port-id, so stop here. + return true; + } + int path = HdmiUtils.twoBytesToInt(params, offset); + if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == getPhysicalAddress()) { + return true; + } + int portId = pathToPortId(path); + if (portId == Constants.INVALID_PORT_ID) { + return false; + } + return true; + } + + /** + * Returns whether the physical address of an external source could exist in this network. + */ + private boolean verifyExternalSourcePhysicalAddress(byte[] params, int offset) { + int externalSourceSpecifier = params[offset]; + offset = offset + 1; + if (externalSourceSpecifier == 0x05) { + if (params.length - offset >= 2) { + return verifyPhysicalAddress(params, offset); + } + } + return true; + } + + /** + * Returns whether the source address of a message is a local logical address. + */ + private boolean sourceAddressIsLocal(HdmiCecMessage message) { + for (HdmiCecLocalDevice device : getAllLocalDevices()) { + synchronized (device.mLock) { + if (message.getSource() == device.getDeviceInfo().getLogicalAddress() + && message.getSource() != Constants.ADDR_UNREGISTERED) { + HdmiLogger.warning( + "Unexpected source: message sent from device itself, " + message); + return true; + } + } + } + return false; + } + @ServiceThreadOnly @VisibleForTesting @Constants.HandleMessageResult protected int handleCecCommand(HdmiCecMessage message) { assertRunOnServiceThread(); - int errorCode = mMessageValidator.isValid(message, true); - if (errorCode != HdmiCecMessageValidator.OK) { - // We'll not response on the messages with the invalid source or destination - // or with parameter length shorter than specified in the standard. - if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) { - return Constants.ABORT_INVALID_OPERAND; - } + + @HdmiCecMessageValidator.ValidationResult + int validationResult = message.getValidationResult(); + if (validationResult == HdmiCecMessageValidator.ERROR_PARAMETER + || !verifyPhysicalAddresses(message)) { + return Constants.ABORT_INVALID_OPERAND; + } else if (validationResult != HdmiCecMessageValidator.OK + || sourceAddressIsLocal(message)) { return Constants.HANDLED; } + getHdmiCecNetwork().handleCecMessage(message); @Constants.HandleMessageResult int handleMessageResult = @@ -1409,9 +1479,16 @@ public class HdmiControlService extends SystemService { private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus, int cecVersion) { String displayName = readStringSetting(Global.DEVICE_NAME, Build.MODEL); - return new HdmiDeviceInfo(logicalAddress, - getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, - getVendorId(), displayName, powerStatus, cecVersion); + return HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(logicalAddress) + .setPhysicalAddress(getPhysicalAddress()) + .setPortId(pathToPortId(getPhysicalAddress())) + .setDeviceType(deviceType) + .setVendorId(getVendorId()) + .setDisplayName(displayName) + .setDevicePowerStatus(powerStatus) + .setCecVersion(cecVersion) + .build(); } // Set the display name in HdmiDeviceInfo of the current devices to content provided by @@ -1422,10 +1499,9 @@ public class HdmiControlService extends SystemService { if (deviceInfo.getDisplayName().equals(newDisplayName)) { continue; } - device.setDeviceInfo(new HdmiDeviceInfo( - deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress(), - deviceInfo.getPortId(), deviceInfo.getDeviceType(), deviceInfo.getVendorId(), - newDisplayName, deviceInfo.getDevicePowerStatus(), deviceInfo.getCecVersion())); + synchronized (device.mLock) { + device.setDeviceInfo(deviceInfo.toBuilder().setDisplayName(newDisplayName).build()); + } sendCecCommand( HdmiCecMessageBuilder.buildSetOsdNameCommand( deviceInfo.getLogicalAddress(), Constants.ADDR_TV, newDisplayName)); @@ -2629,7 +2705,7 @@ public class HdmiControlService extends SystemService { return activeSourceInfo; } - return new HdmiDeviceInfo(activeSource.physicalAddress, + return HdmiDeviceInfo.hardwarePort(activeSource.physicalAddress, pathToPortId(activeSource.physicalAddress)); } @@ -2637,7 +2713,7 @@ public class HdmiControlService extends SystemService { int activePath = tv().getActivePath(); if (activePath != HdmiDeviceInfo.PATH_INVALID) { HdmiDeviceInfo info = mHdmiCecNetwork.getSafeDeviceInfoByPath(activePath); - return (info != null) ? info : new HdmiDeviceInfo(activePath, + return (info != null) ? info : HdmiDeviceInfo.hardwarePort(activePath, tv().getActivePortId()); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java b/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java index 06ecb5a2c8a0..43469b529108 100644 --- a/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java +++ b/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java @@ -8,7 +8,7 @@ import android.hardware.hdmi.IHdmiControlCallback; */ final class HdmiMhlLocalDeviceStub { - private static final HdmiDeviceInfo INFO = new HdmiDeviceInfo( + private static final HdmiDeviceInfo INFO = HdmiDeviceInfo.mhlDevice( Constants.INVALID_PHYSICAL_ADDRESS, Constants.INVALID_PORT_ID, -1, -1); private final HdmiControlService mService; private final int mPortId; diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java index 03e5de8ac8bd..ba19cf062cdf 100644 --- a/services/core/java/com/android/server/hdmi/HdmiUtils.java +++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java @@ -391,15 +391,6 @@ final class HdmiUtils { } /** - * Clone {@link HdmiDeviceInfo} with new power status. - */ - static HdmiDeviceInfo cloneHdmiDeviceInfo(HdmiDeviceInfo info, int newPowerStatus) { - return new HdmiDeviceInfo(info.getLogicalAddress(), - info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(), - info.getVendorId(), info.getDisplayName(), newPowerStatus, info.getCecVersion()); - } - - /** * Dump a {@link SparseArray} to the print writer. * * <p>The dump is formatted: @@ -470,7 +461,7 @@ final class HdmiUtils { } /** - * Method to parse target physical address to the port number on the current device. + * Method to build target physical address to the port number on the current device. * * <p>This check assumes target address is valid. * @@ -562,7 +553,28 @@ final class HdmiUtils { for (int i = 0; i < params.length; i++) { params[i] = (byte) Integer.parseInt(parts[i + 2], 16); } - return new HdmiCecMessage(src, dest, opcode, params); + return HdmiCecMessage.build(src, dest, opcode, params); + } + + /** + * Some operands in the CEC spec consist of a variable number of bytes, where each byte except + * the last one has bit 7 set to 1. + * Given the index of a byte in such an operand, this method returns the index of the last byte + * in the operand, or -1 if the input is invalid (e.g. operand not terminated properly). + * @param params Byte array representing a CEC message's parameters + * @param offset Index of a byte in the operand to find the end of + */ + public static int getEndOfSequence(byte[] params, int offset) { + if (offset < 0) { + return -1; + } + while (offset < params.length && ((params[offset] >> 7) & 1) == 1) { + offset++; + } + if (offset >= params.length) { + return -1; + } + return offset; } public static class ShortAudioDescriptorXmlParser { diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index a307ea33abf7..c4b98c21b608 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -68,7 +68,7 @@ final class NewDeviceAction extends HdmiCecFeatureAction { mDeviceLogicalAddress = deviceLogicalAddress; mDevicePhysicalAddress = devicePhysicalAddress; mDeviceType = deviceType; - mVendorId = Constants.UNKNOWN_VENDOR_ID; + mVendorId = Constants.VENDOR_ID_UNKNOWN; } @Override @@ -174,10 +174,14 @@ final class NewDeviceAction extends HdmiCecFeatureAction { if (mDisplayName == null) { mDisplayName = HdmiUtils.getDefaultDeviceName(mDeviceLogicalAddress); } - HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo( - mDeviceLogicalAddress, mDevicePhysicalAddress, - tv().getPortId(mDevicePhysicalAddress), - mDeviceType, mVendorId, mDisplayName); + HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mDeviceLogicalAddress) + .setPhysicalAddress(mDevicePhysicalAddress) + .setPortId(tv().getPortId(mDevicePhysicalAddress)) + .setDeviceType(mDeviceType) + .setVendorId(mVendorId) + .setDisplayName(mDisplayName) + .build(); localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo); // Consume CEC messages we already got for this newly found device. diff --git a/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java b/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java new file mode 100644 index 000000000000..80bfb96d15d4 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java @@ -0,0 +1,189 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; +import static com.android.server.hdmi.HdmiCecMessageValidator.OK; +import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult; + +import android.annotation.NonNull; +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +/** + * Represents a validated <Report Features> message with parsed parameters. + * + * Only parses the [CEC Version] and [Device Features] operands. + * [All Device Types] and [RC Profile] are not parsed, but can be specified in construction. + */ +public class ReportFeaturesMessage extends HdmiCecMessage { + + @HdmiControlManager.HdmiCecVersion + private final int mCecVersion; + + @NonNull + private final DeviceFeatures mDeviceFeatures; + + private ReportFeaturesMessage(int source, int destination, byte[] params, int cecVersion, + DeviceFeatures deviceFeatures) { + super(source, destination, Constants.MESSAGE_REPORT_FEATURES, params, OK); + mCecVersion = cecVersion; + mDeviceFeatures = deviceFeatures; + } + + /** + * Static factory method. Intended for constructing outgoing or test messages, as it uses + * structured types instead of raw bytes to construct the parameters. + */ + public static HdmiCecMessage build( + int source, + @HdmiControlManager.HdmiCecVersion int cecVersion, + List<Integer> allDeviceTypes, + @Constants.RcProfile int rcProfile, + List<Integer> rcFeatures, + DeviceFeatures deviceFeatures) { + + byte cecVersionByte = (byte) (cecVersion & 0xFF); + byte deviceTypes = 0; + for (Integer deviceType : allDeviceTypes) { + deviceTypes |= (byte) (1 << hdmiDeviceInfoDeviceTypeToShiftValue(deviceType)); + } + + byte rcProfileByte = 0; + rcProfileByte |= (byte) (rcProfile << 6); + if (rcProfile == Constants.RC_PROFILE_SOURCE) { + for (@Constants.RcProfileSource Integer rcFeature : rcFeatures) { + rcProfileByte |= (byte) (1 << rcFeature); + } + } else { + @Constants.RcProfileTv byte rcProfileTv = (byte) (rcFeatures.get(0) & 0xFFFF); + rcProfileByte |= rcProfileTv; + } + + byte[] fixedOperands = {cecVersionByte, deviceTypes, rcProfileByte}; + byte[] deviceFeaturesBytes = deviceFeatures.toOperand(); + + // Concatenate fixed length operands and [Device Features] + byte[] params = Arrays.copyOf(fixedOperands, + fixedOperands.length + deviceFeaturesBytes.length); + System.arraycopy(deviceFeaturesBytes, 0, params, + fixedOperands.length, deviceFeaturesBytes.length); + + @ValidationResult + int addressValidationResult = validateAddress(source, Constants.ADDR_BROADCAST); + if (addressValidationResult != OK) { + return new HdmiCecMessage(source, Constants.ADDR_BROADCAST, + Constants.MESSAGE_REPORT_FEATURES, params, addressValidationResult); + } else { + return new ReportFeaturesMessage(source, Constants.ADDR_BROADCAST, params, + cecVersion, deviceFeatures); + } + } + + @Constants.DeviceType + private static int hdmiDeviceInfoDeviceTypeToShiftValue(int deviceType) { + switch (deviceType) { + case HdmiDeviceInfo.DEVICE_TV: + return Constants.ALL_DEVICE_TYPES_TV; + case HdmiDeviceInfo.DEVICE_RECORDER: + return Constants.ALL_DEVICE_TYPES_RECORDER; + case HdmiDeviceInfo.DEVICE_TUNER: + return Constants.ALL_DEVICE_TYPES_TUNER; + case HdmiDeviceInfo.DEVICE_PLAYBACK: + return Constants.ALL_DEVICE_TYPES_PLAYBACK; + case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM: + return Constants.ALL_DEVICE_TYPES_AUDIO_SYSTEM; + case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH: + return Constants.ALL_DEVICE_TYPES_SWITCH; + default: + throw new IllegalArgumentException("Unhandled device type: " + deviceType); + } + } + + /** + * Must only be called from {@link HdmiCecMessage#build}. + * + * Parses and validates CEC message data as a <Report Features> message. Intended for + * constructing a representation of an incoming message, as it takes raw bytes for parameters. + * + * If successful, returns an instance of {@link ReportFeaturesMessage}. + * If unsuccessful, returns an {@link HdmiCecMessage} with the reason for validation failure + * accessible through {@link HdmiCecMessage#getValidationResult}. + */ + static HdmiCecMessage build(int source, int destination, byte[] params) { + // Helper function for building a message that failed validation + Function<Integer, HdmiCecMessage> invalidMessage = + validationResult -> new HdmiCecMessage(source, destination, + Constants.MESSAGE_REPORT_FEATURES, params, validationResult); + + @ValidationResult int addressValidationResult = validateAddress(source, destination); + if (addressValidationResult != OK) { + return invalidMessage.apply(addressValidationResult); + } + + if (params.length < 4) { + return invalidMessage.apply(ERROR_PARAMETER_SHORT); + } + + int cecVersion = Byte.toUnsignedInt(params[0]); + + int rcProfileEnd = HdmiUtils.getEndOfSequence(params, 2); + if (rcProfileEnd == -1) { + return invalidMessage.apply(ERROR_PARAMETER_SHORT); + } + int deviceFeaturesEnd = HdmiUtils.getEndOfSequence( + params, rcProfileEnd + 1); + if (deviceFeaturesEnd == -1) { + return invalidMessage.apply(ERROR_PARAMETER_SHORT); + } + int deviceFeaturesStart = HdmiUtils.getEndOfSequence(params, 2) + 1; + byte[] deviceFeaturesBytes = Arrays.copyOfRange(params, deviceFeaturesStart, params.length); + DeviceFeatures deviceFeatures = DeviceFeatures.fromOperand(deviceFeaturesBytes); + + return new ReportFeaturesMessage(source, destination, params, cecVersion, deviceFeatures); + } + + /** + * Validates the source and destination addresses for a <Report Features> message. + */ + public static int validateAddress(int source, int destination) { + return HdmiCecMessageValidator.validateAddress(source, destination, + HdmiCecMessageValidator.DEST_BROADCAST); + } + + /** + * Returns the contents of the [CEC Version] operand: the version number of the CEC + * specification which was used to design the device. + */ + @HdmiControlManager.HdmiCecVersion + public int getCecVersion() { + return mCecVersion; + } + + /** + * Returns the contents of the [Device Features] operand: the set of features supported by + * the device. + */ + public DeviceFeatures getDeviceFeatures() { + return mDeviceFeatures; + } +} diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java new file mode 100644 index 000000000000..5154669a90ab --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java @@ -0,0 +1,126 @@ +/* + * 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.hdmi; + +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; + +import static com.android.server.hdmi.Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL; + +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.hardware.tv.cec.V1_0.SendMessageResult; + +/** + * Determines whether a target device supports the <Set Audio Volume Level> message. + * + * Sends the device <Set Audio Volume Level>[0x7F]. The value 0x7F is defined by the spec such that + * setting the volume to this level results in no change to the current volume level. + * + * The target device supports <Set Audio Volume Level> only if it does not respond with + * <Feature Abort> within {@link HdmiConfig.TIMEOUT_MS} milliseconds. + */ +public class SetAudioVolumeLevelDiscoveryAction extends HdmiCecFeatureAction { + private static final String TAG = "SetAudioVolumeLevelDiscoveryAction"; + + private static final int STATE_WAITING_FOR_FEATURE_ABORT = 1; + + private final int mTargetAddress; + + public SetAudioVolumeLevelDiscoveryAction(HdmiCecLocalDevice source, + int targetAddress, IHdmiControlCallback callback) { + super(source, callback); + + mTargetAddress = targetAddress; + } + + boolean start() { + sendCommand(SetAudioVolumeLevelMessage.build( + getSourceAddress(), mTargetAddress, Constants.AUDIO_VOLUME_STATUS_UNKNOWN), + result -> { + if (result == SendMessageResult.SUCCESS) { + // Message sent successfully; wait for <Feature Abort> in response + mState = STATE_WAITING_FOR_FEATURE_ABORT; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + } else { + finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } + }); + return true; + } + + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAITING_FOR_FEATURE_ABORT) { + return false; + } + switch (cmd.getOpcode()) { + case Constants.MESSAGE_FEATURE_ABORT: + return handleFeatureAbort(cmd); + default: + return false; + } + } + + private boolean handleFeatureAbort(HdmiCecMessage cmd) { + int originalOpcode = cmd.getParams()[0] & 0xFF; + if (originalOpcode == MESSAGE_SET_AUDIO_VOLUME_LEVEL && cmd.getSource() == mTargetAddress) { + if (updateAvcSupport(FEATURE_NOT_SUPPORTED)) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } else { + finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); + } + return true; + } + return false; + } + + void handleTimerEvent(int state) { + if (updateAvcSupport(FEATURE_SUPPORTED)) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } else { + finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); + } + } + + /** + * Updates the System Audio device's support for <Set Audio Volume Level> in the + * {@link HdmiCecNetwork}. Can fail if the System Audio device is not in our + * {@link HdmiCecNetwork}. + * + * @return Whether support was successfully updated in the network. + */ + private boolean updateAvcSupport( + @DeviceFeatures.FeatureSupportStatus int setAudioVolumeLevelSupport) { + HdmiCecNetwork network = source().mService.getHdmiCecNetwork(); + HdmiDeviceInfo currentDeviceInfo = network.getCecDeviceInfo(mTargetAddress); + + if (currentDeviceInfo == null) { + return false; + } else { + network.updateCecDevice( + currentDeviceInfo.toBuilder() + .setDeviceFeatures(currentDeviceInfo.getDeviceFeatures().toBuilder() + .setSetAudioVolumeLevelSupport(setAudioVolumeLevelSupport) + .build()) + .build() + ); + return true; + } + } +} diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.java new file mode 100644 index 000000000000..2ec0e7feec32 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.java @@ -0,0 +1,102 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; +import static com.android.server.hdmi.HdmiCecMessageValidator.OK; +import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult; + +/** + * Represents a validated <Set Audio Volume Level> message with parsed parameters. + */ +public class SetAudioVolumeLevelMessage extends HdmiCecMessage { + private final int mAudioVolumeLevel; + + private SetAudioVolumeLevelMessage(int source, int destination, byte[] params, + int audioVolumeLevel) { + super(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, params, OK); + mAudioVolumeLevel = audioVolumeLevel; + } + + /** + * Static factory method. Intended for constructing outgoing or test messages, as it uses + * structured types instead of raw bytes to construct the parameters. + * + * @param source Initiator address. Cannot be {@link Constants#ADDR_UNREGISTERED} + * @param destination Destination address. Cannot be {@link Constants#ADDR_BROADCAST} + * @param audioVolumeLevel [Audio Volume Level]. Either 0x7F (representing no volume change) + * or between 0 and 100 inclusive (representing volume percentage). + */ + public static HdmiCecMessage build(int source, int destination, int audioVolumeLevel) { + byte[] params = { (byte) (audioVolumeLevel & 0xFF) }; + + @ValidationResult + int addressValidationResult = validateAddress(source, destination); + if (addressValidationResult == OK) { + return new SetAudioVolumeLevelMessage(source, destination, params, audioVolumeLevel); + } else { + return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + params, addressValidationResult); + } + } + + /** + * Must only be called from {@link HdmiCecMessage#build}. + * + * Parses and validates CEC message data as a <SetAudioVolumeLevel> message. Intended for + * constructing a representation of an incoming message, as it takes raw bytes for + * parameters. + * + * If successful, returns an instance of {@link SetAudioVolumeLevelMessage}. + * If unsuccessful, returns an {@link HdmiCecMessage} with the reason for validation failure + * accessible through {@link HdmiCecMessage#getValidationResult}. + */ + public static HdmiCecMessage build(int source, int destination, byte[] params) { + if (params.length == 0) { + return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + params, ERROR_PARAMETER_SHORT); + } + + int audioVolumeLevel = params[0]; + + @ValidationResult + int addressValidationResult = validateAddress(source, destination); + if (addressValidationResult == OK) { + return new SetAudioVolumeLevelMessage(source, destination, params, audioVolumeLevel); + } else { + return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + params, addressValidationResult); + } + } + + /** + * Validates the source and destination addresses for a <Set Audio Volume Level> message. + */ + public static int validateAddress(int source, int destination) { + return HdmiCecMessageValidator.validateAddress(source, destination, + HdmiCecMessageValidator.DEST_DIRECT); + } + + /** + * Returns the contents of the [Audio Volume Level] operand: + * either 0x7F, indicating no change to the current volume level, + * or a percentage between 0 and 100 (inclusive). + */ + public int getAudioVolumeLevel() { + return mAudioVolumeLevel; + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 220d790d1208..db13deba1972 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -18,8 +18,6 @@ package com.android.server.inputmethod; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static com.android.server.inputmethod.InputMethodManagerService.MSG_INITIALIZE_IME; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; @@ -264,6 +262,13 @@ final class InputMethodBindingController { } /** + * Returns {@code true} if current IME supports Stylus Handwriting. + */ + boolean supportsStylusHandwriting() { + return mSupportsStylusHw; + } + + /** * Used to bring IME service up to visible adjustment while it is being shown. */ @GuardedBy("ImfLock.class") @@ -302,15 +307,14 @@ final class InputMethodBindingController { return; } if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); - // Dispatch display id for InputMethodService to update context display. - mService.executeOrSendMessage(mCurMethod, - mService.mCaller.obtainMessageIOO(MSG_INITIALIZE_IME, - mMethodMap.get(mSelectedMethodId).getConfigChanges(), - mCurMethod, mCurToken)); + final InputMethodInfo info = mMethodMap.get(mSelectedMethodId); + mSupportsStylusHw = info.supportsStylusHandwriting(); + mService.executeOrSendInitializeIme(mCurMethod, mCurToken, + info.getConfigChanges(), mSupportsStylusHw); mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); mService.reRequestCurrentClientSessionLocked(); } - mSupportsStylusHw = mMethodMap.get(mSelectedMethodId).supportsStylusHandwriting(); + if (mSupportsStylusHw) { // TODO init Handwriting spy. } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index c87dc8987b25..ec49b4768b74 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -219,37 +219,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int FAILURE = -1; } - static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; - static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2; - static final int MSG_SHOW_IM_CONFIG = 3; + private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; + private static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2; + private static final int MSG_SHOW_IM_CONFIG = 3; - static final int MSG_UNBIND_INPUT = 1000; - static final int MSG_BIND_INPUT = 1010; - static final int MSG_SHOW_SOFT_INPUT = 1020; - static final int MSG_HIDE_SOFT_INPUT = 1030; - static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; - static final int MSG_INITIALIZE_IME = 1040; - static final int MSG_CREATE_SESSION = 1050; - static final int MSG_REMOVE_IME_SURFACE = 1060; - static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061; - static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070; + private static final int MSG_UNBIND_INPUT = 1000; + private static final int MSG_BIND_INPUT = 1010; + private static final int MSG_SHOW_SOFT_INPUT = 1020; + private static final int MSG_HIDE_SOFT_INPUT = 1030; + private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; + private static final int MSG_INITIALIZE_IME = 1040; + private static final int MSG_CREATE_SESSION = 1050; + private static final int MSG_REMOVE_IME_SURFACE = 1060; + private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061; + private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070; + private static final int MSG_START_HANDWRITING = 1100; - static final int MSG_START_INPUT = 2000; + private static final int MSG_START_INPUT = 2000; - static final int MSG_UNBIND_CLIENT = 3000; - static final int MSG_BIND_CLIENT = 3010; - static final int MSG_SET_ACTIVE = 3020; - static final int MSG_SET_INTERACTIVE = 3030; - static final int MSG_REPORT_FULLSCREEN_MODE = 3045; + private static final int MSG_UNBIND_CLIENT = 3000; + private static final int MSG_BIND_CLIENT = 3010; + private static final int MSG_SET_ACTIVE = 3020; + private static final int MSG_SET_INTERACTIVE = 3030; + private static final int MSG_REPORT_FULLSCREEN_MODE = 3045; - static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; + private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; - static final int MSG_SYSTEM_UNLOCK_USER = 5000; - static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010; + private static final int MSG_SYSTEM_UNLOCK_USER = 5000; + private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010; - static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000; + private static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000; - static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; + private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; @@ -277,14 +278,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final Context mContext; final Resources mRes; - final Handler mHandler; + private final Handler mHandler; final InputMethodSettings mSettings; final SettingsObserver mSettingsObserver; final IWindowManager mIWindowManager; final WindowManagerInternal mWindowManagerInternal; final PackageManagerInternal mPackageManagerInternal; final InputManagerInternal mInputManagerInternal; - final HandlerCaller mCaller; + private final HandlerCaller mCaller; final boolean mHasFeature; private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap = new ArrayMap<>(); @@ -315,6 +316,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private int mMethodMapUpdateCount = 0; /** + * Tracks requestIds for Stylus handwriting mode. + */ + @GuardedBy("ImfLock.class") + private int mHwRequestId = 0; + + /** * The display id for which the latest startInput was called. */ @GuardedBy("ImfLock.class") @@ -1457,31 +1464,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private static final class MethodCallback extends IInputSessionCallback.Stub { - private final InputMethodManagerService mParentIMMS; - private final IInputMethod mMethod; - private final InputChannel mChannel; - - MethodCallback(InputMethodManagerService imms, IInputMethod method, - InputChannel channel) { - mParentIMMS = imms; - mMethod = method; - mChannel = channel; - } - - @Override - public void sessionCreated(IInputMethodSession session) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.sessionCreated"); - final long ident = Binder.clearCallingIdentity(); - try { - mParentIMMS.onSessionCreated(mMethod, session, mChannel); - } finally { - Binder.restoreCallingIdentity(ident); - } - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - } - private static final class UserSwitchHandlerTask implements Runnable { final InputMethodManagerService mService; @@ -1799,10 +1781,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mShowOngoingImeSwitcherForPhones = mRes.getBoolean( com.android.internal.R.bool.show_ongoing_ime_switcher); if (mShowOngoingImeSwitcherForPhones) { - final InputMethodMenuController.HardKeyboardListener hardKeyboardListener = - mMenuController.getHardKeyboardListener(); - mWindowManagerInternal.setOnHardKeyboardStatusChangeListener( - hardKeyboardListener); + mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(available -> { + mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0) + .sendToTarget(); + }); } mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); @@ -2234,7 +2216,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - void executeOrSendMessage(IInterface target, Message msg) { + private void executeOrSendMessage(IInterface target, Message msg) { if (target.asBinder() instanceof Binder) { mCaller.sendMessage(msg); } else { @@ -2520,39 +2502,52 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @AnyThread + void executeOrSendInitializeIme(@NonNull IInputMethod inputMethod, @NonNull IBinder token, + @android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) { + executeOrSendMessage(inputMethod, mCaller.obtainMessageIOOO(MSG_INITIALIZE_IME, + configChanges, inputMethod, token, supportStylusHw)); + } + + @AnyThread void scheduleNotifyImeUidToAudioService(int uid) { mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE); mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget(); } - void onSessionCreated(IInputMethod method, IInputMethodSession session, - InputChannel channel) { - synchronized (ImfLock.class) { - if (mUserSwitchHandlerTask != null) { - // We have a pending user-switching task so it's better to just ignore this session. - channel.dispose(); - return; - } - IInputMethod curMethod = getCurMethodLocked(); - if (curMethod != null && method != null - && curMethod.asBinder() == method.asBinder()) { - if (mCurClient != null) { - clearClientSessionLocked(mCurClient); - mCurClient.curSession = new SessionState(mCurClient, - method, session, channel); - InputBindResult res = attachNewInputLocked( - StartInputReason.SESSION_CREATED_BY_IME, true); - if (res.method != null) { - executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( - MSG_BIND_CLIENT, mCurClient.client, res)); - } + @BinderThread + void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated"); + try { + synchronized (ImfLock.class) { + if (mUserSwitchHandlerTask != null) { + // We have a pending user-switching task so it's better to just ignore this + // session. + channel.dispose(); return; } + IInputMethod curMethod = getCurMethodLocked(); + if (curMethod != null && method != null + && curMethod.asBinder() == method.asBinder()) { + if (mCurClient != null) { + clearClientSessionLocked(mCurClient); + mCurClient.curSession = new SessionState(mCurClient, + method, session, channel); + InputBindResult res = attachNewInputLocked( + StartInputReason.SESSION_CREATED_BY_IME, true); + if (res.method != null) { + executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( + MSG_BIND_CLIENT, mCurClient.client, res)); + } + return; + } + } } - } - // Session abandoned. Close its associated input channel. - channel.dispose(); + // Session abandoned. Close its associated input channel. + channel.dispose(); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } } @GuardedBy("ImfLock.class") @@ -2589,7 +2584,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub IInputMethod curMethod = getCurMethodLocked(); executeOrSendMessage(curMethod, mCaller.obtainMessageOOO( MSG_CREATE_SESSION, curMethod, channels[1], - new MethodCallback(this, curMethod, channels[0]))); + new IInputSessionCallback.Stub() { + @Override + public void sessionCreated(IInputMethodSession session) { + final long ident = Binder.clearCallingIdentity(); + try { + onSessionCreated(curMethod, session, channels[0]); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + })); } } @@ -3019,21 +3024,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final long ident = Binder.clearCallingIdentity(); try { - if (mCurClient == null || client == null - || mCurClient.client.asBinder() != client.asBinder()) { - // We need to check if this is the current client with - // focus in the window manager, to allow this call to - // be made before input is started in it. - final ClientState cs = mClients.get(client.asBinder()); - if (cs == null) { - throw new IllegalArgumentException( - "unknown client " + client.asBinder()); - } - if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, - cs.selfReportedDisplayId)) { - Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); - return false; - } + if (!canInteractWithImeLocked(uid, client, "showSoftInput")) { + return false; } if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); return showCurrentInputLocked(windowToken, flags, resultReceiver, reason); @@ -3044,6 +3036,40 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override + public void startStylusHandwriting(IInputMethodClient client) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting"); + ImeTracing.getInstance().triggerManagerServiceDump( + "InputMethodManagerService#startStylusHandwriting"); + int uid = Binder.getCallingUid(); + synchronized (ImfLock.class) { + if (!calledFromValidUserLocked()) { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + return; + } + final long ident = Binder.clearCallingIdentity(); + try { + if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting")) { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + return; + } + if (!mBindingController.supportsStylusHandwriting()) { + Slog.w(TAG, "Stylus HW unsupported by IME. Ignoring startStylusHandwriting()"); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + return; + } + if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started"); + if (getCurMethodLocked() != null) { + executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageIO( + MSG_START_HANDWRITING, ++mHwRequestId, getCurMethodLocked())); + } + } finally { + Binder.restoreCallingIdentity(ident); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + } + } + @BinderThread @Override public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) { @@ -3523,6 +3549,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return res; } + @GuardedBy("ImfLock.class") + private boolean canInteractWithImeLocked( + int uid, IInputMethodClient client, String methodName) { + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + // We need to check if this is the current client with + // focus in the window manager, to allow this call to + // be made before input is started in it. + final ClientState cs = mClients.get(client.asBinder()); + if (cs == null) { + throw new IllegalArgumentException("unknown client " + client.asBinder()); + } + if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, + cs.selfReportedDisplayId)) { + Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client)); + return false; + } + } + return true; + } + private boolean shouldRestoreImeVisibility(IBinder windowToken, @SoftInputModeFlags int softInputMode) { switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) { @@ -4237,7 +4284,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final IBinder token = (IBinder) args.arg2; ((IInputMethod) args.arg1).initializeInternal(token, - new InputMethodPrivilegedOperationsImpl(this, token), msg.arg1); + new InputMethodPrivilegedOperationsImpl(this, token), + msg.arg1, (boolean) args.arg3); } catch (RemoteException e) { } args.recycle(); @@ -4366,9 +4414,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // -------------------------------------------------------------- case MSG_HARD_KEYBOARD_SWITCH_CHANGED: - final InputMethodMenuController.HardKeyboardListener hardKeyboardListener = - mMenuController.getHardKeyboardListener(); - hardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1); + mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1); return true; case MSG_SYSTEM_UNLOCK_USER: { final int userId = msg.arg1; @@ -4410,10 +4456,34 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; } + case MSG_START_HANDWRITING: + try { + (((IInputMethod) msg.obj)).canStartStylusHandwriting(msg.arg1); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e); + } + return true; } return false; } + @BinderThread + private void onStylusHandwritingReady(int requestId) { + synchronized (ImfLock.class) { + if (mHwRequestId != requestId) { + // obsolete request + return; + } + + try { + // TODO: replace null with actual Channel, MotionEvents + getCurMethodLocked().startStylusHandwriting(null, null); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling startStylusHandwriting(): ", e); + } + } + } + private void handleSetInteractive(final boolean interactive) { synchronized (ImfLock.class) { mIsInteractive = interactive; @@ -5958,5 +6028,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible) { mImms.applyImeVisibility(mToken, windowToken, setVisible); } + + @BinderThread + @Override + public void onStylusHandwritingReady(int requestId) { + mImms.onStylusHandwritingReady(requestId); + } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 132be7d11ab1..fcb1be0fc88c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -18,7 +18,6 @@ package com.android.server.inputmethod; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.server.inputmethod.InputMethodManagerService.DEBUG; -import static com.android.server.inputmethod.InputMethodManagerService.MSG_HARD_KEYBOARD_SWITCH_CHANGED; import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID; import android.app.ActivityThread; @@ -28,7 +27,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.os.Handler; import android.os.IBinder; import android.text.TextUtils; import android.util.ArrayMap; @@ -62,10 +60,8 @@ public class InputMethodMenuController { private final InputMethodManagerService mService; private final InputMethodUtils.InputMethodSettings mSettings; private final InputMethodSubtypeSwitchingController mSwitchingController; - private final Handler mHandler; private final ArrayMap<String, InputMethodInfo> mMethodMap; private final KeyguardManager mKeyguardManager; - private final HardKeyboardListener mHardKeyboardListener; private final WindowManagerInternal mWindowManagerInternal; private Context mSettingsContext; @@ -83,10 +79,8 @@ public class InputMethodMenuController { mService = service; mSettings = mService.mSettings; mSwitchingController = mService.mSwitchingController; - mHandler = mService.mHandler; mMethodMap = mService.mMethodMap; mKeyguardManager = mService.mKeyguardManager; - mHardKeyboardListener = new HardKeyboardListener(); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); } @@ -271,10 +265,6 @@ public class InputMethodMenuController { } } - HardKeyboardListener getHardKeyboardListener() { - return mHardKeyboardListener; - } - AlertDialog getSwitchingDialogLocked() { return mSwitchingDialog; } @@ -290,24 +280,16 @@ public class InputMethodMenuController { return mSwitchingDialog.isShowing(); } - class HardKeyboardListener implements WindowManagerInternal.OnHardKeyboardStatusChangeListener { - @Override - public void onHardKeyboardStatusChange(boolean available) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, - available ? 1 : 0)); + void handleHardKeyboardStatusChange(boolean available) { + if (DEBUG) { + Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available); } - - public void handleHardKeyboardStatusChange(boolean available) { - if (DEBUG) { - Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available); - } - synchronized (ImfLock.class) { - if (mSwitchingDialog != null && mSwitchingDialogTitleView != null - && mSwitchingDialog.isShowing()) { - mSwitchingDialogTitleView.findViewById( - com.android.internal.R.id.hard_keyboard_section).setVisibility( - available ? View.VISIBLE : View.GONE); - } + synchronized (ImfLock.class) { + if (mSwitchingDialog != null && mSwitchingDialogTitleView != null + && mSwitchingDialog.isShowing()) { + mSwitchingDialogTitleView.findViewById( + com.android.internal.R.id.hard_keyboard_section).setVisibility( + available ? View.VISIBLE : View.GONE); } } } diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 09780f3f6f40..667698767818 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -45,7 +45,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.net.Uri; diff --git a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java new file mode 100644 index 000000000000..282e3c1d4b00 --- /dev/null +++ b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java @@ -0,0 +1,53 @@ +/* + * 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.locales; + +import static android.os.Process.INVALID_UID; + +import com.android.internal.util.FrameworkStatsLog; + +/** + * Holds data used to report the ApplicationLocalesChanged atom. + */ +public final class AppLocaleChangedAtomRecord { + final int mCallingUid; + int mTargetUid = INVALID_UID; + String mNewLocales = ""; + String mPrevLocales = ""; + int mStatus = FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED; + + AppLocaleChangedAtomRecord(int callingUid) { + this.mCallingUid = callingUid; + } + + void setNewLocales(String newLocales) { + this.mNewLocales = newLocales; + } + + void setTargetUid(int targetUid) { + this.mTargetUid = targetUid; + } + + void setPrevLocales(String prevLocales) { + this.mPrevLocales = prevLocales; + } + + void setStatus(int status) { + this.mStatus = status; + } +} diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java index b2bd47b55cfb..134fb967cac5 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java +++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java @@ -21,7 +21,6 @@ import static android.os.UserHandle.USER_NULL; import static com.android.server.locales.LocaleManagerService.DEBUG; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.content.BroadcastReceiver; @@ -34,7 +33,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.BestClock; import android.os.Binder; -import android.os.Environment; import android.os.HandlerThread; import android.os.LocaleList; import android.os.Process; @@ -42,7 +40,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; -import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; import android.util.TypedXmlPullParser; @@ -53,17 +50,12 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.util.XmlUtils; -import libcore.io.IoUtils; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; @@ -71,8 +63,6 @@ import java.time.Clock; import java.time.Duration; import java.time.ZoneOffset; import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Helper class for managing backup and restore of app-specific locales. @@ -87,33 +77,28 @@ class LocaleManagerBackupHelper { private static final String ATTR_LOCALES = "locales"; private static final String ATTR_CREATION_TIME_MILLIS = "creationTimeMillis"; - private static final String STAGE_FILE_NAME = "staged_locales"; private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android"; - - private static final Pattern STAGE_FILE_NAME_PATTERN = Pattern.compile( - TextUtils.formatSimple("(^%s_)(\\d+)(\\.xml$)", STAGE_FILE_NAME)); - private static final int USER_ID_GROUP_INDEX_IN_PATTERN = 2; - private static final Duration STAGE_FILE_RETENTION_PERIOD = Duration.ofDays(3); + // Stage data would be deleted on reboot since it's stored in memory. So it's retained until + // retention period OR next reboot, whichever happens earlier. + private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3); private final LocaleManagerService mLocaleManagerService; private final PackageManagerInternal mPackageManagerInternal; - private final File mStagedLocalesDir; private final Clock mClock; private final Context mContext; private final Object mStagedDataLock = new Object(); // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using // SparseArray because it is more memory-efficient than a HashMap. - private final SparseArray<StagedData> mStagedData = new SparseArray<>(); + private final SparseArray<StagedData> mStagedData; private final PackageMonitor mPackageMonitor; private final BroadcastReceiver mUserMonitor; LocaleManagerBackupHelper(LocaleManagerService localeManagerService, PackageManagerInternal pmInternal) { - this(localeManagerService.mContext, localeManagerService, pmInternal, - new File(Environment.getDataSystemCeDirectory(), - "app_locales"), getDefaultClock()); + this(localeManagerService.mContext, localeManagerService, pmInternal, getDefaultClock(), + new SparseArray<>()); } private static @NonNull Clock getDefaultClock() { @@ -123,14 +108,12 @@ class LocaleManagerBackupHelper { @VisibleForTesting LocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, - PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) { + PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData) { mContext = context; mLocaleManagerService = localeManagerService; mPackageManagerInternal = pmInternal; mClock = clock; - mStagedLocalesDir = stagedLocalesDir; - - loadAllStageFiles(); + mStagedData = stagedData; HandlerThread broadcastHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); @@ -158,67 +141,6 @@ class LocaleManagerBackupHelper { } /** - * Loads the staged data into memory by reading all the files in the staged directory. - * - * <p><b>Note:</b> We don't ned to hold the lock here because this is only called in the - * constructor (before any broadcast receivers are registered). - */ - private void loadAllStageFiles() { - File[] files = mStagedLocalesDir.listFiles(); - if (files == null) { - return; - } - for (File file : files) { - String fileName = file.getName(); - Matcher matcher = STAGE_FILE_NAME_PATTERN.matcher(fileName); - if (!matcher.matches()) { - file.delete(); - Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, - "Unrecognized file")); - continue; - } - try { - final int userId = Integer.parseInt(matcher.group(USER_ID_GROUP_INDEX_IN_PATTERN)); - StagedData stagedData = readStageFile(file); - if (stagedData != null) { - mStagedData.put(userId, stagedData); - } else { - file.delete(); - Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, - "Could not read file")); - } - } catch (NumberFormatException e) { - file.delete(); - Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName, - "Could not parse user id from file name")); - } - } - } - - /** - * Loads the stage file from the disk and parses it into a list of app backups. - */ - private @Nullable StagedData readStageFile(@NonNull File file) { - InputStream stagedDataInputStream = null; - AtomicFile stageFile = new AtomicFile(file); - try { - stagedDataInputStream = stageFile.openRead(); - final TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(stagedDataInputStream, StandardCharsets.UTF_8.name()); - - XmlUtils.beginDocument(parser, LOCALES_XML_TAG); - long creationTimeMillis = parser.getAttributeLong(/* namespace= */ null, - ATTR_CREATION_TIME_MILLIS); - return new StagedData(creationTimeMillis, readFromXml(parser)); - } catch (IOException | XmlPullParserException e) { - Slog.e(TAG, "Could not parse stage file ", e); - } finally { - IoUtils.closeQuietly(stagedDataInputStream); - } - return null; - } - - /** * @see LocaleManagerInternal#getBackupPayload(int userId) */ public byte[] getBackupPayload(int userId) { @@ -261,9 +183,7 @@ class LocaleManagerBackupHelper { final ByteArrayOutputStream out = new ByteArrayOutputStream(); try { - // Passing arbitrary value for creationTimeMillis since it is ignored when forStage - // is false. - writeToXml(out, pkgStates, /* forStage= */ false, /* creationTimeMillis= */ -1); + writeToXml(out, pkgStates); } catch (IOException e) { Slog.e(TAG, "Could not write to xml for backup ", e); return null; @@ -284,7 +204,7 @@ class LocaleManagerBackupHelper { int userId = mStagedData.keyAt(i); StagedData stagedData = mStagedData.get(userId); if (stagedData.mCreationTimeMillis - < mClock.millis() - STAGE_FILE_RETENTION_PERIOD.toMillis()) { + < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) { deleteStagedDataLocked(userId); } } @@ -305,7 +225,7 @@ class LocaleManagerBackupHelper { final ByteArrayInputStream inputStream = new ByteArrayInputStream(payload); - HashMap<String, String> pkgStates = new HashMap<>(); + HashMap<String, String> pkgStates; try { // Parse the input blob into a list of BackupPackageState. final TypedXmlPullParser parser = Xml.newFastPullParser(); @@ -315,6 +235,7 @@ class LocaleManagerBackupHelper { pkgStates = readFromXml(parser); } catch (IOException | XmlPullParserException e) { Slog.e(TAG, "Could not parse payload ", e); + return; } // We need a lock here to prevent race conditions when accessing the stage file. @@ -323,7 +244,7 @@ class LocaleManagerBackupHelper { // performed simultaneously. synchronized (mStagedDataLock) { // Backups for apps which are yet to be installed. - mStagedData.put(userId, new StagedData(mClock.millis(), new HashMap<>())); + StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>()); for (String pkgName : pkgStates.keySet()) { String languageTags = pkgStates.get(pkgName); @@ -333,7 +254,7 @@ class LocaleManagerBackupHelper { checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId); } else { // Stage the data if the app isn't installed. - mStagedData.get(userId).mPackageStates.put(pkgName, languageTags); + stagedData.mPackageStates.put(pkgName, languageTags); if (DEBUG) { Slog.d(TAG, "Add locales=" + languageTags + " package=" + pkgName + " for lazy restore."); @@ -341,7 +262,9 @@ class LocaleManagerBackupHelper { } } - writeStageFileLocked(userId); + if (!stagedData.mPackageStates.isEmpty()) { + mStagedData.put(userId, stagedData); + } } } @@ -396,55 +319,10 @@ class LocaleManagerBackupHelper { } } - /** - * Converts the list of app backups into xml and writes it onto the disk. - */ - private void writeStageFileLocked(int userId) { - StagedData stagedData = mStagedData.get(userId); - if (stagedData.mPackageStates.isEmpty()) { - deleteStagedDataLocked(userId); - return; - } - - final FileOutputStream stagedDataOutputStream; - AtomicFile stageFile = new AtomicFile( - new File(mStagedLocalesDir, - TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId))); - try { - stagedDataOutputStream = stageFile.startWrite(); - } catch (IOException e) { - Slog.e(TAG, "Failed to save stage file"); - return; - } - - try { - writeToXml(stagedDataOutputStream, stagedData.mPackageStates, /* forStage= */ true, - stagedData.mCreationTimeMillis); - stageFile.finishWrite(stagedDataOutputStream); - if (DEBUG) { - Slog.d(TAG, "Stage file written."); - } - } catch (IOException e) { - Slog.e(TAG, "Could not write stage file", e); - stageFile.failWrite(stagedDataOutputStream); - } - } - private void deleteStagedDataLocked(@UserIdInt int userId) { - AtomicFile stageFile = getStageFileIfExistsLocked(userId); - if (stageFile != null) { - stageFile.delete(); - } mStagedData.remove(userId); } - private @Nullable AtomicFile getStageFileIfExistsLocked(@UserIdInt int userId) { - final File stageFile = new File(mStagedLocalesDir, - TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId)); - return stageFile.isFile() ? new AtomicFile(stageFile) - : null; - } - /** * Parses the backup data from the serialized xml input stream. */ @@ -468,15 +346,8 @@ class LocaleManagerBackupHelper { /** * Converts the list of app backup data into a serialized xml stream. - * - * @param forStage Flag to indicate whether this method is called for the purpose of - * staging the data. Note that if this is false, {@code creationTimeMillis} is ignored because - * we only need it for the stage data. - * @param creationTimeMillis The timestamp when the stage data was created. This is required - * to determine when to delete the stage data. */ - private static void writeToXml(OutputStream stream, - @NonNull HashMap<String, String> pkgStates, boolean forStage, long creationTimeMillis) + private static void writeToXml(OutputStream stream, @NonNull HashMap<String, String> pkgStates) throws IOException { if (pkgStates.isEmpty()) { // No need to write anything at all if pkgStates is empty. @@ -488,11 +359,6 @@ class LocaleManagerBackupHelper { out.startDocument(/* encoding= */ null, /* standalone= */ true); out.startTag(/* namespace= */ null, LOCALES_XML_TAG); - if (forStage) { - out.attribute(/* namespace= */ null, ATTR_CREATION_TIME_MILLIS, - Long.toString(creationTimeMillis)); - } - for (String pkg : pkgStates.keySet()) { out.startTag(/* namespace= */ null, PACKAGE_XML_TAG); out.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, pkg); @@ -504,7 +370,7 @@ class LocaleManagerBackupHelper { out.endDocument(); } - private static class StagedData { + static class StagedData { final long mCreationTimeMillis; final HashMap<String, String> mPackageStates; @@ -517,7 +383,7 @@ class LocaleManagerBackupHelper { /** * Broadcast listener to capture user removed event. * - * <p>The stage file is deleted when a user is removed. + * <p>The stage data is deleted when a user is removed. */ private final class UserMonitor extends BroadcastReceiver { @Override @@ -546,6 +412,8 @@ class LocaleManagerBackupHelper { public void onPackageAdded(String packageName, int uid) { try { synchronized (mStagedDataLock) { + cleanStagedDataForOldEntriesLocked(); + int userId = UserHandle.getUserId(uid); if (mStagedData.contains(userId)) { // Perform lazy restore only if the staged data exists. @@ -589,7 +457,7 @@ class LocaleManagerBackupHelper { // Check if the package is installed indeed if (!isPackageInstalledForUser(packageName, userId)) { Slog.e(TAG, packageName + " not installed for user " + userId - + ". Could not restore locales from stage file"); + + ". Could not restore locales from stage data"); return; } @@ -603,8 +471,11 @@ class LocaleManagerBackupHelper { // Remove the restored entry from the staged data list. stagedData.mPackageStates.remove(pkgName); - // Update the file on the disk. - writeStageFileLocked(userId); + + // Remove the stage data entry for user if there are no more packages to restore. + if (stagedData.mPackageStates.isEmpty()) { + mStagedData.remove(userId); + } // No need to loop further after restoring locales because the staged data will // contain at most one entry for the newly added package. diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index 6aabdb5b05df..d459f8df99b6 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -39,6 +39,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityTaskManagerInternal; @@ -148,40 +149,52 @@ public class LocaleManagerService extends SystemService { */ public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId, @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException { - requireNonNull(appPackageName); - requireNonNull(locales); - - //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user. - userId = mActivityManagerInternal.handleIncomingUser( - Binder.getCallingPid(), Binder.getCallingUid(), userId, - false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, - "setApplicationLocales", appPackageName); - - // This function handles two types of set operations: - // 1.) A normal, non-privileged app setting its own locale. - // 2.) A privileged system service setting locales of another package. - // The least privileged case is a normal app performing a set, so check that first and - // set locales if the package name is owned by the app. Next, check if the caller has the - // necessary permission and set locales. - boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId); - if (!isCallerOwner) { - enforceChangeConfigurationPermission(); - } - - final long token = Binder.clearCallingIdentity(); + AppLocaleChangedAtomRecord atomRecordForMetrics = new + AppLocaleChangedAtomRecord(Binder.getCallingUid()); try { - setApplicationLocalesUnchecked(appPackageName, userId, locales); + requireNonNull(appPackageName); + requireNonNull(locales); + atomRecordForMetrics.setNewLocales(locales.toLanguageTags()); + //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user. + userId = mActivityManagerInternal.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), userId, + false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, + "setApplicationLocales", appPackageName); + + // This function handles two types of set operations: + // 1.) A normal, non-privileged app setting its own locale. + // 2.) A privileged system service setting locales of another package. + // The least privileged case is a normal app performing a set, so check that first and + // set locales if the package name is owned by the app. Next, check if the caller has + // the necessary permission and set locales. + boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId, + atomRecordForMetrics); + if (!isCallerOwner) { + enforceChangeConfigurationPermission(atomRecordForMetrics); + } + + final long token = Binder.clearCallingIdentity(); + try { + setApplicationLocalesUnchecked(appPackageName, userId, locales, + atomRecordForMetrics); + } finally { + Binder.restoreCallingIdentity(token); + } } finally { - Binder.restoreCallingIdentity(token); + logMetric(atomRecordForMetrics); } } private void setApplicationLocalesUnchecked(@NonNull String appPackageName, - @UserIdInt int userId, @NonNull LocaleList locales) { + @UserIdInt int userId, @NonNull LocaleList locales, + @NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { if (DEBUG) { Slog.d(TAG, "setApplicationLocales: setting locales for package " + appPackageName + " and user " + userId); } + + atomRecordForMetrics.setPrevLocales(getApplicationLocalesUnchecked(appPackageName, userId) + .toLanguageTags()); final ActivityTaskManagerInternal.PackageConfigurationUpdater updater = mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName, userId); @@ -194,6 +207,11 @@ public class LocaleManagerService extends SystemService { notifyRegisteredReceivers(appPackageName, userId, locales); mBackupHelper.notifyBackupManager(); + atomRecordForMetrics.setStatus( + FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__STATUS__CONFIG_COMMITTED); + } else { + atomRecordForMetrics.setStatus(FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__STATUS__CONFIG_UNCOMMITTED); } } @@ -221,18 +239,13 @@ public class LocaleManagerService extends SystemService { */ private void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId, LocaleList locales) { - try { - String installingPackageName = mContext.getPackageManager() - .getInstallSourceInfo(appPackageName).getInstallingPackageName(); - if (installingPackageName != null) { - Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED, - appPackageName, locales); - //Set package name to ensure that only installer of the app receives this intent. - intent.setPackage(installingPackageName); - mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - } - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Package not found " + appPackageName); + String installingPackageName = getInstallingPackageName(appPackageName); + if (installingPackageName != null) { + Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED, + appPackageName, locales); + //Set package name to ensure that only installer of the app receives this intent. + intent.setPackage(installingPackageName); + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); } } @@ -259,26 +272,48 @@ public class LocaleManagerService extends SystemService { } /** + * Same as {@link LocaleManagerService#isPackageOwnedByCaller(String, int, + * AppLocaleChangedAtomRecord)}, but for methods that do not log locale atom. + */ + private boolean isPackageOwnedByCaller(String appPackageName, int userId) { + return isPackageOwnedByCaller(appPackageName, userId, /* atomRecordForMetrics= */null); + } + + /** * Checks if the package is owned by the calling app or not for the given user id. * * @throws IllegalArgumentException if package not found for given userid */ - private boolean isPackageOwnedByCaller(String appPackageName, int userId) { - final int uid = mPackageManagerInternal - .getPackageUid(appPackageName, /* flags */ 0, userId); + private boolean isPackageOwnedByCaller(String appPackageName, int userId, + @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) { + final int uid = getPackageUid(appPackageName, userId); if (uid < 0) { Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); + if (atomRecordForMetrics != null) { + atomRecordForMetrics.setStatus(FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_INVALID_TARGET_PACKAGE); + } throw new IllegalArgumentException("Unknown package: " + appPackageName + " for user " + userId); } + if (atomRecordForMetrics != null) { + atomRecordForMetrics.setTargetUid(uid); + } //Once valid package found, ignore the userId part for validating package ownership //as apps with INTERACT_ACROSS_USERS permission could be changing locale for different user. return UserHandle.isSameApp(Binder.getCallingUid(), uid); } - private void enforceChangeConfigurationPermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales"); + private void enforceChangeConfigurationPermission(@NonNull AppLocaleChangedAtomRecord + atomRecordForMetrics) { + try { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales"); + } catch (SecurityException e) { + atomRecordForMetrics.setStatus(FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__STATUS__FAILURE_PERMISSION_ABSENT); + throw e; + } } /** @@ -295,13 +330,17 @@ public class LocaleManagerService extends SystemService { false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, "getApplicationLocales", appPackageName); - // This function handles two types of query operations: + // This function handles three types of query operations: // 1.) A normal, non-privileged app querying its own locale. - // 2.) A privileged system service querying locales of another package. + // 2.) The installer of the given app querying locales of a package installed + // by said installer. + // 3.) A privileged system service querying locales of another package. // The least privileged case is a normal app performing a query, so check that first and - // get locales if the package name is owned by the app. Next, check if the caller has the - // necessary permission and get locales. - if (!isPackageOwnedByCaller(appPackageName, userId)) { + // get locales if the package name is owned by the app. Next check if the calling app + // is the installer of the given app and get locales. If neither conditions matched, + // check if the caller has the necessary permission and fetch locales. + if (!isPackageOwnedByCaller(appPackageName, userId) + && !isCallerInstaller(appPackageName, userId)) { enforceReadAppSpecificLocalesPermission(); } final long token = Binder.clearCallingIdentity(); @@ -312,6 +351,7 @@ public class LocaleManagerService extends SystemService { } } + @NonNull private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName, @UserIdInt int userId) { if (DEBUG) { @@ -332,12 +372,41 @@ public class LocaleManagerService extends SystemService { return locales != null ? locales : LocaleList.getEmptyLocaleList(); } + /** + * Checks if the calling app is the installer of the app whose locale changed. + */ + private boolean isCallerInstaller(String appPackageName, int userId) { + String installingPackageName = getInstallingPackageName(appPackageName); + if (installingPackageName != null) { + // Get the uid of installer-on-record to compare with the calling uid. + int installerUid = getPackageUid(installingPackageName, userId); + return installerUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), installerUid); + } + return false; + } + private void enforceReadAppSpecificLocalesPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.READ_APP_SPECIFIC_LOCALES, "getApplicationLocales"); } + private int getPackageUid(String appPackageName, int userId) { + return mPackageManagerInternal + .getPackageUid(appPackageName, /* flags */ 0, userId); + } + + @Nullable + private String getInstallingPackageName(String packageName) { + try { + return mContext.getPackageManager() + .getInstallSourceInfo(packageName).getInstallingPackageName(); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Package not found " + packageName); + } + return null; + } + /** * Dumps useful info related to service. */ @@ -345,4 +414,13 @@ public class LocaleManagerService extends SystemService { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; // TODO(b/201766221): Implement when there is state. } + + private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) { + FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED, + atomRecordForMetrics.mCallingUid, + atomRecordForMetrics.mTargetUid, + atomRecordForMetrics.mNewLocales, + atomRecordForMetrics.mPrevLocales, + atomRecordForMetrics.mStatus); + } } diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index f1141845f2bd..c02411ec4c1b 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -16,14 +16,15 @@ package com.android.server.location.gnss; -import static android.content.pm.PackageManager.FEATURE_WATCH; - import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; +import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.location.provider.ProviderProperties.ACCURACY_FINE; import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_GSM_CELLID; +import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_LTE_CELLID; +import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_NR_CELLID; import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_UMTS_CELLID; import static com.android.server.location.gnss.hal.GnssNative.AGPS_SETID_TYPE_IMSI; import static com.android.server.location.gnss.hal.GnssNative.AGPS_SETID_TYPE_MSISDN; @@ -84,9 +85,18 @@ import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.telephony.CarrierConfigManager; +import android.telephony.CellIdentity; +import android.telephony.CellIdentityGsm; +import android.telephony.CellIdentityLte; +import android.telephony.CellIdentityNr; +import android.telephony.CellIdentityWcdma; +import android.telephony.CellInfo; +import android.telephony.CellInfoGsm; +import android.telephony.CellInfoLte; +import android.telephony.CellInfoNr; +import android.telephony.CellInfoWcdma; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; @@ -110,6 +120,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -1136,7 +1147,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements if (DEBUG) { Log.d(TAG, "startBatching " + mFixInterval + " " + batchLengthMs); } - if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), true)) { + if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), 0, true)) { mBatchingStarted = true; if (batchSize < getBatchSize()) { @@ -1386,29 +1397,127 @@ public class GnssLocationProvider extends AbstractLocationProvider implements postWithWakeLockHeld(mNtpTimeHelper::retrieveAndInjectNtpTime); } + + private static int getCellType(CellInfo ci) { + if (ci instanceof CellInfoGsm) { + return CellInfo.TYPE_GSM; + } else if (ci instanceof CellInfoWcdma) { + return CellInfo.TYPE_WCDMA; + } else if (ci instanceof CellInfoLte) { + return CellInfo.TYPE_LTE; + } else if (ci instanceof CellInfoNr) { + return CellInfo.TYPE_NR; + } + return CellInfo.TYPE_UNKNOWN; + } + + /** + * Extract the CID/CI for GSM/WCDMA/LTE/NR + * + * @return the cell ID or -1 if invalid + */ + private static long getCidFromCellIdentity(CellIdentity id) { + if (id == null) return -1; + long cid = -1; + switch(id.getType()) { + case CellInfo.TYPE_GSM: cid = ((CellIdentityGsm) id).getCid(); break; + case CellInfo.TYPE_WCDMA: cid = ((CellIdentityWcdma) id).getCid(); break; + case CellInfo.TYPE_LTE: cid = ((CellIdentityLte) id).getCi(); break; + case CellInfo.TYPE_NR: cid = ((CellIdentityNr) id).getNci(); break; + default: break; + } + // If the CID is unreported + if (cid == (id.getType() == CellInfo.TYPE_NR + ? CellInfo.UNAVAILABLE_LONG : CellInfo.UNAVAILABLE)) { + cid = -1; + } + + return cid; + } + + private void setRefLocation(int type, CellIdentity ci) { + String mcc_str = ci.getMccString(); + String mnc_str = ci.getMncString(); + int mcc = mcc_str != null ? Integer.parseInt(mcc_str) : CellInfo.UNAVAILABLE; + int mnc = mnc_str != null ? Integer.parseInt(mnc_str) : CellInfo.UNAVAILABLE; + int lac = CellInfo.UNAVAILABLE; + int tac = CellInfo.UNAVAILABLE; + int pcid = CellInfo.UNAVAILABLE; + int arfcn = CellInfo.UNAVAILABLE; + long cid = CellInfo.UNAVAILABLE_LONG; + + switch (type) { + case AGPS_REF_LOCATION_TYPE_GSM_CELLID: + CellIdentityGsm cig = (CellIdentityGsm) ci; + cid = cig.getCid(); + lac = cig.getLac(); + break; + case AGPS_REF_LOCATION_TYPE_UMTS_CELLID: + CellIdentityWcdma ciw = (CellIdentityWcdma) ci; + cid = ciw.getCid(); + lac = ciw.getLac(); + break; + case AGPS_REF_LOCATION_TYPE_LTE_CELLID: + CellIdentityLte cil = (CellIdentityLte) ci; + cid = cil.getCi(); + tac = cil.getTac(); + pcid = cil.getPci(); + break; + case AGPS_REF_LOCATION_TYPE_NR_CELLID: + CellIdentityNr cin = (CellIdentityNr) ci; + cid = cin.getNci(); + tac = cin.getTac(); + pcid = cin.getPci(); + arfcn = cin.getNrarfcn(); + break; + default: + } + + mGnssNative.setAgpsReferenceLocationCellId( + type, mcc, mnc, lac, cid, tac, pcid, arfcn); + } + private void requestRefLocation() { TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + final int phoneType = phone.getPhoneType(); if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { - GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation(); - if ((gsm_cell != null) && (phone.getNetworkOperator() != null) - && (phone.getNetworkOperator().length() > 3)) { - int type; - int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0, 3)); - int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3)); - int networkType = phone.getNetworkType(); - if (networkType == TelephonyManager.NETWORK_TYPE_UMTS - || networkType == TelephonyManager.NETWORK_TYPE_HSDPA - || networkType == TelephonyManager.NETWORK_TYPE_HSUPA - || networkType == TelephonyManager.NETWORK_TYPE_HSPA - || networkType == TelephonyManager.NETWORK_TYPE_HSPAP) { - type = AGPS_REF_LOCATION_TYPE_UMTS_CELLID; + + List<CellInfo> cil = phone.getAllCellInfo(); + if (cil != null) { + HashMap<Integer, CellIdentity> cellIdentityMap = new HashMap<>(); + cil.sort(Comparator.comparingInt( + (CellInfo ci) -> ci.getCellSignalStrength().getAsuLevel()).reversed()); + + for (CellInfo ci : cil) { + int status = ci.getCellConnectionStatus(); + if (status == CellInfo.CONNECTION_PRIMARY_SERVING + || status == CellInfo.CONNECTION_SECONDARY_SERVING) { + CellIdentity c = ci.getCellIdentity(); + int t = getCellType(ci); + if (getCidFromCellIdentity(c) != -1 + && !cellIdentityMap.containsKey(t)) { + cellIdentityMap.put(t, c); + } + } + } + + if (cellIdentityMap.containsKey(CellInfo.TYPE_GSM)) { + setRefLocation(AGPS_REF_LOCATION_TYPE_GSM_CELLID, + cellIdentityMap.get(CellInfo.TYPE_GSM)); + } else if (cellIdentityMap.containsKey(CellInfo.TYPE_WCDMA)) { + setRefLocation(AGPS_REF_LOCATION_TYPE_UMTS_CELLID, + cellIdentityMap.get(CellInfo.TYPE_WCDMA)); + } else if (cellIdentityMap.containsKey(CellInfo.TYPE_LTE)) { + setRefLocation(AGPS_REF_LOCATION_TYPE_LTE_CELLID, + cellIdentityMap.get(CellInfo.TYPE_LTE)); + } else if (cellIdentityMap.containsKey(CellInfo.TYPE_NR)) { + setRefLocation(AGPS_REF_LOCATION_TYPE_NR_CELLID, + cellIdentityMap.get(CellInfo.TYPE_NR)); } else { - type = AGPS_REF_LOCATION_TYPE_GSM_CELLID; + Log.e(TAG, "No available serving cell information."); } - mGnssNative.setAgpsReferenceLocationCellId(type, mcc, mnc, gsm_cell.getLac(), - gsm_cell.getCid()); } else { Log.e(TAG, "Error getting cell location info."); } diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index a513e0898344..e072bf7dc1f7 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -124,9 +124,12 @@ public class GnssNative { // IMPORTANT - must match OEM definitions, this isn't part of a hal for some reason public static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1; public static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2; + public static final int AGPS_REF_LOCATION_TYPE_LTE_CELLID = 4; + public static final int AGPS_REF_LOCATION_TYPE_NR_CELLID = 8; @IntDef(prefix = "AGPS_REF_LOCATION_TYPE_", value = {AGPS_REF_LOCATION_TYPE_GSM_CELLID, - AGPS_REF_LOCATION_TYPE_UMTS_CELLID}) + AGPS_REF_LOCATION_TYPE_UMTS_CELLID, AGPS_REF_LOCATION_TYPE_LTE_CELLID, + AGPS_REF_LOCATION_TYPE_NR_CELLID}) @Retention(RetentionPolicy.SOURCE) public @interface AgpsReferenceLocationType {} @@ -814,9 +817,10 @@ public class GnssNative { /** * Start batching. */ - public boolean startBatch(long periodNanos, boolean wakeOnFifoFull) { + public boolean startBatch(long periodNanos, float minUpdateDistanceMeters, + boolean wakeOnFifoFull) { Preconditions.checkState(mRegistered); - return mGnssHal.startBatch(periodNanos, wakeOnFifoFull); + return mGnssHal.startBatch(periodNanos, minUpdateDistanceMeters, wakeOnFifoFull); } /** @@ -930,9 +934,9 @@ public class GnssNative { * Sets AGPS reference cell id location. */ public void setAgpsReferenceLocationCellId(@AgpsReferenceLocationType int type, int mcc, - int mnc, int lac, int cid) { + int mnc, int lac, long cid, int tac, int pcid, int arfcn) { Preconditions.checkState(mRegistered); - mGnssHal.setAgpsReferenceLocationCellId(type, mcc, mnc, lac, cid); + mGnssHal.setAgpsReferenceLocationCellId(type, mcc, mnc, lac, cid, tac, pcid, arfcn); } /** @@ -1377,8 +1381,9 @@ public class GnssNative { native_cleanup_batching(); } - protected boolean startBatch(long periodNanos, boolean wakeOnFifoFull) { - return native_start_batch(periodNanos, wakeOnFifoFull); + protected boolean startBatch(long periodNanos, float minUpdateDistanceMeters, + boolean wakeOnFifoFull) { + return native_start_batch(periodNanos, minUpdateDistanceMeters, wakeOnFifoFull); } protected void flushBatch() { @@ -1433,8 +1438,8 @@ public class GnssNative { } protected void setAgpsReferenceLocationCellId(@AgpsReferenceLocationType int type, int mcc, - int mnc, int lac, int cid) { - native_agps_set_ref_location_cellid(type, mcc, mnc, lac, cid); + int mnc, int lac, long cid, int tac, int pcid, int arfcn) { + native_agps_set_ref_location_cellid(type, mcc, mnc, lac, cid, tac, pcid, arfcn); } protected boolean isPsdsSupported() { @@ -1536,7 +1541,8 @@ public class GnssNative { private static native void native_cleanup_batching(); - private static native boolean native_start_batch(long periodNanos, boolean wakeOnFifoFull); + private static native boolean native_start_batch(long periodNanos, + float minUpdateDistanceMeters, boolean wakeOnFifoFull); private static native void native_flush_batch(); @@ -1575,7 +1581,7 @@ public class GnssNative { private static native void native_agps_set_id(int type, String setid); private static native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc, - int lac, int cid); + int lac, long cid, int tac, int pcid, int arfcn); // PSDS APIs diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index fab11a16f399..682a27adc15f 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -39,7 +39,10 @@ import static com.android.internal.widget.LockPatternUtils.USER_FRP; import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE; import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled; import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential; +import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG; +import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -123,6 +126,8 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.ILockSettings; +import com.android.internal.widget.IWeakEscrowTokenActivatedListener; +import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.internal.widget.LockscreenCredential; @@ -135,6 +140,7 @@ import com.android.server.locksettings.LockSettingsStorage.CredentialHash; import com.android.server.locksettings.LockSettingsStorage.PersistentData; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; +import com.android.server.locksettings.SyntheticPasswordManager.TokenType; import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -1123,6 +1129,16 @@ public class LockSettingsService extends ILockSettings.Stub { return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; } + private void checkManageWeakEscrowTokenMethodUsage() { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN, + "Requires MANAGE_WEAK_ESCROW_TOKEN permission."); + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { + throw new IllegalArgumentException( + "Weak escrow token are only for automotive devices."); + } + } + @Override public boolean hasSecureLockScreen() { return mHasSecureLockScreen; @@ -1911,6 +1927,97 @@ public class LockSettingsService extends ILockSettings.Stub { }); } + /** Register the given WeakEscrowTokenRemovedListener. */ + @Override + public boolean registerWeakEscrowTokenRemovedListener( + @NonNull IWeakEscrowTokenRemovedListener listener) { + checkManageWeakEscrowTokenMethodUsage(); + final long token = Binder.clearCallingIdentity(); + try { + return mSpManager.registerWeakEscrowTokenRemovedListener(listener); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** Unregister the given WeakEscrowTokenRemovedListener. */ + @Override + public boolean unregisterWeakEscrowTokenRemovedListener( + @NonNull IWeakEscrowTokenRemovedListener listener) { + checkManageWeakEscrowTokenMethodUsage(); + final long token = Binder.clearCallingIdentity(); + try { + return mSpManager.unregisterWeakEscrowTokenRemovedListener(listener); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public long addWeakEscrowToken(byte[] token, int userId, + @NonNull IWeakEscrowTokenActivatedListener listener) { + checkManageWeakEscrowTokenMethodUsage(); + Objects.requireNonNull(listener, "Listener can not be null."); + EscrowTokenStateChangeCallback internalListener = (handle, userId1) -> { + try { + listener.onWeakEscrowTokenActivated(handle, userId1); + } catch (RemoteException e) { + Slog.e(TAG, "Exception while notifying weak escrow token has been activated", e); + } + }; + final long restoreToken = Binder.clearCallingIdentity(); + try { + return addEscrowToken(token, TOKEN_TYPE_WEAK, userId, internalListener); + } finally { + Binder.restoreCallingIdentity(restoreToken); + } + } + + @Override + public boolean removeWeakEscrowToken(long handle, int userId) { + checkManageWeakEscrowTokenMethodUsage(); + final long token = Binder.clearCallingIdentity(); + try { + return removeEscrowToken(handle, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean isWeakEscrowTokenActive(long handle, int userId) { + checkManageWeakEscrowTokenMethodUsage(); + final long token = Binder.clearCallingIdentity(); + try { + return isEscrowTokenActive(handle, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) { + checkManageWeakEscrowTokenMethodUsage(); + final long restoreToken = Binder.clearCallingIdentity(); + try { + synchronized (mSpManager) { + if (!mSpManager.hasEscrowData(userId)) { + Slog.w(TAG, "Escrow token is disabled on the current user"); + return false; + } + AuthenticationResult authResult = mSpManager.unwrapWeakTokenBasedSyntheticPassword( + getGateKeeperService(), handle, token, userId); + if (authResult.authToken == null) { + Slog.w(TAG, "Invalid escrow token supplied"); + return false; + } + return true; + } + } finally { + Binder.restoreCallingIdentity(restoreToken); + } + } + @VisibleForTesting /** Note: this method is overridden in unit tests */ protected void tieProfileLockToParent(int userId, LockscreenCredential password) { if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId); @@ -3029,6 +3136,7 @@ public class LockSettingsService extends ILockSettings.Stub { private long setLockCredentialWithAuthTokenLocked(LockscreenCredential credential, AuthenticationToken auth, int userId) { if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId); + final int savedCredentialType = getCredentialTypeInternal(userId); long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(), credential, auth, userId); final Map<Integer, LockscreenCredential> profilePasswords; @@ -3075,6 +3183,9 @@ public class LockSettingsService extends ILockSettings.Stub { setUserPasswordMetrics(credential, userId); mManagedProfilePasswordCache.removePassword(userId); + if (savedCredentialType != CREDENTIAL_TYPE_NONE) { + mSpManager.destroyAllWeakTokenBasedSyntheticPasswords(userId); + } if (profilePasswords != null) { for (Map.Entry<Integer, LockscreenCredential> entry : profilePasswords.entrySet()) { @@ -3242,8 +3353,9 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private long addEscrowToken(byte[] token, int userId, EscrowTokenStateChangeCallback callback) { - if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId); + private long addEscrowToken(@NonNull byte[] token, @TokenType int type, int userId, + @NonNull EscrowTokenStateChangeCallback callback) { + if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId + ", type=" + type); synchronized (mSpManager) { // Migrate to synthetic password based credentials if the user has no password, // the token can then be activated immediately. @@ -3264,7 +3376,8 @@ public class LockSettingsService extends ILockSettings.Stub { throw new SecurityException("Escrow token is disabled on the current user"); } } - long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId, callback); + long handle = mSpManager.createTokenBasedSyntheticPassword(token, type, userId, + callback); if (auth != null) { mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId); } @@ -3345,8 +3458,8 @@ public class LockSettingsService extends ILockSettings.Stub { private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential, long tokenHandle, byte[] token, int userId) { final AuthenticationResult result; - result = mSpManager.unwrapTokenBasedSyntheticPassword( - getGateKeeperService(), tokenHandle, token, userId); + result = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle, + token, userId); if (result.authToken == null) { Slog.w(TAG, "Invalid escrow token supplied"); return false; @@ -3369,7 +3482,8 @@ public class LockSettingsService extends ILockSettings.Stub { AuthenticationResult authResult; synchronized (mSpManager) { if (!mSpManager.hasEscrowData(userId)) { - throw new SecurityException("Escrow token is disabled on the current user"); + Slog.w(TAG, "Escrow token is disabled on the current user"); + return false; } authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle, token, userId); @@ -3643,7 +3757,8 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public long addEscrowToken(byte[] token, int userId, EscrowTokenStateChangeCallback callback) { - return LockSettingsService.this.addEscrowToken(token, userId, callback); + return LockSettingsService.this.addEscrowToken(token, TOKEN_TYPE_STRONG, userId, + callback); } @Override diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index 601a5727e545..2da443116a42 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -18,6 +18,7 @@ package com.android.server.locksettings; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.PasswordMetrics; @@ -28,6 +29,7 @@ import android.hardware.weaver.V1_0.WeaverConfig; import android.hardware.weaver.V1_0.WeaverReadResponse; import android.hardware.weaver.V1_0.WeaverReadStatus; import android.hardware.weaver.V1_0.WeaverStatus; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserManager; import android.security.GateKeeper; @@ -41,6 +43,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.ICheckCredentialProgressCallback; +import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; @@ -48,6 +51,8 @@ import com.android.server.locksettings.LockSettingsStorage.PersistentData; import libcore.util.HexEncoding; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -111,7 +116,8 @@ public class SyntheticPasswordManager { private static final byte SYNTHETIC_PASSWORD_VERSION_V2 = 2; private static final byte SYNTHETIC_PASSWORD_VERSION_V3 = 3; private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0; - private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1; + private static final byte SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED = 1; + private static final byte SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED = 2; // 256-bit synthetic password private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8; @@ -366,10 +372,47 @@ public class SyntheticPasswordManager { } } + static class SyntheticPasswordBlob { + byte mVersion; + byte mType; + byte[] mContent; + + public static SyntheticPasswordBlob create(byte version, byte type, byte[] content) { + SyntheticPasswordBlob result = new SyntheticPasswordBlob(); + result.mVersion = version; + result.mType = type; + result.mContent = content; + return result; + } + + public static SyntheticPasswordBlob fromBytes(byte[] data) { + SyntheticPasswordBlob result = new SyntheticPasswordBlob(); + result.mVersion = data[0]; + result.mType = data[1]; + result.mContent = Arrays.copyOfRange(data, 2, data.length); + return result; + } + + public byte[] toByte() { + byte[] blob = new byte[mContent.length + 1 + 1]; + blob[0] = mVersion; + blob[1] = mType; + System.arraycopy(mContent, 0, blob, 2, mContent.length); + return blob; + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({TOKEN_TYPE_STRONG, TOKEN_TYPE_WEAK}) + @interface TokenType {} + static final int TOKEN_TYPE_STRONG = 0; + static final int TOKEN_TYPE_WEAK = 1; + static class TokenData { byte[] secdiscardableOnDisk; byte[] weaverSecret; byte[] aggregatedSecret; + @TokenType int mType; EscrowTokenStateChangeCallback mCallback; } @@ -381,6 +424,9 @@ public class SyntheticPasswordManager { private final UserManager mUserManager; + private final RemoteCallbackList<IWeakEscrowTokenRemovedListener> mListeners = + new RemoteCallbackList<>(); + public SyntheticPasswordManager(Context context, LockSettingsStorage storage, UserManager userManager, PasswordSlotManager passwordSlotManager) { mContext = context; @@ -880,13 +926,34 @@ public class SyntheticPasswordManager { * Create a token based Synthetic password for the given user. * @return the handle of the token */ - public long createTokenBasedSyntheticPassword(byte[] token, int userId, + public long createStrongTokenBasedSyntheticPassword(byte[] token, int userId, + @Nullable EscrowTokenStateChangeCallback changeCallback) { + return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_STRONG, userId, + changeCallback); + } + + /** + * Create a weak token based Synthetic password for the given user. + * @return the handle of the weak token + */ + public long createWeakTokenBasedSyntheticPassword(byte[] token, int userId, + @Nullable EscrowTokenStateChangeCallback changeCallback) { + return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_WEAK, userId, + changeCallback); + } + + /** + * Create a token based Synthetic password of the given type for the given user. + * @return the handle of the token + */ + public long createTokenBasedSyntheticPassword(byte[] token, @TokenType int type, int userId, @Nullable EscrowTokenStateChangeCallback changeCallback) { long handle = generateHandle(); if (!tokenMap.containsKey(userId)) { tokenMap.put(userId, new ArrayMap<>()); } TokenData tokenData = new TokenData(); + tokenData.mType = type; final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH); if (isWeaverAvailable()) { tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize); @@ -910,6 +977,7 @@ public class SyntheticPasswordManager { return new ArraySet<>(tokenMap.get(userId).keySet()); } + /** Remove the given pending token. */ public boolean removePendingToken(long handle, int userId) { if (!tokenMap.containsKey(userId)) { return false; @@ -941,7 +1009,7 @@ public class SyntheticPasswordManager { mPasswordSlotManager.markSlotInUse(slot); } saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId); - createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken, + createSyntheticPasswordBlob(handle, getTokenBasedBlobType(tokenData.mType), authToken, tokenData.aggregatedSecret, 0L, userId); tokenMap.get(userId).remove(handle); if (tokenData.mCallback != null) { @@ -953,26 +1021,23 @@ public class SyntheticPasswordManager { private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken, byte[] applicationId, long sid, int userId) { final byte[] secret; - if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { + if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED + || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) { secret = authToken.getEscrowSecret(); } else { secret = authToken.getSyntheticPassword(); } byte[] content = createSPBlob(getKeyName(handle), secret, applicationId, sid); - byte[] blob = new byte[content.length + 1 + 1]; /* * We can upgrade from v1 to v2 because that's just a change in the way that * the SP is stored. However, we can't upgrade to v3 because that is a change * in the way that passwords are derived from the SP. */ - if (authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3) { - blob[0] = SYNTHETIC_PASSWORD_VERSION_V3; - } else { - blob[0] = SYNTHETIC_PASSWORD_VERSION_V2; - } - blob[1] = type; - System.arraycopy(content, 0, blob, 2, content.length); - saveState(SP_BLOB_NAME, blob, handle, userId); + byte version = authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3 + ? SYNTHETIC_PASSWORD_VERSION_V3 : SYNTHETIC_PASSWORD_VERSION_V2; + + SyntheticPasswordBlob blob = SyntheticPasswordBlob.create(version, type, content); + saveState(SP_BLOB_NAME, blob.toByte(), handle, userId); } /** @@ -1089,6 +1154,36 @@ public class SyntheticPasswordManager { */ public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword( IGateKeeperService gatekeeper, long handle, byte[] token, int userId) { + SyntheticPasswordBlob blob = SyntheticPasswordBlob + .fromBytes(loadState(SP_BLOB_NAME, handle, userId)); + return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle, + blob.mType, token, userId); + } + + /** + * Decrypt a synthetic password by supplying an strong escrow token and corresponding token + * blob handle generated previously. If the decryption is successful, initiate a GateKeeper + * verification to referesh the SID & Auth token maintained by the system. + */ + public @NonNull AuthenticationResult unwrapStrongTokenBasedSyntheticPassword( + IGateKeeperService gatekeeper, long handle, byte[] token, int userId) { + return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle, + SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED, token, userId); + } + + /** + * Decrypt a synthetic password by supplying a weak escrow token and corresponding token + * blob handle generated previously. If the decryption is successful, initiate a GateKeeper + * verification to referesh the SID & Auth token maintained by the system. + */ + public @NonNull AuthenticationResult unwrapWeakTokenBasedSyntheticPassword( + IGateKeeperService gatekeeper, long handle, byte[] token, int userId) { + return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle, + SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED, token, userId); + } + + private @NonNull AuthenticationResult unwrapTokenBasedSyntheticPasswordInternal( + IGateKeeperService gatekeeper, long handle, byte type, byte[] token, int userId) { AuthenticationResult result = new AuthenticationResult(); byte[] secdiscardable = loadSecdiscardable(handle, userId); int slotId = loadWeaverSlot(handle, userId); @@ -1109,8 +1204,7 @@ public class SyntheticPasswordManager { PERSONALISATION_WEAVER_TOKEN, secdiscardable); } byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable); - result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, - applicationId, 0L, userId); + result.authToken = unwrapSyntheticPasswordBlob(handle, type, applicationId, 0L, userId); if (result.authToken != null) { result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); if (result.gkResponse == null) { @@ -1126,33 +1220,33 @@ public class SyntheticPasswordManager { private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type, byte[] applicationId, long sid, int userId) { - byte[] blob = loadState(SP_BLOB_NAME, handle, userId); - if (blob == null) { + byte[] data = loadState(SP_BLOB_NAME, handle, userId); + if (data == null) { return null; } - final byte version = blob[0]; - if (version != SYNTHETIC_PASSWORD_VERSION_V3 - && version != SYNTHETIC_PASSWORD_VERSION_V2 - && version != SYNTHETIC_PASSWORD_VERSION_V1) { + SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(data); + if (blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V3 + && blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V2 + && blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V1) { throw new IllegalArgumentException("Unknown blob version"); } - if (blob[1] != type) { + if (blob.mType != type) { throw new IllegalArgumentException("Invalid blob type"); } final byte[] secret; - if (version == SYNTHETIC_PASSWORD_VERSION_V1) { - secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle), - Arrays.copyOfRange(blob, 2, blob.length), applicationId); + if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) { + secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle), blob.mContent, + applicationId); } else { - secret = decryptSPBlob(getKeyName(handle), - Arrays.copyOfRange(blob, 2, blob.length), applicationId); + secret = decryptSPBlob(getKeyName(handle), blob.mContent, applicationId); } if (secret == null) { Slog.e(TAG, "Fail to decrypt SP for user " + userId); return null; } - AuthenticationToken result = new AuthenticationToken(version); - if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { + AuthenticationToken result = new AuthenticationToken(blob.mVersion); + if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED + || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) { if (!loadEscrowData(result, userId)) { Slog.e(TAG, "User is not escrowable: " + userId); return null; @@ -1161,7 +1255,7 @@ public class SyntheticPasswordManager { } else { result.recreateDirectly(secret); } - if (version == SYNTHETIC_PASSWORD_VERSION_V1) { + if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) { Slog.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type); createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId); } @@ -1233,9 +1327,28 @@ public class SyntheticPasswordManager { return hasState(SP_BLOB_NAME, handle, userId); } + /** Destroy the escrow token with the given handle for the given user. */ public void destroyTokenBasedSyntheticPassword(long handle, int userId) { + SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME, handle, + userId)); destroySyntheticPassword(handle, userId); destroyState(SECDISCARDABLE_NAME, handle, userId); + if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) { + notifyWeakEscrowTokenRemovedListeners(handle, userId); + } + } + + /** Destroy all weak escrow tokens for the given user. */ + public void destroyAllWeakTokenBasedSyntheticPasswords(int userId) { + List<Long> handles = mStorage.listSyntheticPasswordHandlesForUser(SECDISCARDABLE_NAME, + userId); + for (long handle: handles) { + SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME, + handle, userId)); + if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) { + destroyTokenBasedSyntheticPassword(handle, userId); + } + } } public void destroyPasswordBasedSyntheticPassword(long handle, int userId) { @@ -1285,6 +1398,16 @@ public class SyntheticPasswordManager { return loadState(SECDISCARDABLE_NAME, handle, userId); } + private byte getTokenBasedBlobType(@TokenType int type) { + switch (type) { + case TOKEN_TYPE_WEAK: + return SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED; + case TOKEN_TYPE_STRONG: + default: + return SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED; + } + } + /** * Retrieves the saved password metrics associated with a SP handle. Only meaningful to be * called on the handle of a password-based synthetic password. A valid AuthenticationToken for @@ -1439,4 +1562,33 @@ public class SyntheticPasswordManager { } return success; } + + /** Register the given IWeakEscrowTokenRemovedListener. */ + public boolean registerWeakEscrowTokenRemovedListener( + IWeakEscrowTokenRemovedListener listener) { + return mListeners.register(listener); + } + + /** Unregister the given IWeakEscrowTokenRemovedListener. */ + public boolean unregisterWeakEscrowTokenRemovedListener( + IWeakEscrowTokenRemovedListener listener) { + return mListeners.unregister(listener); + } + + private void notifyWeakEscrowTokenRemovedListeners(long handle, int userId) { + int i = mListeners.beginBroadcast(); + try { + while (i > 0) { + i--; + try { + mListeners.getBroadcastItem(i).onWeakEscrowTokenRemoved(handle, userId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception while notifying WeakEscrowTokenRemovedListener.", + e); + } + } + } finally { + mListeners.finishBroadcast(); + } + } } diff --git a/services/core/java/com/android/server/media/OWNERS b/services/core/java/com/android/server/media/OWNERS index 2e2d812c058e..8097f4e9b329 100644 --- a/services/core/java/com/android/server/media/OWNERS +++ b/services/core/java/com/android/server/media/OWNERS @@ -1,8 +1,6 @@ +# Bug component: 137631 elaurent@google.com -hdmoon@google.com -insun@google.com -jaewan@google.com -jinpark@google.com -klhyun@google.com lajos@google.com -sungsoo@google.com + +# go/android-fwk-media-solutions for info on areas of ownership. +include platform/frameworks/av:/media/janitors/media_solutions_OWNERS diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 03a63b9058a8..8ef42ff97aad 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -16,11 +16,8 @@ package com.android.server.net; -import android.annotation.NonNull; import android.annotation.Nullable; import android.net.Network; -import android.net.NetworkTemplate; -import android.net.netstats.provider.NetworkStatsProvider; import android.os.PowerExemptionManager.ReasonCode; import android.telephony.SubscriptionPlan; @@ -56,11 +53,6 @@ public abstract class NetworkPolicyManagerInternal { */ public abstract SubscriptionPlan getSubscriptionPlan(Network network); - /** - * Return the active {@link SubscriptionPlan} for the given template. - */ - public abstract SubscriptionPlan getSubscriptionPlan(NetworkTemplate template); - public static final int QUOTA_TYPE_JOBS = 1; public static final int QUOTA_TYPE_MULTIPATH = 2; @@ -99,13 +91,4 @@ public abstract class NetworkPolicyManagerInternal { */ public abstract void setMeteredRestrictedPackagesAsync( Set<String> packageNames, int userId); - - /** - * Notifies that the specified {@link NetworkStatsProvider} has reached its quota - * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or - * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}. - * - * @param tag the human readable identifier of the custom network stats provider. - */ - public abstract void onStatsProviderWarningOrLimitReached(@NonNull String tag); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 65c5b88d846a..a8383b612941 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -130,7 +130,7 @@ import static com.android.internal.util.XmlUtils.writeIntArrayXml; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; -import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; +import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT; import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -151,6 +151,8 @@ import android.app.IUidObserver; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -181,7 +183,6 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStateSnapshot; -import android.net.NetworkStats; import android.net.NetworkTemplate; import android.net.TelephonyNetworkSpecifier; import android.net.TrafficStats; @@ -441,7 +442,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private final Context mContext; private final IActivityManager mActivityManager; - private NetworkStatsManagerInternal mNetworkStats; + private NetworkStatsManager mNetworkStats; private final INetworkManagementService mNetworkManager; private UsageStatsManagerInternal mUsageStats; private AppStandbyInternal mAppStandby; @@ -453,6 +454,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private ConnectivityManager mConnManager; private PowerManagerInternal mPowerManagerInternal; private PowerWhitelistManager mPowerWhitelistManager; + @NonNull + private final Dependencies mDeps; /** Current cached value of the current Battery Saver mode's setting for restrict background. */ @GuardedBy("mUidRulesFirstLock") @@ -704,7 +707,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { public NetworkPolicyManagerService(Context context, IActivityManager activityManager, INetworkManagementService networkManagement) { this(context, activityManager, networkManagement, AppGlobals.getPackageManager(), - getDefaultClock(), getDefaultSystemDir(), false); + getDefaultClock(), getDefaultSystemDir(), false, new Dependencies(context)); } private static @NonNull File getDefaultSystemDir() { @@ -716,9 +719,59 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { Clock.systemUTC()); } + static class Dependencies { + final Context mContext; + final NetworkStatsManager mNetworkStatsManager; + Dependencies(Context context) { + mContext = context; + mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class); + // Query stats from NetworkStatsService will trigger a poll by default. + // But since NPMS listens stats updated event, and will query stats + // after the event. A polling -> updated -> query -> polling loop will be introduced + // if polls on open. Hence, while NPMS manages it's poll requests explicitly, set + // flag to false to prevent a polling loop. + mNetworkStatsManager.setPollOnOpen(false); + } + + long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { + Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes"); + try { + final NetworkStats.Bucket ret = mNetworkStatsManager + .querySummaryForDevice(template, start, end); + return ret.getRxBytes() + ret.getTxBytes(); + } catch (RuntimeException e) { + Slog.w(TAG, "Failed to read network stats: " + e); + return 0; + } finally { + Trace.traceEnd(TRACE_TAG_NETWORK); + } + } + + @NonNull + List<NetworkStats.Bucket> getNetworkUidBytes( + @NonNull NetworkTemplate template, long start, long end) { + Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes"); + final List<NetworkStats.Bucket> buckets = new ArrayList<>(); + try { + final NetworkStats stats = mNetworkStatsManager.querySummary(template, start, end); + while (stats.hasNextBucket()) { + final NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + stats.getNextBucket(bucket); + buckets.add(bucket); + } + } catch (RuntimeException e) { + Slog.w(TAG, "Failed to read network stats: " + e); + } finally { + Trace.traceEnd(TRACE_TAG_NETWORK); + } + return buckets; + } + } + + @VisibleForTesting public NetworkPolicyManagerService(Context context, IActivityManager activityManager, INetworkManagementService networkManagement, IPackageManager pm, Clock clock, - File systemDir, boolean suppressDefaultPolicy) { + File systemDir, boolean suppressDefaultPolicy, Dependencies deps) { mContext = Objects.requireNonNull(context, "missing context"); mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager"); mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement"); @@ -739,10 +792,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUidEventHandler = new Handler(mUidEventThread.getLooper(), mUidEventHandlerCallback); mSuppressDefaultPolicy = suppressDefaultPolicy; + mDeps = Objects.requireNonNull(deps, "missing Dependencies"); mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"), "net-policy"); mAppOps = context.getSystemService(AppOpsManager.class); + mNetworkStats = context.getSystemService(NetworkStatsManager.class); mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler); // Expose private service for system components to use. LocalServices.addService(NetworkPolicyManagerInternal.class, @@ -842,7 +897,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); mAppStandby = LocalServices.getService(AppStandbyInternal.class); - mNetworkStats = LocalServices.getService(NetworkStatsManagerInternal.class); synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { @@ -1161,21 +1215,34 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { }; /** - * Receiver that watches for {@link INetworkStatsService} updates, which we + * Receiver that watches for {@link NetworkStatsManager} updates, which we * use to check against {@link NetworkPolicy#warningBytes}. */ - final private BroadcastReceiver mStatsReceiver = new BroadcastReceiver() { + private final NetworkStatsBroadcastReceiver mStatsReceiver = + new NetworkStatsBroadcastReceiver(); + private class NetworkStatsBroadcastReceiver extends BroadcastReceiver { + private boolean mIsAnyIntentReceived = false; @Override public void onReceive(Context context, Intent intent) { // on background handler thread, and verified // READ_NETWORK_USAGE_HISTORY permission above. + mIsAnyIntentReceived = true; + synchronized (mNetworkPoliciesSecondLock) { updateNetworkRulesNL(); updateNetworkEnabledNL(); updateNotificationsNL(); } } + + /** + * Return whether any {@code ACTION_NETWORK_STATS_UPDATED} intent is received. + * Used to determine if NetworkStatsService is ready. + */ + public boolean isAnyIntentReceived() { + return mIsAnyIntentReceived; + } }; /** @@ -1385,15 +1452,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { long maxBytes = 0; int maxUid = 0; - final NetworkStats stats = getNetworkUidBytes(template, start, end); - NetworkStats.Entry entry = null; - for (int i = 0; i < stats.size(); i++) { - entry = stats.getValues(i, entry); - final long bytes = entry.rxBytes + entry.txBytes; + // Skip if not ready. NetworkStatsService will block public API calls until it is + // ready. To prevent NPMS be blocked on that, skip and fail fast instead. + if (!mStatsReceiver.isAnyIntentReceived()) return null; + + final List<NetworkStats.Bucket> stats = mDeps.getNetworkUidBytes(template, start, end); + for (final NetworkStats.Bucket entry : stats) { + final long bytes = entry.getRxBytes() + entry.getTxBytes(); totalBytes += bytes; if (bytes > maxBytes) { maxBytes = bytes; - maxUid = entry.uid; + maxUid = entry.getUid(); } } @@ -3363,6 +3432,35 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return result; } + /** + * Get subscription plan for the given networkTemplate. + * + * @param template the networkTemplate to get the subscription plan for. + */ + @Override + public SubscriptionPlan getSubscriptionPlan(@NonNull NetworkTemplate template) { + enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + synchronized (mNetworkPoliciesSecondLock) { + final int subId = findRelevantSubIdNL(template); + return getPrimarySubscriptionPlanLocked(subId); + } + } + + /** + * Notifies that the specified {@link NetworkStatsProvider} has reached its quota + * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or + * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}. + */ + @Override + public void notifyStatsProviderWarningOrLimitReached() { + enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + // This API may be called before the system is ready. + synchronized (mNetworkPoliciesSecondLock) { + if (!mSystemReady) return; + } + mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget(); + } + @Override public SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage) { enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage); @@ -4980,7 +5078,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // make sure stats are recorded frequently enough; we aim // for 2MB threshold for 2GB/month rules. final long persistThreshold = lowestRule / 1000; - mNetworkStats.advisePersistThreshold(persistThreshold); + // TODO: Sync internal naming with the API surface. + mNetworkStats.setDefaultGlobalAlert(persistThreshold); return true; } case MSG_UPDATE_INTERFACE_QUOTAS: { @@ -5349,25 +5448,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Deprecated private long getTotalBytes(NetworkTemplate template, long start, long end) { - return getNetworkTotalBytes(template, start, end); - } - - private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { - try { - return mNetworkStats.getNetworkTotalBytes(template, start, end); - } catch (RuntimeException e) { - Slog.w(TAG, "Failed to read network stats: " + e); - return 0; - } - } - - private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) { - try { - return mNetworkStats.getNetworkUidBytes(template, start, end); - } catch (RuntimeException e) { - Slog.w(TAG, "Failed to read network stats: " + e); - return new NetworkStats(SystemClock.elapsedRealtime(), 0); - } + // Skip if not ready. NetworkStatsService will block public API calls until it is + // ready. To prevent NPMS be blocked on that, skip and fail fast instead. + if (!mStatsReceiver.isAnyIntentReceived()) return 0; + return mDeps.getNetworkTotalBytes(template, start, end); } private boolean isBandwidthControlEnabled() { @@ -5583,14 +5667,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public SubscriptionPlan getSubscriptionPlan(NetworkTemplate template) { - synchronized (mNetworkPoliciesSecondLock) { - final int subId = findRelevantSubIdNL(template); - return getPrimarySubscriptionPlanLocked(subId); - } - } - - @Override public long getSubscriptionOpportunisticQuota(Network network, int quotaType) { final long quotaBytes; synchronized (mNetworkPoliciesSecondLock) { @@ -5632,12 +5708,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED, userId, 0, packageNames).sendToTarget(); } - - @Override - public void onStatsProviderWarningOrLimitReached(@NonNull String tag) { - Log.v(TAG, "onStatsProviderWarningOrLimitReached: " + tag); - mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget(); - } } private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index 54557386d73c..c548e7edc3cf 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -39,4 +39,7 @@ public interface NotificationManagerInternal { /** Get the number of notification channels for a given package */ int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); + + /** Does the specified package/uid have permission to post notifications? */ + boolean areNotificationsEnabledForPackage(String pkg, int uid); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a0eeb6553088..86b385b4d810 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2657,16 +2657,20 @@ public class NotificationManagerService extends SystemService { } private void sendAppBlockStateChangedBroadcast(String pkg, int uid, boolean blocked) { - try { - getContext().sendBroadcastAsUser( - new Intent(ACTION_APP_BLOCK_STATE_CHANGED) - .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, blocked) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .setPackage(pkg), - UserHandle.of(UserHandle.getUserId(uid)), null); - } catch (SecurityException e) { - Slog.w(TAG, "Can't notify app about app block change", e); - } + // From Android T, revoking the notification permission will cause the app to be killed. + // delay this broadcast so it doesn't race with that process death + mHandler.postDelayed(() -> { + try { + getContext().sendBroadcastAsUser( + new Intent(ACTION_APP_BLOCK_STATE_CHANGED) + .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, blocked) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .setPackage(pkg), + UserHandle.of(UserHandle.getUserId(uid)), null); + } catch (SecurityException e) { + Slog.w(TAG, "Can't notify app about app block change", e); + } + }, 500); } @Override @@ -3094,6 +3098,13 @@ public class NotificationManagerService extends SystemService { if (mPreferencesHelper.setValidMessageSent( r.getSbn().getPackageName(), r.getUid())) { handleSavePolicyFile(); + } else if (r.getNotification().getBubbleMetadata() != null) { + // If bubble metadata is present it is valid (if invalid it's removed + // via BubbleExtractor). + if (mPreferencesHelper.setValidBubbleSent( + r.getSbn().getPackageName(), r.getUid())) { + handleSavePolicyFile(); + } } } else { if (mPreferencesHelper.setInvalidMessageSent( @@ -3597,6 +3608,12 @@ public class NotificationManagerService extends SystemService { } @Override + public boolean hasSentValidBubble(String pkg, int uid) { + checkCallerIsSystem(); + return mPreferencesHelper.hasSentValidBubble(pkg, uid); + } + + @Override public void setNotificationDelegate(String callingPkg, String delegate) { checkCallerIsSameApp(callingPkg); final int callingUid = Binder.getCallingUid(); @@ -6205,6 +6222,11 @@ public class NotificationManagerService extends SystemService { return NotificationManagerService.this .getNumNotificationChannelsForPackage(pkg, uid, includeDeleted); } + + @Override + public boolean areNotificationsEnabledForPackage(String pkg, int uid) { + return areNotificationsEnabledForPackageInt(pkg, uid); + } }; int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) { @@ -6669,31 +6691,33 @@ public class NotificationManagerService extends SystemService { // package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks. if (!isSystemNotification && !isNotificationFromListener) { final int callingUid = Binder.getCallingUid(); - if (mNotificationsByKey.get(r.getSbn().getKey()) == null - && isCallerInstantApp(callingUid, userId)) { - // Ephemeral apps have some special constraints for notifications. - // They are not allowed to create new notifications however they are allowed to - // update notifications created by the system (e.g. a foreground service - // notification). - throw new SecurityException("Instant app " + pkg - + " cannot create notifications"); - } - - // rate limit updates that aren't completed progress notifications - if (mNotificationsByKey.get(r.getSbn().getKey()) != null - && !r.getNotification().hasCompletedProgress() - && !isAutogroup) { - - final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg); - if (appEnqueueRate > mMaxPackageEnqueueRate) { - mUsageStats.registerOverRateQuota(pkg); - final long now = SystemClock.elapsedRealtime(); - if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) { - Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate - + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg); - mLastOverRateLogTime = now; + synchronized (mNotificationLock) { + if (mNotificationsByKey.get(r.getSbn().getKey()) == null + && isCallerInstantApp(callingUid, userId)) { + // Ephemeral apps have some special constraints for notifications. + // They are not allowed to create new notifications however they are allowed to + // update notifications created by the system (e.g. a foreground service + // notification). + throw new SecurityException("Instant app " + pkg + + " cannot create notifications"); + } + + // rate limit updates that aren't completed progress notifications + if (mNotificationsByKey.get(r.getSbn().getKey()) != null + && !r.getNotification().hasCompletedProgress() + && !isAutogroup) { + + final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg); + if (appEnqueueRate > mMaxPackageEnqueueRate) { + mUsageStats.registerOverRateQuota(pkg); + final long now = SystemClock.elapsedRealtime(); + if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) { + Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate + + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg); + mLastOverRateLogTime = now; + } + return false; } - return false; } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 258ae8c1c849..05f000c607d8 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -101,6 +101,8 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000; + @VisibleForTesting + static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 50000; private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000; private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000; @@ -254,6 +256,7 @@ public class PreferencesHelper implements RankingConfig { } } boolean skipWarningLogged = false; + boolean skipGroupWarningLogged = false; boolean hasSAWPermission = false; if (upgradeForBubbles && uid != UNKNOWN_UID) { hasSAWPermission = mAppOps.noteOpNoThrow( @@ -303,6 +306,14 @@ public class PreferencesHelper implements RankingConfig { String tagName = parser.getName(); // Channel groups if (TAG_GROUP.equals(tagName)) { + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + if (!skipGroupWarningLogged) { + Slog.w(TAG, "Skipping further groups for " + r.pkg + + "; app has too many"); + skipGroupWarningLogged = true; + } + continue; + } String id = parser.getAttributeValue(null, ATT_ID); CharSequence groupName = parser.getAttributeValue(null, ATT_NAME); @@ -808,6 +819,23 @@ public class PreferencesHelper implements RankingConfig { } } + /** Sets whether this package has sent a notification with valid bubble metadata. */ + public boolean setValidBubbleSent(String packageName, int uid) { + synchronized (mPackagePreferences) { + PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); + boolean valueChanged = !r.hasSentValidBubble; + r.hasSentValidBubble = true; + return valueChanged; + } + } + + boolean hasSentValidBubble(String packageName, int uid) { + synchronized (mPackagePreferences) { + PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); + return r.hasSentValidBubble; + } + } + @Override public boolean isGroupBlocked(String packageName, int uid, String groupId) { if (groupId == null) { @@ -848,6 +876,12 @@ public class PreferencesHelper implements RankingConfig { if (r == null) { throw new IllegalArgumentException("Invalid package"); } + if (fromTargetApp) { + group.setBlocked(false); + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + throw new IllegalStateException("Limit exceed; cannot create more groups"); + } + } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); if (oldGroup != null) { group.setChannels(oldGroup.getChannels()); @@ -2813,8 +2847,9 @@ public class PreferencesHelper implements RankingConfig { boolean hasSentInvalidMessage = false; boolean hasSentValidMessage = false; - // notE: only valid while hasSentMessage is false and hasSentInvalidMessage is true + // note: only valid while hasSentMessage is false and hasSentInvalidMessage is true boolean userDemotedMsgApp = false; + boolean hasSentValidBubble = false; Delegate delegate = null; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index 8acc8572453b..54dd113253ec 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -19,6 +19,7 @@ package com.android.server.notification; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.media.AudioAttributes; import android.os.Process; import android.os.VibrationAttributes; @@ -39,18 +40,16 @@ public final class VibratorHelper { private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps - private static final int CHIRP_LEVEL_DURATION_MILLIS = 100; - private static final int DEFAULT_CHIRP_RAMP_DURATION_MILLIS = 100; - private static final int FALLBACK_CHIRP_RAMP_DURATION_MILLIS = 50; private final Vibrator mVibrator; private final long[] mDefaultPattern; private final long[] mFallbackPattern; + @Nullable private final float[] mDefaultPwlePattern; + @Nullable private final float[] mFallbackPwlePattern; public VibratorHelper(Context context) { mVibrator = context.getSystemService(Vibrator.class); - mDefaultPattern = getLongArray( - context.getResources(), + mDefaultPattern = getLongArray(context.getResources(), com.android.internal.R.array.config_defaultNotificationVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); @@ -58,6 +57,10 @@ public final class VibratorHelper { R.array.config_notificationFallbackVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); + mDefaultPwlePattern = getFloatArray(context.getResources(), + com.android.internal.R.array.config_defaultNotificationVibeWaveform); + mFallbackPwlePattern = getFloatArray(context.getResources(), + com.android.internal.R.array.config_notificationFallbackVibeWaveform); } /** @@ -83,6 +86,52 @@ public final class VibratorHelper { } /** + * Safely create a {@link VibrationEffect} from given waveform description. + * + * <p>The waveform is described by a sequence of values for target amplitude, frequency and + * duration, that are forwarded to + * {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}. + * + * <p>This method returns {@code null} if the pattern is also {@code null} or invalid. + * + * @param values The list of values describing the waveform as a sequence of target amplitude, + * frequency and duration. + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + @Nullable + public static VibrationEffect createPwleWaveformVibration(@Nullable float[] values, + boolean insistent) { + try { + if (values == null) { + return null; + } + + int length = values.length; + // The waveform is described by triples (amplitude, frequency, duration) + if ((length == 0) || (length % 3 != 0)) { + return null; + } + + VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform(); + for (int i = 0; i < length; i += 3) { + waveformBuilder.addRamp( + /* amplitude= */ values[i], + /* frequencyHz= */ values[i + 1], + /* duration= */ (int) values[i + 2]); + } + + if (insistent) { + return waveformBuilder.build(/* repeat= */ 0); + } + return waveformBuilder.build(); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: " + + Arrays.toString(values)); + } + return null; + } + + /** * Vibrate the device with given {@code effect}. * * <p>We need to vibrate as "android" so we can breakthrough DND. @@ -106,7 +155,10 @@ public final class VibratorHelper { */ public VibrationEffect createFallbackVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { - return createChirpVibration(FALLBACK_CHIRP_RAMP_DURATION_MILLIS, insistent); + VibrationEffect effect = createPwleWaveformVibration(mFallbackPwlePattern, insistent); + if (effect != null) { + return effect; + } } return createWaveformVibration(mFallbackPattern, insistent); } @@ -118,29 +170,29 @@ public final class VibratorHelper { */ public VibrationEffect createDefaultVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { - return createChirpVibration(DEFAULT_CHIRP_RAMP_DURATION_MILLIS, insistent); + VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent); + if (effect != null) { + return effect; + } } return createWaveformVibration(mDefaultPattern, insistent); } - private static VibrationEffect createChirpVibration(int rampDuration, boolean insistent) { - VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform() - .addStep(/* amplitude= */ 0, /* frequencyHz= */ 60f, /* duration= */ 0) - .addRamp(/* amplitude= */ 1, /* frequencyHz= */ 120f, rampDuration) - .addStep(/* amplitude= */ 1, /* frequencyHz= */ 120f, CHIRP_LEVEL_DURATION_MILLIS) - .addRamp(/* amplitude= */ 0, /* frequencyHz= */ 60f, rampDuration); - - if (insistent) { - return waveformBuilder - .addStep(/* amplitude= */ 0, CHIRP_LEVEL_DURATION_MILLIS) - .build(/* repeat= */ 0); + @Nullable + private static float[] getFloatArray(Resources resources, int resId) { + TypedArray array = resources.obtainTypedArray(resId); + try { + float[] values = new float[array.length()]; + for (int i = 0; i < values.length; i++) { + values[i] = array.getFloat(i, Float.NaN); + if (Float.isNaN(values[i])) { + return null; + } + } + return values; + } finally { + array.recycle(); } - - VibrationEffect singleBeat = waveformBuilder.build(); - return VibrationEffect.startComposition() - .addEffect(singleBeat) - .addEffect(singleBeat, /* delay= */ CHIRP_LEVEL_DURATION_MILLIS) - .compose(); } private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index c9e564a0ce9d..8e944b7a965d 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -37,13 +37,14 @@ import com.android.server.FgThread; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** - * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 - * seconds without a transaction. + * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds + * without a transaction. **/ class IdmapDaemon { // The amount of time in milliseconds to wait after a transaction to the idmap service is made @@ -67,11 +68,14 @@ class IdmapDaemon { * to the service is open. **/ private class Connection implements AutoCloseable { + @Nullable + private final IIdmap2 mIdmap2; private boolean mOpened = true; - private Connection() { + private Connection(IIdmap2 idmap2) { synchronized (mIdmapToken) { mOpenedCount.incrementAndGet(); + mIdmap2 = idmap2; } } @@ -102,6 +106,11 @@ class IdmapDaemon { }, mIdmapToken, SERVICE_TIMEOUT_MS); } } + + @Nullable + public IIdmap2 getIdmap2() { + return mIdmap2; + } } static IdmapDaemon getInstance() { @@ -115,14 +124,29 @@ class IdmapDaemon { @Nullable String overlayName, int policies, boolean enforce, int userId) throws TimeoutException, RemoteException { try (Connection c = connect()) { - return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for createIdmap(\"" + targetPath + + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", " + + enforce + ", " + userId + ")"); + return null; + } + + return idmap2.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), policies, enforce, userId); } } boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException { try (Connection c = connect()) { - return mService.removeIdmap(overlayPath, userId); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for removeIdmap(\"" + overlayPath + + "\", " + userId + ")"); + return false; + } + + return idmap2.removeIdmap(overlayPath, userId); } } @@ -130,14 +154,29 @@ class IdmapDaemon { @Nullable String overlayName, int policies, boolean enforce, int userId) throws Exception { try (Connection c = connect()) { - return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for verifyIdmap(\"" + targetPath + + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", " + + enforce + ", " + userId + ")"); + return false; + } + + return idmap2.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), policies, enforce, userId); } } boolean idmapExists(String overlayPath, int userId) { try (Connection c = connect()) { - return new File(mService.getIdmapPath(overlayPath, userId)).isFile(); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for idmapExists(\"" + overlayPath + + "\", " + userId + ")"); + return false; + } + + return new File(idmap2.getIdmapPath(overlayPath, userId)).isFile(); } catch (Exception e) { Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e); return false; @@ -146,7 +185,13 @@ class IdmapDaemon { FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) { try (Connection c = connect()) { - return mService.createFabricatedOverlay(overlay); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for createFabricatedOverlay()"); + return null; + } + + return idmap2.createFabricatedOverlay(overlay); } catch (Exception e) { Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e); return null; @@ -155,7 +200,14 @@ class IdmapDaemon { boolean deleteFabricatedOverlay(@NonNull String path) { try (Connection c = connect()) { - return mService.deleteFabricatedOverlay(path); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for deleteFabricatedOverlay(\"" + path + + "\")"); + return false; + } + + return idmap2.deleteFabricatedOverlay(path); } catch (Exception e) { Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e); return false; @@ -164,10 +216,18 @@ class IdmapDaemon { synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() { final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>(); - try (Connection c = connect()) { - mService.acquireFabricatedOverlayIterator(); + Connection c = null; + try { + c = connect(); + final IIdmap2 service = c.getIdmap2(); + if (service == null) { + Slog.w(TAG, "idmap2d service is not ready for getFabricatedOverlayInfos()"); + return Collections.emptyList(); + } + + service.acquireFabricatedOverlayIterator(); List<FabricatedOverlayInfo> infos; - while (!(infos = mService.nextFabricatedOverlayInfos()).isEmpty()) { + while (!(infos = service.nextFabricatedOverlayInfos()).isEmpty()) { allInfos.addAll(infos); } return allInfos; @@ -175,17 +235,26 @@ class IdmapDaemon { Slog.wtf(TAG, "failed to get all fabricated overlays", e); } finally { try { - mService.releaseFabricatedOverlayIterator(); + if (c.getIdmap2() != null) { + c.getIdmap2().releaseFabricatedOverlayIterator(); + } } catch (RemoteException e) { // ignore } + c.close(); } return allInfos; } String dumpIdmap(@NonNull String overlayPath) { try (Connection c = connect()) { - String dump = mService.dumpIdmap(overlayPath); + final IIdmap2 service = c.getIdmap2(); + if (service == null) { + final String dumpText = "idmap2d service is not ready for dumpIdmap()"; + Slog.w(TAG, dumpText); + return dumpText; + } + String dump = service.dumpIdmap(overlayPath); return TextUtils.nullIfEmpty(dump); } catch (Exception e) { Slog.wtf(TAG, "failed to dump idmap", e); @@ -193,8 +262,16 @@ class IdmapDaemon { } } + @Nullable private IBinder getIdmapService() throws TimeoutException, RemoteException { - SystemService.start(IDMAP_DAEMON); + try { + SystemService.start(IDMAP_DAEMON); + } catch (RuntimeException e) { + if (e.getMessage().contains("failed to set system property")) { + Slog.w(TAG, "Failed to enable idmap2 daemon", e); + return null; + } + } final long endMillis = SystemClock.elapsedRealtime() + SERVICE_CONNECT_TIMEOUT_MS; while (SystemClock.elapsedRealtime() <= endMillis) { @@ -226,17 +303,23 @@ class IdmapDaemon { } } + @NonNull private Connection connect() throws TimeoutException, RemoteException { synchronized (mIdmapToken) { FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken); if (mService != null) { // Not enough time has passed to stop the idmap service. Reuse the existing // interface. - return new Connection(); + return new Connection(mService); + } + + IBinder binder = getIdmapService(); + if (binder == null) { + return new Connection(null); } - mService = IIdmap2.Stub.asInterface(getIdmapService()); - return new Connection(); + mService = IIdmap2.Stub.asInterface(binder); + return new Connection(mService); } } } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 047a70170587..38781fad76fd 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -33,7 +33,7 @@ import android.content.om.CriticalOverlayInfo; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.os.FabricatedOverlayInfo; import android.os.FabricatedOverlayInternal; import android.text.TextUtils; @@ -492,7 +492,7 @@ final class OverlayManagerServiceImpl { Set<PackageAndUser> registerFabricatedOverlay( @NonNull final FabricatedOverlayInternal overlay) throws OperationFailedException { - if (ParsingPackageUtils.validateName(overlay.overlayName, + if (FrameworkParsingPackageUtils.validateName(overlay.overlayName, false /* requireSeparator */, true /* requireFilename */) != null) { throw new OperationFailedException( "overlay name can only consist of alphanumeric characters, '_', and '.'"); diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 05567480e6bf..6f10a6bbb9ca 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -32,9 +32,9 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.SigningDetails; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ParsedApexSystemService; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.component.ParsedApexSystemService; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Binder; @@ -799,7 +799,7 @@ public abstract class ApexManager { throw new RuntimeException(re); } catch (Exception e) { throw new PackageManagerException( - PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED, "apexd verification failed : " + e.getMessage()); } } @@ -826,7 +826,7 @@ public abstract class ApexManager { throw new RuntimeException(re); } catch (Exception e) { throw new PackageManagerException( - PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED, "Failed to mark apexd session as ready : " + e.getMessage()); } } diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 22cd06dfd16b..a66af3cf7603 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -24,7 +24,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; -import android.content.pm.SELinuxUtil; +import com.android.server.pm.pkg.SELinuxUtil; import android.content.pm.UserInfo; import android.os.CreateAppDataArgs; import android.os.Environment; diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 6f54625224bf..b916de328038 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -33,11 +33,11 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedProvider; import android.os.Binder; import android.os.Process; import android.os.Trace; diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 05a51ccfaca7..31df0a53eaa9 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -896,10 +896,10 @@ public final class BackgroundDexOptService { synchronized (mLock) { if (!mFinishedPostBootUpdate) { mFinishedPostBootUpdate = true; - JobScheduler js = mInjector.getJobScheduler(); - js.cancel(JOB_POST_BOOT_UPDATE); } } + // Safe to do this outside lock. + mInjector.getJobScheduler().cancel(JOB_POST_BOOT_UPDATE); } private void notifyPinService(ArraySet<String> updatedPackages) { diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index 6ec3405727eb..cd4244bf1c50 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -36,14 +36,14 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedProviderImpl; -import android.content.pm.parsing.component.ParsedService; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProviderImpl; +import com.android.server.pm.pkg.component.ParsedService; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index e37aaa5a9509..2aa0e0174d0d 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -92,14 +92,14 @@ import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.PackageUserStateUtils; import android.os.Binder; import android.os.Build; import android.os.IBinder; diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index 62db886b90e9..b30798485bf7 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -19,6 +19,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.MANAGE_APP_OPS_MODES; +import static android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES; import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; import static android.content.pm.CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED; @@ -154,17 +155,15 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { // must have the required permission and the users must be in the same profile group // in order to launch any of its own activities. if (callerUserId != userId) { - final int permissionFlag = PermissionChecker.checkPermissionForPreflight( - mContext, - INTERACT_ACROSS_PROFILES, - callingPid, - callingUid, - callingPackage); - if (permissionFlag != PermissionChecker.PERMISSION_GRANTED - || !isSameProfileGroup(callerUserId, userId)) { - throw new SecurityException("Attempt to launch activity without required " - + INTERACT_ACROSS_PROFILES - + " permission or target user is not in the same profile group."); + if (!hasInteractAcrossProfilesPermission(callingPackage, callingUid, callingPid) + && !isPermissionGranted(START_CROSS_PROFILE_ACTIVITIES, callingUid)) { + throw new SecurityException("Attempt to launch activity without one of the" + + " required " + INTERACT_ACROSS_PROFILES + " or " + + START_CROSS_PROFILE_ACTIVITIES + " permissions."); + } + if (!isSameProfileGroup(callerUserId, userId)) { + throw new SecurityException("Attempt to launch activity when target user is" + + " not in the same profile group."); } } launchIntent.setComponent(component); diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java index dfa6c6655c45..9efe81aed24d 100644 --- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java +++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java @@ -32,7 +32,6 @@ import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS; import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.Nullable; -import android.content.pm.parsing.ParsingPackageUtils; import android.os.Environment; import android.os.SystemClock; import android.os.Trace; @@ -47,6 +46,7 @@ import com.android.server.EventLogTags; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedArrayMap; import java.io.File; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 9302aaddcdb2..6a5d76bc248b 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -91,7 +91,6 @@ import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.AppOpsManager; import android.app.ApplicationPackageManager; import android.app.backup.IBackupManager; import android.content.ContentResolver; @@ -113,11 +112,6 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.VerifierInfo; import android.content.pm.dex.DexMetadataHelper; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.net.Uri; @@ -163,6 +157,11 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.Permission; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.rollback.RollbackManagerInternal; import com.android.server.utils.WatchedArrayMap; import com.android.server.utils.WatchedLongSparseArray; @@ -195,38 +194,32 @@ final class InstallPackageHelper { private final AppDataHelper mAppDataHelper; private final BroadcastHelper mBroadcastHelper; private final RemovePackageHelper mRemovePackageHelper; - private final StorageManager mStorageManager; - private final RollbackManagerInternal mRollbackManager; private final IncrementalManager mIncrementalManager; private final ApexManager mApexManager; private final DexManager mDexManager; private final ArtManagerService mArtManagerService; - private final AppOpsManager mAppOpsManager; private final Context mContext; private final PackageDexOptimizer mPackageDexOptimizer; private final PackageAbiHelper mPackageAbiHelper; private final ViewCompiler mViewCompiler; - private final IBackupManager mIBackupManager; private final SharedLibrariesImpl mSharedLibraries; + private final PackageManagerServiceInjector mInjector; // TODO(b/198166813): remove PMS dependency InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) { mPm = pm; + mInjector = pm.mInjector; mAppDataHelper = appDataHelper; mBroadcastHelper = new BroadcastHelper(pm.mInjector); mRemovePackageHelper = new RemovePackageHelper(pm); - mStorageManager = pm.mInjector.getSystemService(StorageManager.class); - mRollbackManager = pm.mInjector.getLocalService(RollbackManagerInternal.class); mIncrementalManager = pm.mInjector.getIncrementalManager(); mApexManager = pm.mInjector.getApexManager(); mDexManager = pm.mInjector.getDexManager(); mArtManagerService = pm.mInjector.getArtManagerService(); - mAppOpsManager = pm.mInjector.getSystemService(AppOpsManager.class); mContext = pm.mInjector.getContext(); mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer(); mPackageAbiHelper = pm.mInjector.getAbiHelper(); mViewCompiler = pm.mInjector.getViewCompiler(); - mIBackupManager = pm.mInjector.getIBackupManager(); mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); } @@ -693,7 +686,8 @@ final class InstallPackageHelper { * Returns whether the restore successfully completed. */ private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) { - if (mIBackupManager != null) { + IBackupManager iBackupManager = mInjector.getIBackupManager(); + if (iBackupManager != null) { // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM // in the BackupManager. USER_ALL is used in compatibility tests. if (userId == UserHandle.USER_ALL) { @@ -704,8 +698,8 @@ final class InstallPackageHelper { } Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token); try { - if (mIBackupManager.isUserReadyForBackup(userId)) { - mIBackupManager.restoreAtInstallForUser( + if (iBackupManager.isUserReadyForBackup(userId)) { + iBackupManager.restoreAtInstallForUser( userId, res.mPkg.getPackageName(), token); } else { Slog.w(TAG, "User " + userId + " is not ready. Restore at install " @@ -756,7 +750,9 @@ final class InstallPackageHelper { if (ps != null && doSnapshotOrRestore) { final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps); - mRollbackManager.snapshotAndRestoreUserData(packageName, + final RollbackManagerInternal rollbackManager = + mInjector.getLocalService(RollbackManagerInternal.class); + rollbackManager.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers), appId, ceDataInode, seInfo, token); return true; } @@ -1211,21 +1207,36 @@ final class InstallPackageHelper { } PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); - if (ps != null) { - if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); + PackageSetting signatureCheckPs = ps; + + // SDK libs can have other major versions with different package names. + if (signatureCheckPs == null && parsedPackage.isSdkLibrary()) { + WatchedLongSparseArray<SharedLibraryInfo> libraryInfos = + mSharedLibraries.getSharedLibraryInfos( + parsedPackage.getSdkLibName()); + if (libraryInfos != null && libraryInfos.size() > 0) { + // Any existing version would do. + SharedLibraryInfo libraryInfo = libraryInfos.valueAt(0); + signatureCheckPs = mPm.mSettings.getPackageLPr(libraryInfo.getPackageName()); + } + } - // Static shared libs have same package with different versions where - // we internally use a synthetic package name to allow multiple versions - // of the same package, therefore we need to compare signatures against - // the package setting for the latest library version. - PackageSetting signatureCheckPs = ps; - if (parsedPackage.isStaticSharedLibrary()) { - SharedLibraryInfo libraryInfo = mSharedLibraries.getLatestSharedLibraVersionLPr( - parsedPackage); - if (libraryInfo != null) { - signatureCheckPs = mPm.mSettings.getPackageLPr( - libraryInfo.getPackageName()); - } + // Static shared libs have same package with different versions where + // we internally use a synthetic package name to allow multiple versions + // of the same package, therefore we need to compare signatures against + // the package setting for the latest library version. + if (parsedPackage.isStaticSharedLibrary()) { + SharedLibraryInfo libraryInfo = + mSharedLibraries.getLatestStaticSharedLibraVersionLPr(parsedPackage); + if (libraryInfo != null) { + signatureCheckPs = mPm.mSettings.getPackageLPr(libraryInfo.getPackageName()); + } + } + + if (signatureCheckPs != null) { + if (DEBUG_INSTALL) { + Slog.d(TAG, + "Existing package for signature checking: " + signatureCheckPs); } // Quick validity check that we're signed correctly if updating; @@ -1260,6 +1271,10 @@ final class InstallPackageHelper { throw new PrepareFailure(e.error, e.getMessage()); } } + } + + if (ps != null) { + if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); if (ps.getPkg() != null) { systemApp = ps.getPkg().isSystem(); @@ -1969,14 +1984,15 @@ final class InstallPackageHelper { reconciledPkg.mPrepareResult.mExistingPackage.getPackageName()); if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP) == 0) { - if (ps1.getOldCodePaths() == null) { - ps1.setOldCodePaths(new ArraySet<>()); + Set<String> oldCodePaths = ps1.getOldCodePaths(); + if (oldCodePaths == null) { + oldCodePaths = new ArraySet<>(); } - Collections.addAll(ps1.getOldCodePaths(), oldPackage.getBaseApkPath()); + Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath()); if (oldPackage.getSplitCodePaths() != null) { - Collections.addAll(ps1.getOldCodePaths(), - oldPackage.getSplitCodePaths()); + Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths()); } + ps1.setOldCodePaths(oldCodePaths); } else { ps1.setOldCodePaths(null); } @@ -2787,8 +2803,10 @@ final class InstallPackageHelper { // Send broadcast package appeared if external for all users if (res.mPkg.isExternalStorage()) { if (!update) { + final StorageManager storageManager = + mInjector.getSystemService(StorageManager.class); VolumeInfo volume = - mStorageManager.findVolumeByUuid( + storageManager.findVolumeByUuid( StorageManager.convert( res.mPkg.getVolumeUuid()).toString()); int packageExternalStorageType = diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java index 1e1d169f4830..db346dabaa2b 100644 --- a/services/core/java/com/android/server/pm/KeySetManagerService.java +++ b/services/core/java/com/android/server/pm/KeySetManagerService.java @@ -17,11 +17,11 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; -import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; import static com.android.server.pm.PackageManagerService.SCAN_INITIAL; import android.annotation.NonNull; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Base64; @@ -340,9 +340,10 @@ public class KeySetManagerService { if (p == null || p.getKeySetData() == null) { return null; } - Long keySetId = p.getKeySetData().getAliases().get(alias); + final ArrayMap<String, Long> aliases = p.getKeySetData().getAliases(); + Long keySetId = aliases.get(alias); if (keySetId == null) { - throw new IllegalArgumentException("Unknown KeySet alias: " + alias); + throw new IllegalArgumentException("Unknown KeySet alias: " + alias + ", aliases = " + aliases); } return mKeySets.get(keySetId); } @@ -811,7 +812,7 @@ public class KeySetManagerService { long identifier = parser.getAttributeLong(null, "identifier"); int refCount = 0; byte[] publicKey = parser.getAttributeBytesBase64(null, "value", null); - PublicKey pub = parsePublicKey(publicKey); + PublicKey pub = FrameworkParsingPackageUtils.parsePublicKey(publicKey); if (pub != null) { PublicKeyHandle pkh = new PublicKeyHandle(identifier, refCount, pub); mPublicKeys.put(identifier, pkh); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 416b3a426fa5..1f10d77086d3 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -331,7 +331,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements StagingManager.StagedSession stagedSession = session.mStagedSession; if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId() && getSession(stagedSession.getParentSessionId()) == null) { - stagedSession.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + stagedSession.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, "An orphan staged session " + stagedSession.sessionId() + " is found, " + "parent " + stagedSession.getParentSessionId() + " is missing"); continue; @@ -513,19 +513,25 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements if (!valid) { Slog.w(TAG, "Remove old session: " + session.sessionId); // Remove expired sessions as well as child sessions if any - mSessions.remove(session.sessionId); - // Since this is early during boot we don't send - // any observer events about the session, but we - // keep details around for dumpsys. - addHistoricalSessionLocked(session); - for (PackageInstallerSession child : session.getChildSessions()) { - mSessions.remove(child.sessionId); - addHistoricalSessionLocked(child); - } + removeActiveSession(session); } } } + /** + * Moves a session (including the child sessions) from mSessions to mHistoricalSessions. + * This should only be called on a root session. + */ + @GuardedBy("mSessions") + private void removeActiveSession(PackageInstallerSession session) { + mSessions.remove(session.sessionId); + addHistoricalSessionLocked(session); + for (PackageInstallerSession child : session.getChildSessions()) { + mSessions.remove(child.sessionId); + addHistoricalSessionLocked(child); + } + } + @GuardedBy("mSessions") private void addHistoricalSessionLocked(PackageInstallerSession session) { CharArrayWriter writer = new CharArrayWriter(); @@ -843,7 +849,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid, null, null, false, false, false, false, null, SessionInfo.INVALID_ID, - false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, ""); + false, false, false, SessionInfo.SESSION_NO_ERROR, ""); synchronized (mSessions) { mSessions.put(sessionId, session); @@ -1654,10 +1660,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mStagingManager.abortSession(session.mStagedSession); } synchronized (mSessions) { - if (!session.isStaged() || !success) { - mSessions.remove(session.sessionId); + // Child sessions will be removed along with its parent as a whole + if (!session.hasParentSessionId()) { + // Retain policy: + // 1. Don't keep non-staged sessions + // 2. Don't keep explicitly abandoned sessions + // 3. Don't keep sessions that fail validation (isCommitted() is false) + boolean shouldRemove = !session.isStaged() || session.isDestroyed() + || !session.isCommitted(); + if (shouldRemove) { + removeActiveSession(session); + } } - addHistoricalSessionLocked(session); final File appIconFile = buildAppIconFile(session.sessionId); if (appIconFile.exists()) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f45e54b04e54..e0f1b0b44cf8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -27,6 +27,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE; import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT; +import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_STAGED; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; @@ -90,7 +91,7 @@ import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.graphics.Bitmap; @@ -460,7 +461,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private boolean mSessionFailed; @GuardedBy("mLock") - private int mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; + private int mSessionErrorCode = SessionInfo.SESSION_NO_ERROR; @GuardedBy("mLock") private String mSessionErrorMessage; @@ -1733,61 +1734,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @WorkerThread private void handleStreamValidateAndCommit() { - PackageManagerException unrecoverableFailure = null; - // This will track whether the session and any children were validated and are ready to - // progress to the next phase of install - boolean allSessionsReady = false; try { - allSessionsReady = streamValidateAndCommit(); - } catch (PackageManagerException e) { - unrecoverableFailure = e; - } - - if (isMultiPackage()) { - final List<PackageInstallerSession> childSessions; - synchronized (mLock) { - childSessions = getChildSessionsLocked(); + // This will track whether the session and any children were validated and are ready to + // progress to the next phase of install + boolean allSessionsReady = true; + for (PackageInstallerSession child : getChildSessions()) { + allSessionsReady &= child.streamValidateAndCommit(); } - int childCount = childSessions.size(); - - // This will contain all child sessions that do not encounter an unrecoverable failure - ArrayList<PackageInstallerSession> nonFailingSessions = new ArrayList<>(childCount); - - for (int i = childCount - 1; i >= 0; --i) { - // commit all children, regardless if any of them fail; we'll throw/return - // as appropriate once all children have been processed - try { - PackageInstallerSession session = childSessions.get(i); - allSessionsReady &= session.streamValidateAndCommit(); - nonFailingSessions.add(session); - } catch (PackageManagerException e) { - allSessionsReady = false; - if (unrecoverableFailure == null) { - unrecoverableFailure = e; - } - } + if (allSessionsReady && streamValidateAndCommit()) { + mHandler.obtainMessage(MSG_INSTALL).sendToTarget(); } - // If we encountered any unrecoverable failures, destroy all other sessions including - // the parent - if (unrecoverableFailure != null) { - // {@link #streamValidateAndCommit()} calls - // {@link #onSessionValidationFailure(PackageManagerException)}, but we don't - // expect it to ever do so for parent sessions. Call that on this parent to clean - // it up and notify listeners of the error. - onSessionValidationFailure(unrecoverableFailure); - // fail other child sessions that did not already fail - for (int i = nonFailingSessions.size() - 1; i >= 0; --i) { - PackageInstallerSession session = nonFailingSessions.get(i); - session.onSessionValidationFailure(unrecoverableFailure); - } - } - } - - if (!allSessionsReady) { - return; + } catch (PackageManagerException e) { + destroy(); + String msg = ExceptionUtils.getCompleteMessage(e); + dispatchSessionFinished(e.error, msg, null); + maybeFinishChildSessions(e.error, msg); } - - mHandler.obtainMessage(MSG_INSTALL).sendToTarget(); } private final class FileSystemConnector extends @@ -2025,11 +1987,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } return true; } catch (PackageManagerException e) { - throw onSessionValidationFailure(e); + throw e; } catch (Throwable e) { // Convert all exceptions into package manager exceptions as only those are handled // in the code above. - throw onSessionValidationFailure(new PackageManagerException(e)); + throw new PackageManagerException(e); } } @@ -2092,15 +2054,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (isStaged()) { // This will clean up the session when it reaches the terminal state mStagedSession.setSessionFailed( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode); + SessionInfo.SESSION_VERIFICATION_FAILED, msgWithErrorCode); mStagedSession.notifyEndPreRebootVerification(); } else { // Session is sealed and committed but could not be verified, we need to destroy it. destroy(); - // Dispatch message to remove session from PackageInstallerService. - dispatchSessionFinished(error, msg, null); - maybeFinishChildSessions(error, msg); } + // Dispatch message to remove session from PackageInstallerService. + dispatchSessionFinished(error, msg, null); + maybeFinishChildSessions(error, msg); } private void onSessionInstallationFailure(int error, String detailedMessage) { @@ -2301,7 +2263,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (params.isStaged) { // TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even // though ideally, we just need to send session committed broadcast. - dispatchSessionFinished(INSTALL_SUCCEEDED, "Session staged", null); + sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null); mStagedSession.verifySession(); } else { @@ -2547,8 +2509,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (isStaged()) { mSessionProvider.getSessionVerifier().verifyStaged(mStagedSession, (error, msg) -> { mStagedSession.notifyEndPreRebootVerification(); - if (error == SessionInfo.STAGED_SESSION_NO_ERROR) { + if (error == SessionInfo.SESSION_NO_ERROR) { mStagingManager.commitSession(mStagedSession); + } else { + dispatchSessionFinished(INSTALL_FAILED_VERIFICATION_FAILURE, msg, null); + maybeFinishChildSessions(INSTALL_FAILED_VERIFICATION_FAILURE, msg); } }); return; @@ -2578,7 +2543,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Do not try to install staged apex session. Parent session will have at least one apk // session. if (!isMultiPackage() && isApexSession() && params.isStaged) { - sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, + dispatchSessionFinished(INSTALL_SUCCEEDED, "Apex package should have been installed by apexd", null); return null; } @@ -2592,14 +2557,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) { - if (isStaged()) { - sendUpdateToRemoteStatusReceiver(returnCode, msg, extras); - } else { + if (!isStaged()) { // We've reached point of no return; call into PMS to install the stage. // Regardless of success or failure we always destroy session. destroyInternal(); - dispatchSessionFinished(returnCode, msg, extras); } + dispatchSessionFinished(returnCode, msg, extras); } }; @@ -4168,7 +4131,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSessionReady = true; mSessionApplied = false; mSessionFailed = false; - mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; + mSessionErrorCode = SessionInfo.SESSION_NO_ERROR; mSessionErrorMessage = ""; } mCallback.onSessionChanged(this); @@ -4196,7 +4159,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSessionReady = false; mSessionApplied = true; mSessionFailed = false; - mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; + mSessionErrorCode = SessionInfo.SESSION_NO_ERROR; mSessionErrorMessage = ""; Slog.d(TAG, "Marking session " + sessionId + " as applied"); } @@ -4705,7 +4668,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isFailed = in.getAttributeBoolean(null, ATTR_IS_FAILED, false); final boolean isApplied = in.getAttributeBoolean(null, ATTR_IS_APPLIED, false); final int sessionErrorCode = in.getAttributeInt(null, ATTR_SESSION_ERROR_CODE, - SessionInfo.STAGED_SESSION_NO_ERROR); + SessionInfo.SESSION_NO_ERROR); final String sessionErrorMessage = readStringAttribute(in, ATTR_SESSION_ERROR_MESSAGE); if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 248944e476ad..13f91e0dca62 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -130,9 +130,6 @@ import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.dex.IArtManager; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedMainComponent; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -239,9 +236,11 @@ import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SuspendParams; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.pm.pkg.mutate.PackageStateWrite; -import com.android.server.pm.pkg.mutate.PackageUserStateWrite; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationService; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 898f67345031..d8f0cc340884 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -51,7 +51,7 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import android.content.pm.parsing.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedMainComponent; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Binder; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index be2bdaac13e4..fd2256f14963 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -230,6 +230,8 @@ class PackageManagerShellCommand extends ShellCommand { return runDexoptJob(); case "cancel-bg-dexopt-job": return cancelBgDexOptJob(); + case "delete-dexopt": + return runDeleteDexOpt(); case "dump-profiles": return runDumpProfiles(); case "snapshot-profile": @@ -1917,6 +1919,24 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runDeleteDexOpt() throws RemoteException { + PrintWriter pw = getOutPrintWriter(); + String packageName = getNextArg(); + if (TextUtils.isEmpty(packageName)) { + pw.println("Error: no package name"); + return 1; + } + long freedBytes = LocalServices.getService( + PackageManagerInternal.class).deleteOatArtifactsOfPackage(packageName); + if (freedBytes < 0) { + pw.println("Error: delete failed"); + return 1; + } + pw.println("Success: freed " + freedBytes + " bytes"); + Slog.i(TAG, "delete-dexopt " + packageName + " ,freed " + freedBytes + " bytes"); + return 0; + } + private int runDumpProfiles() throws RemoteException { String packageName = getNextArg(); mInterface.dumpProfiles(packageName); @@ -2745,7 +2765,7 @@ class PackageManagerShellCommand extends ShellCommand { IUserManager um = IUserManager.Stub.asInterface( ServiceManager.getService(Context.USER_SERVICE)); if (setEphemeralIfInUse) { - return removeUserOrSetEphemeral(um, userId); + return removeUserWhenPossible(um, userId); } else { final boolean success = wait ? removeUserAndWait(um, userId) : removeUser(um, userId); if (success) { @@ -2808,10 +2828,10 @@ class PackageManagerShellCommand extends ShellCommand { } } - private int removeUserOrSetEphemeral(IUserManager um, @UserIdInt int userId) + private int removeUserWhenPossible(IUserManager um, @UserIdInt int userId) throws RemoteException { Slog.i(TAG, "Removing " + userId + " or set as ephemeral if in use."); - int result = um.removeUserOrSetEphemeral(userId, /* evenWhenDisallowed= */ false); + int result = um.removeUserWhenPossible(userId, /* overrideDevicePolicy= */ false); switch (result) { case UserManager.REMOVE_RESULT_REMOVED: getOutPrintWriter().printf("Success: user %d removed\n", userId); @@ -4000,6 +4020,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" force-dex-opt PACKAGE"); pw.println(" Force immediate execution of dex opt for the given PACKAGE."); pw.println(""); + pw.println(" delete-dexopt PACKAGE"); + pw.println(" Delete dex optimization results for the given PACKAGE."); + pw.println(""); pw.println(" bg-dexopt-job"); pw.println(" Execute the background optimizations immediately."); pw.println(" Note that the command only runs the background optimizer logic. It may"); diff --git a/services/core/java/com/android/server/pm/PackageProperty.java b/services/core/java/com/android/server/pm/PackageProperty.java index ee9ed3b90599..2055537b4cc8 100644 --- a/services/core/java/com/android/server/pm/PackageProperty.java +++ b/services/core/java/com/android/server/pm/PackageProperty.java @@ -27,7 +27,7 @@ import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; import android.content.pm.PackageManager.PropertyLocation; -import android.content.pm.parsing.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedComponent; import android.os.Binder; import android.os.UserHandle; import android.util.ArrayMap; diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java index a532fe3a3d4d..9bfb7d19eee1 100644 --- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java +++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java @@ -28,7 +28,7 @@ import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.rollback.RollbackInfo; @@ -202,7 +202,7 @@ final class PackageSessionVerifier { } private void onVerificationSuccess(StagingManager.StagedSession session, Callback callback) { - callback.onResult(SessionInfo.STAGED_SESSION_NO_ERROR, null); + callback.onResult(SessionInfo.SESSION_NO_ERROR, null); } private void onVerificationFailure(StagingManager.StagedSession session, Callback callback, @@ -298,7 +298,7 @@ final class PackageSessionVerifier { // Failed to get hold of StorageManager Slog.e(TAG, "Failed to get hold of StorageManager", e); throw new PackageManagerException( - SessionInfo.STAGED_SESSION_UNKNOWN, + SessionInfo.SESSION_UNKNOWN_ERROR, "Failed to get hold of StorageManager"); } // Proactively mark session as ready before calling apexd. Although this call order @@ -336,7 +336,7 @@ final class PackageSessionVerifier { final ParseResult<SigningDetails> newResult = ApkSignatureVerifier.verify( input.reset(), apexPath, minSignatureScheme); if (newResult.isError()) { - throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + apexPath + " : " + newResult.getException(), newResult.getException()); } @@ -355,7 +355,7 @@ final class PackageSessionVerifier { input.reset(), existingApexPkg.applicationInfo.sourceDir, SigningDetails.SignatureSchemeVersion.JAR); if (existingResult.isError()) { - throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir + " : " + existingResult.getException(), existingResult.getException()); } @@ -369,7 +369,7 @@ final class PackageSessionVerifier { return; } - throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, "APK-container signature of APEX package " + packageName + " with version " + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not" + " compatible with the one currently installed on device"); @@ -412,11 +412,11 @@ final class PackageSessionVerifier { packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags); if (packageInfo == null) { throw new PackageManagerException( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + SessionInfo.SESSION_VERIFICATION_FAILED, "Unable to generate package info: " + apexInfo.modulePath); } } catch (PackageManagerException e) { - throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e); } result.add(packageInfo); @@ -438,7 +438,7 @@ final class PackageSessionVerifier { } } throw new PackageManagerException( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + SessionInfo.SESSION_VERIFICATION_FAILED, "Could not find rollback id for commit session: " + sessionId); } @@ -546,7 +546,7 @@ final class PackageSessionVerifier { try { checkActiveSessions(PackageHelper.getStorageManager().supportsCheckpoint()); } catch (RemoteException e) { - throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, "Can't query fs-checkpoint status : " + e); } } @@ -562,7 +562,7 @@ final class PackageSessionVerifier { } if (!supportsCheckpoint && activeSessions > 1) { throw new PackageManagerException( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + SessionInfo.SESSION_VERIFICATION_FAILED, "Cannot stage multiple sessions without checkpoint support"); } } @@ -593,13 +593,13 @@ final class PackageSessionVerifier { // will be deleted. } stagedSession.setSessionFailed( - SessionInfo.STAGED_SESSION_CONFLICT, + SessionInfo.SESSION_CONFLICT, "Session was failed by rollback session: " + session.sessionId()); Slog.i(TAG, "Session " + stagedSession.sessionId() + " is marked failed due to " + "rollback session: " + session.sessionId()); } else if (!isRollback(session) && isRollback(stagedSession)) { throw new PackageManagerException( - SessionInfo.STAGED_SESSION_CONFLICT, + SessionInfo.SESSION_CONFLICT, "Session was failed by rollback session: " + stagedSession.sessionId()); } @@ -622,7 +622,7 @@ final class PackageSessionVerifier { final String packageName = child.getPackageName(); if (packageName == null) { throw new PackageManagerException( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + SessionInfo.SESSION_VERIFICATION_FAILED, "Cannot stage session " + child.sessionId() + " with package name null"); } for (StagingManager.StagedSession stagedSession : mStagedSessions) { @@ -634,14 +634,14 @@ final class PackageSessionVerifier { if (stagedSession.getCommittedMillis() < parent.getCommittedMillis()) { // Fail the session committed later when there are overlapping packages throw new PackageManagerException( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + SessionInfo.SESSION_VERIFICATION_FAILED, "Package: " + packageName + " in session: " + child.sessionId() + " has been staged already by session: " + stagedSession.sessionId()); } else { stagedSession.setSessionFailed( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + SessionInfo.SESSION_VERIFICATION_FAILED, "Package: " + packageName + " in session: " + stagedSession.sessionId() + " has been staged already by session: " diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 67f6b123d99b..f3d88edf40dd 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -28,7 +28,7 @@ import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index d60d01971534..b0e03403b653 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -29,7 +29,7 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.content.pm.PackageManager; -import android.content.pm.parsing.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedInstrumentation; import android.os.UserHandle; import android.os.incremental.IncrementalManager; import android.util.Log; diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 6d2ec0da896a..79ab563e4e6e 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -52,13 +52,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Build; @@ -85,6 +78,13 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.PackageStateUtils; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import dalvik.system.VMRuntime; diff --git a/services/core/java/com/android/server/pm/ScanRequest.java b/services/core/java/com/android/server/pm/ScanRequest.java index 482b79cf8378..34abdb108068 100644 --- a/services/core/java/com/android/server/pm/ScanRequest.java +++ b/services/core/java/com/android/server/pm/ScanRequest.java @@ -18,12 +18,12 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.ParsingPackageUtils; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; /** A package to be scanned */ @VisibleForTesting diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7085682662e6..4583771794ba 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -50,13 +50,13 @@ import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.PackageUserStateUtils; import android.net.Uri; import android.os.Binder; import android.os.Build; diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index aa230508287e..2227a7810dec 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -384,7 +384,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable * @return The latest version of shared library info. */ @GuardedBy("mPm.mLock") - @Nullable SharedLibraryInfo getLatestSharedLibraVersionLPr(@NonNull AndroidPackage pkg) { + @Nullable SharedLibraryInfo getLatestStaticSharedLibraVersionLPr(@NonNull AndroidPackage pkg) { WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get( pkg.getStaticSharedLibName()); if (versionedLib == null) { @@ -416,7 +416,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable PackageSetting sharedLibPackage = null; synchronized (mPm.mLock) { final SharedLibraryInfo latestSharedLibraVersionLPr = - getLatestSharedLibraVersionLPr(scanResult.mRequest.mParsedPackage); + getLatestStaticSharedLibraVersionLPr(scanResult.mRequest.mParsedPackage); if (latestSharedLibraVersionLPr != null) { sharedLibPackage = mPm.mSettings.getPackageLPr( latestSharedLibraVersionLPr.getPackageName()); diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 9df0edb211f1..bc484618bb25 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -18,9 +18,9 @@ package com.android.server.pm; import android.annotation.NonNull; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProcessImpl; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProcessImpl; import android.service.pm.PackageServiceDumpProto; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index bf7ef1b24776..15e64dffe892 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -146,8 +146,10 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_PERSON_IS_IMPORTANT = "is-important"; private static final String NAME_CATEGORIES = "categories"; + private static final String NAME_CAPABILITY = "capability"; private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array"; + private static final String TAG_MAP_XMLUTILS = "map"; private static final String ATTR_NAME_XMLUTILS = "name"; private static final String KEY_DYNAMIC = "dynamic"; @@ -1829,6 +1831,12 @@ class ShortcutPackage extends ShortcutPackageItem { } ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); + + final Map<String, Map<String, List<String>>> capabilityBindings = + si.getCapabilityBindings(); + if (capabilityBindings != null && !capabilityBindings.isEmpty()) { + XmlUtils.writeMapXml(si.getCapabilityBindings(), NAME_CAPABILITY, out); + } } out.endTag(null, TAG_SHORTCUT); @@ -1961,6 +1969,7 @@ class ShortcutPackage extends ShortcutPackageItem { int backupVersionCode; ArraySet<String> categories = null; ArrayList<Person> persons = new ArrayList<>(); + Map<String, Map<String, List<String>>> capabilityBindings = null; id = ShortcutService.parseStringAttribute(parser, ATTR_ID); activityComponent = ShortcutService.parseComponentNameAttribute(parser, @@ -2029,6 +2038,13 @@ class ShortcutPackage extends ShortcutPackageItem { } } continue; + case TAG_MAP_XMLUTILS: + if (NAME_CAPABILITY.equals(ShortcutService.parseStringAttribute(parser, + ATTR_NAME_XMLUTILS))) { + capabilityBindings = (Map<String, Map<String, List<String>>>) + XmlUtils.readValueXml(parser, new String[1]); + } + continue; } throw ShortcutService.throwForInvalidTag(depth, tag); } @@ -2064,7 +2080,7 @@ class ShortcutPackage extends ShortcutPackageItem { rank, extras, lastChangedTimestamp, flags, iconResId, iconResName, bitmapPath, iconUri, disabledReason, persons.toArray(new Person[persons.size()]), locusId, - splashScreenThemeResName); + splashScreenThemeResName, capabilityBindings); } private static Intent parseIntent(TypedXmlPullParser parser) diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index b86c50b23687..63f1f2d518f7 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -459,7 +459,8 @@ public class ShortcutParser { disabledReason, null /* persons */, null /* locusId */, - splashScreenThemeResName); + splashScreenThemeResName, + null /* capabilityBindings */); } private static String parseCategory(ShortcutService service, AttributeSet attrs) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 0a2735cdbf76..057f8def1798 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3474,8 +3474,8 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable private ParcelFileDescriptor getShortcutIconParcelFileDescriptor( - @NonNull final ShortcutInfo shortcutInfo) { - if (!shortcutInfo.hasIconFile()) { + @Nullable final ShortcutInfo shortcutInfo) { + if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { return null; } final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 8a6ef6bfcb41..29de5551cb27 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -284,7 +284,7 @@ public class StagingManager { String packageName = apexSession.getPackageName(); String errorMsg = mApexManager.getApkInApexInstallError(packageName); if (errorMsg != null) { - throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + throw new PackageManagerException(SessionInfo.SESSION_ACTIVATION_FAILED, "Failed to install apk-in-apex of " + packageName + " : " + errorMsg); } } @@ -397,7 +397,7 @@ public class StagingManager { revertMsg += " Reason for revert: " + reasonForRevert; } Slog.d(TAG, revertMsg); - session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg); + session.setSessionFailed(SessionInfo.SESSION_UNKNOWN_ERROR, revertMsg); return; } @@ -484,7 +484,7 @@ public class StagingManager { for (String apkInApex : mApexManager.getApksInApex(packageName)) { if (!apkNames.add(apkInApex)) { throw new PackageManagerException( - SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + SessionInfo.SESSION_ACTIVATION_FAILED, "Package: " + packageName + " in session: " + apexSession.sessionId() + " has duplicate apk-in-apex: " + apkInApex, null); @@ -511,7 +511,7 @@ public class StagingManager { Slog.e(TAG, "Failure to install APK staged session " + session.sessionId() + " [" + errorMessage + "]"); throw new PackageManagerException( - SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage); + SessionInfo.SESSION_ACTIVATION_FAILED, errorMessage); } } @@ -665,7 +665,7 @@ public class StagingManager { // is upgrading. Fail all the sessions and exit early. for (int i = 0; i < sessions.size(); i++) { StagedSession session = sessions.get(i); - session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, "Build fingerprint has changed"); } return; @@ -705,7 +705,7 @@ public class StagingManager { final ApexSessionInfo apexSession = apexSessions.get(session.sessionId()); if (apexSession == null || apexSession.isUnknown) { hasFailedApexSession = true; - session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did " + session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, "apexd did " + "not know anything about a staged session supposed to be activated"); continue; } else if (isApexSessionFailed(apexSession)) { @@ -721,7 +721,7 @@ public class StagingManager { errorMsg += " Error: " + apexSession.errorMessage; } Slog.d(TAG, errorMsg); - session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); + session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, errorMsg); continue; } else if (apexSession.isActivated || apexSession.isSuccess) { hasAppliedApexSession = true; @@ -730,13 +730,13 @@ public class StagingManager { // Apexd did not apply the session for some unknown reason. There is no guarantee // that apexd will install it next time. Safer to proactively mark it as failed. hasFailedApexSession = true; - session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, "Staged session " + session.sessionId() + " at boot didn't activate nor " + "fail. Marking it as failed anyway."); } else { Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state"); hasFailedApexSession = true; - session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, "Impossible state"); } } @@ -756,7 +756,7 @@ public class StagingManager { // Session has been already failed in the loop above. continue; } - session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, "Another apex session failed"); } return; @@ -772,7 +772,7 @@ public class StagingManager { } catch (Exception e) { Slog.e(TAG, "Staged install failed due to unhandled exception", e); onInstallationFailure(session, new PackageManagerException( - SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + SessionInfo.SESSION_ACTIVATION_FAILED, "Staged install failed due to unhandled exception: " + e), supportsCheckpoint, needsCheckpoint); } diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 1433abd1b6c9..bb7e55a4bf40 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -32,7 +32,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackagePartitions; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.os.Environment; import android.os.FileUtils; import android.os.UserHandle; diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 4bcc2a33c631..f87063a83e4f 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -56,27 +56,6 @@ ] }, { - "name": "CtsAppSecurityHostTestCases", - "options": [ - { - "include-filter": "android.appsecurity.cts.PrivilegedUpdateTests" - } - ] - }, - { - "name": "CtsAppSecurityHostTestCases", - "file_patterns": [ - "core/java/.*Install.*", - "services/core/.*Install.*", - "services/core/java/com/android/server/pm/.*" - ], - "options": [ - { - "include-filter": "android.appsecurity.cts.SplitTests" - } - ] - }, - { "name": "PackageManagerServiceHostTests", "options": [ { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index fb3ca91003ea..d29dbbc7c04a 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2487,21 +2487,26 @@ public class UserManagerService extends IUserManager.Stub { return false; } - // Limit the number of profiles that can be created + final int userTypeCount = getProfileIds(userId, userType, false).length; + final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0; + final int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU() + - profilesRemovedCount; + + // Limit total number of users that can be created + if (usersCountAfterRemoving >= UserManager.getMaxSupportedUsers()) { + // Special case: Allow creating a managed profile anyway if there's only 1 user + // Otherwise, disallow. + if (!(isManagedProfile && usersCountAfterRemoving == 1)) { + return false; + } + } + + // Limit the number of profiles of this type that can be created. final int maxUsersOfType = getMaxUsersOfTypePerParent(type); if (maxUsersOfType != UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) { - final int userTypeCount = getProfileIds(userId, userType, false).length; - final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0; if (userTypeCount - profilesRemovedCount >= maxUsersOfType) { return false; } - // Allow creating a managed profile in the special case where there is only one user - if (isManagedProfile) { - int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU() - - profilesRemovedCount; - return usersCountAfterRemoving == 1 - || usersCountAfterRemoving < UserManager.getMaxSupportedUsers(); - } } } return true; @@ -3753,6 +3758,7 @@ public class UserManagerService extends IUserManager.Stub { final boolean isGuest = UserManager.isUserTypeGuest(userType); final boolean isRestricted = UserManager.isUserTypeRestricted(userType); final boolean isDemo = UserManager.isUserTypeDemo(userType); + final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType); final long ident = Binder.clearCallingIdentity(); UserInfo userInfo; @@ -3776,6 +3782,14 @@ public class UserManagerService extends IUserManager.Stub { + ". Maximum number of that type already exists.", UserManager.USER_OPERATION_ERROR_MAX_USERS); } + if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) { + // If the user limit has been reached, we cannot add a user (except guest/demo). + // Note that managed profiles can bypass it in certain circumstances (taken + // into account in the profile check below). + throwCheckedUserOperationException( + "Cannot add user. Maximum user limit is reached.", + UserManager.USER_OPERATION_ERROR_MAX_USERS); + } // TODO(b/142482943): Perhaps let the following code apply to restricted users too. if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) { throwCheckedUserOperationException( @@ -3783,13 +3797,6 @@ public class UserManagerService extends IUserManager.Stub { + " for user " + parentId, UserManager.USER_OPERATION_ERROR_MAX_USERS); } - if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) { - // If we're not adding a guest/demo user or a profile and the 'user limit' has - // been reached, cannot add a user. - throwCheckedUserOperationException( - "Cannot add user. Maximum user limit is reached.", - UserManager.USER_OPERATION_ERROR_MAX_USERS); - } // In legacy mode, restricted profile's parent can only be the owner user if (isRestricted && !UserManager.isSplitSystemUser() && (parentId != UserHandle.USER_SYSTEM)) { @@ -4430,11 +4437,11 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId, - boolean evenWhenDisallowed) { + public @UserManager.RemoveResult int removeUserWhenPossible(@UserIdInt int userId, + boolean overrideDevicePolicy) { checkCreateUsersPermission("Only the system can remove users"); - if (!evenWhenDisallowed) { + if (!overrideDevicePolicy) { final String restriction = getUserRemovalRestriction(userId); if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 01bf63483829..e28a6ea8ea6b 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -31,7 +31,7 @@ import android.content.pm.dex.ArtManagerInternal; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.ISnapshotRuntimeProfileCallback; import android.content.pm.dex.PackageOptimizationInfo; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import android.os.Binder; import android.os.Build; import android.os.Handler; diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java index 8afe62aabd59..61aedd8bd1cf 100644 --- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java +++ b/services/core/java/com/android/server/pm/dex/ViewCompiler.java @@ -16,7 +16,7 @@ package com.android.server.pm.dex; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import android.os.Binder; import android.os.UserHandle; import android.util.Log; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 07cc3d0e42af..0fa0dc39b8bd 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -34,19 +34,19 @@ import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.pm.SharedLibraryInfo; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingUtils; -import android.content.pm.parsing.component.ComponentParseUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.server.pm.pkg.component.ComponentParseUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.PackageUserStateUtils; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java index f467a7f2023f..08e2f7da1ebf 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java +++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java @@ -22,9 +22,9 @@ import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java index 3a4921644175..564585b4cbe9 100644 --- a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java +++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java @@ -18,10 +18,9 @@ package com.android.server.pm.parsing; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedComponent; import android.util.Pair; -import com.android.server.pm.PackageSetting; import com.android.server.pm.pkg.PackageStateInternal; /** diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java index bbf584df0405..dc3bf781034c 100644 --- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java +++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java @@ -21,7 +21,7 @@ import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_T import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_TEST_RUNNER; import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; -import android.content.pm.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java index bf7d897ac3e6..b357ba0a6b4b 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java @@ -17,7 +17,9 @@ package com.android.server.pm.parsing.pkg; import android.annotation.NonNull; -import android.content.pm.parsing.ParsingPackageRead; + +import com.android.internal.content.om.OverlayConfig; +import com.android.server.pm.pkg.parsing.ParsingPackageRead; import com.android.server.pm.pkg.AndroidPackageApi; @@ -31,8 +33,8 @@ import com.android.server.pm.pkg.AndroidPackageApi; * * @hide */ -public interface AndroidPackage extends ParsingPackageRead, AndroidPackageApi { - +public interface AndroidPackage extends ParsingPackageRead, AndroidPackageApi, + OverlayConfig.PackageProvider.Package { /** * The package name as declared in the manifest, since the package can be renamed. For example, diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index 8b2c3a12eda7..7e59bd669824 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -23,12 +23,12 @@ import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.VersionedPackage; import android.content.pm.dex.DexMetadataHelper; -import android.content.pm.parsing.ParsingPackageRead; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; +import com.android.server.pm.pkg.parsing.ParsingPackageRead; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.incremental.IncrementalManager; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index 6846ac5cbb0a..193e1a22d787 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -22,14 +22,8 @@ import android.annotation.Nullable; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.SELinuxUtil; +import com.android.server.pm.pkg.SELinuxUtil; import android.content.pm.SigningDetails; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageImpl; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; import android.content.res.TypedArray; import android.os.Environment; import android.os.Parcel; @@ -41,6 +35,12 @@ import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; import com.android.server.pm.parsing.PackageInfoUtils; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageImpl; import java.io.File; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PkgAppInfo.java b/services/core/java/com/android/server/pm/parsing/pkg/PkgAppInfo.java index a13f2975da8d..6ddae9bf4f8f 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PkgAppInfo.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PkgAppInfo.java @@ -18,7 +18,7 @@ package com.android.server.pm.parsing.pkg; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.PkgWithoutStateAppInfo; +import com.android.server.pm.pkg.parsing.PkgWithoutStateAppInfo; import com.android.server.pm.PackageManagerService; import com.android.server.pm.pkg.PackageState; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PkgPackageInfo.java b/services/core/java/com/android/server/pm/parsing/pkg/PkgPackageInfo.java index e2efbe1482c3..da7f1dc0f47e 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PkgPackageInfo.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PkgPackageInfo.java @@ -17,7 +17,7 @@ package com.android.server.pm.parsing.pkg; import android.content.pm.PackageInfo; -import android.content.pm.parsing.PkgWithoutStatePackageInfo; +import com.android.server.pm.pkg.parsing.PkgWithoutStatePackageInfo; import com.android.server.pm.PackageManagerService; diff --git a/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java b/services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java index b70353a4023c..d9625050d28c 100644 --- a/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java +++ b/services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,11 +14,10 @@ * limitations under the License. */ -package android.content.pm.permission; +package com.android.server.pm.permission; import android.Manifest; import android.annotation.NonNull; -import android.content.pm.parsing.component.ParsedUsesPermission; import com.android.internal.util.DataClass; diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index 041c4fea587e..d5456e3c8dc3 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -23,7 +23,7 @@ import android.annotation.UserIdInt; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; -import android.content.pm.parsing.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermission; import android.os.Build; import android.os.UserHandle; import android.util.Log; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 85e54f3766ea..1cfcdf51f5b8 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -386,7 +386,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void startOneTimePermissionSession(String packageName, @UserIdInt int userId, long timeoutMillis, int importanceToResetTimer, int importanceToKeepSessionAlive) { - mContext.enforceCallingPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS + " to register permissions as one time."); Objects.requireNonNull(packageName); @@ -557,6 +558,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override + public void selfRevokePermissions(@NonNull String packageName, + @NonNull List<String> permissions) { + mPermissionManagerServiceImpl.selfRevokePermissions(packageName, permissions); + } + + @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName, int userId) { return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName, diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 21cb2c96fc6a..981fd8e9e789 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -73,11 +73,11 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionUtils; -import android.content.pm.permission.CompatibilityPermissionInfo; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionUtils; + import android.content.pm.permission.SplitPermissionInfoParcelable; import android.metrics.LogMaker; import android.os.AsyncTask; @@ -1591,6 +1591,25 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } + @Override + public void selfRevokePermissions(String packageName, List<String> permissions) { + final int callingUid = Binder.getCallingUid(); + int callingUserId = UserHandle.getUserId(callingUid); + int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId); + if (targetPackageUid != callingUid) { + throw new SecurityException("uid " + callingUid + + " cannot revoke permissions for package " + packageName + " with uid " + + targetPackageUid); + } + for (String permName : permissions) { + if (!checkCallingOrSelfPermission(permName)) { + throw new SecurityException("uid " + callingUid + " cannot revoke permission " + + permName + " because it does not hold that permission"); + } + } + mPermissionControllerManager.selfRevokePermissions(packageName, permissions); + } + private boolean mayManageRolePermission(int uid) { final PackageManager packageManager = mContext.getPackageManager(); final String[] packageNames = packageManager.getPackagesForUid(uid); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 3771f030aefa..c582f9efa7a0 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -327,6 +327,26 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId); /** + * Triggers the revocation of one or more permissions for a package, under the following + * conditions: + * <ul> + * <li>The package {@code packageName} must be under the same UID as the calling process + * (typically, the target package is the calling package). + * <li>Each permission in {@code permissions} must be granted to the package + * {@code packageName}. + * <li>Each permission in {@code permissions} must be a runtime permission. + * </ul> + * <p> + * For every permission in {@code permissions}, the entire permission group it belongs to will + * be revoked. This revocation happens asynchronously and kills all processes running in the + * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * + * @param packageName The name of the package for which the permissions will be revoked. + * @param permissions List of permissions to be revoked. + */ + void selfRevokePermissions(String packageName, List<String> permissions); + + /** * Get whether you should show UI with rationale for requesting a permission. You should do this * only if you do not have the permission and the context in which the permission is requested * does not clearly communicate to the user what would be the benefit from grating this diff --git a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java index 0e3fda7b937a..3a617041d55e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java +++ b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java @@ -18,7 +18,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java index 6c8e0b72198d..656c445a0ed5 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java @@ -20,8 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.component.ParsedMainComponent; + import android.util.SparseArray; import com.android.server.pm.parsing.pkg.AndroidPackage; diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 03b16929bed8..d47c5eca18ea 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.pkg.FrameworkPackageUserState; import android.os.UserHandle; import java.util.Map; @@ -34,7 +33,7 @@ import java.util.Set; */ // TODO(b/173807334): Expose API //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) -public interface PackageUserState extends FrameworkPackageUserState { +public interface PackageUserState { PackageUserState DEFAULT = PackageUserStateInternal.DEFAULT; diff --git a/core/java/android/content/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index 468bff1b53ec..917c4af017d2 100644 --- a/core/java/android/content/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.pkg; +package com.android.server.pm.pkg; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; @@ -22,26 +22,27 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPON import android.annotation.NonNull; import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; -import android.content.pm.parsing.ParsingPackageRead; -import android.content.pm.parsing.component.ParsedMainComponent; import android.os.Debug; import android.util.DebugUtils; import android.util.Slog; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.parsing.ParsingPackageRead; + /** @hide */ public class PackageUserStateUtils { private static final boolean DEBUG = false; private static final String TAG = "PackageUserStateUtils"; - public static boolean isMatch(@NonNull FrameworkPackageUserState state, + public static boolean isMatch(@NonNull PackageUserState state, ComponentInfo componentInfo, long flags) { return isMatch(state, componentInfo.applicationInfo.isSystemApp(), componentInfo.applicationInfo.enabled, componentInfo.enabled, componentInfo.directBootAware, componentInfo.name, flags); } - public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, + public static boolean isMatch(@NonNull PackageUserState state, boolean isSystem, boolean isPackageEnabled, ParsedMainComponent component, long flags) { return isMatch(state, isSystem, isPackageEnabled, component.isEnabled(), component.isDirectBootAware(), component.getName(), flags); @@ -56,7 +57,7 @@ public class PackageUserStateUtils { * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}. * </p> */ - public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, + public static boolean isMatch(@NonNull PackageUserState state, boolean isSystem, boolean isPackageEnabled, boolean isComponentEnabled, boolean isComponentDirectBootAware, String componentName, long flags) { final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0; @@ -81,7 +82,7 @@ public class PackageUserStateUtils { return reportIfDebug(matchesUnaware || matchesAware, flags); } - public static boolean isAvailable(@NonNull FrameworkPackageUserState state, long flags) { + public static boolean isAvailable(@NonNull PackageUserState state, long flags) { // True if it is installed for this user and it is not hidden. If it is hidden, // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0; @@ -100,13 +101,13 @@ public class PackageUserStateUtils { return result; } - public static boolean isEnabled(@NonNull FrameworkPackageUserState state, ComponentInfo componentInfo, + public static boolean isEnabled(@NonNull PackageUserState state, ComponentInfo componentInfo, long flags) { return isEnabled(state, componentInfo.applicationInfo.enabled, componentInfo.enabled, componentInfo.name, flags); } - public static boolean isEnabled(@NonNull FrameworkPackageUserState state, boolean isPackageEnabled, + public static boolean isEnabled(@NonNull PackageUserState state, boolean isPackageEnabled, ParsedMainComponent parsedComponent, long flags) { return isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), parsedComponent.getName(), flags); @@ -115,7 +116,7 @@ public class PackageUserStateUtils { /** * Test if the given component is considered enabled. */ - public static boolean isEnabled(@NonNull FrameworkPackageUserState state, + public static boolean isEnabled(@NonNull PackageUserState state, boolean isPackageEnabled, boolean isComponentEnabled, String componentName, long flags) { if ((flags & MATCH_DISABLED_COMPONENTS) != 0) { @@ -153,7 +154,7 @@ public class PackageUserStateUtils { return isComponentEnabled; } - public static boolean isPackageEnabled(@NonNull FrameworkPackageUserState state, + public static boolean isPackageEnabled(@NonNull PackageUserState state, @NonNull ParsingPackageRead pkg) { switch (state.getEnabledState()) { case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: diff --git a/core/java/android/content/pm/SELinuxUtil.java b/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java index 898dddf45d04..6cbc1de75010 100644 --- a/core/java/android/content/pm/SELinuxUtil.java +++ b/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -14,9 +14,7 @@ * limitations under the License. */ -package android.content.pm; - -import android.content.pm.pkg.FrameworkPackageUserState; +package com.android.server.pm.pkg; /** * Utility methods that need to be used in application space. @@ -31,7 +29,7 @@ public final class SELinuxUtil { public static final String COMPLETE_STR = ":complete"; /** @hide */ - public static String getSeinfoUser(FrameworkPackageUserState userState) { + public static String getSeinfoUser(PackageUserState userState) { if (userState.isInstantApp()) { return INSTANT_APP_STR + COMPLETE_STR; } diff --git a/core/java/android/content/pm/parsing/component/ComponentMutateUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java index 73d0b9fd8504..1deb8d055e20 100644 --- a/core/java/android/content/pm/parsing/component/ComponentMutateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java index 0334601a0a06..a8fb79a52837 100644 --- a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,27 +14,27 @@ * limitations under the License. */ -package android.content.pm.parsing.component; - -import static android.content.pm.parsing.ParsingPackageUtils.validateName; +package com.android.server.pm.pkg.component; import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; -import android.content.pm.pkg.FrameworkPackageUserState; -import android.content.pm.pkg.PackageUserStateUtils; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; +import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -125,7 +125,8 @@ public class ComponentParseUtils { + ": must be at least two characters"); } String subName = proc.substring(1); - final ParseResult<?> nameResult = validateName(input, subName, false, false); + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, + subName, false, false); if (nameResult.isError()) { return input.error("Invalid " + type + " name " + proc + " in package " + pkg + ": " + nameResult.getErrorMessage()); @@ -133,7 +134,8 @@ public class ComponentParseUtils { return input.success(pkg + proc); } if (!"system".equals(proc)) { - final ParseResult<?> nameResult = validateName(input, proc, true, false); + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, proc, + true, false); if (nameResult.isError()) { return input.error("Invalid " + type + " name " + proc + " in package " + pkg + ": " + nameResult.getErrorMessage()); @@ -169,13 +171,13 @@ public class ComponentParseUtils { return component.getIcon(); } - public static boolean isMatch(FrameworkPackageUserState state, boolean isSystem, + public static boolean isMatch(PackageUserState state, boolean isSystem, boolean isPackageEnabled, ParsedMainComponent component, long flags) { return PackageUserStateUtils.isMatch(state, isSystem, isPackageEnabled, component.isEnabled(), component.isDirectBootAware(), component.getName(), flags); } - public static boolean isEnabled(FrameworkPackageUserState state, boolean isPackageEnabled, + public static boolean isEnabled(PackageUserState state, boolean isPackageEnabled, ParsedMainComponent parsedComponent, long flags) { return PackageUserStateUtils.isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), parsedComponent.getName(), flags); diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java index a661b51dbf81..6d978c40454d 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivity.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java index 93dc5a4ce317..ff97c13a998b 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,13 +14,13 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import android.annotation.NonNull; @@ -29,7 +29,7 @@ import android.app.ActivityTaskManager; import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java index 2ddf923d7dd9..db8815e6555c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,12 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; -import static android.content.pm.parsing.component.ComponentParseUtils.flag; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,9 +27,9 @@ import android.app.ActivityTaskManager; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseInput.DeferredError; import android.content.pm.parsing.result.ParseResult; @@ -58,7 +58,9 @@ import java.util.List; import java.util.Objects; import java.util.Set; -/** @hide */ +/** + * @hide + */ public class ParsedActivityUtils { private static final String TAG = ParsingUtils.TAG; @@ -601,7 +603,7 @@ public class ParsedActivityUtils { * AndroidManifest.xml. * @hide */ - static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) { + public static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) { return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK); } } diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java index fe821e04958f..7690818bf4f1 100644 --- a/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java index 54196fddb1fb..8c4d8f0e8da4 100644 --- a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java index 26abf48ee391..38a6f5a356e7 100644 --- a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.R; import android.annotation.NonNull; diff --git a/core/java/android/content/pm/parsing/component/ParsedAttribution.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java index ac7a9284d89b..3b91f289fe4c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedAttribution.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java @@ -14,18 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.StringRes; -import android.os.Parcel; import android.os.Parcelable; -import android.util.ArraySet; -import com.android.internal.util.DataClass; - -import java.util.ArrayList; import java.util.List; /** diff --git a/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java index 510425fd8584..a4eb4f19c8b5 100644 --- a/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,14 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.StringRes; import android.os.Parcel; import android.os.Parcelable; -import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; diff --git a/core/java/android/content/pm/parsing/component/ParsedAttributionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java index 84f1d44b2a84..98e94c5214f0 100644 --- a/core/java/android/content/pm/parsing/component/ParsedAttributionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java index c1372f6234ad..1a8230dcfb86 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponent.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java index 1c46a107d118..9125e8c0f3f2 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import static java.util.Collections.emptyMap; @@ -25,7 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager.Property; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.os.Bundle; import android.os.Parcel; import android.text.TextUtils; diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java index 5c33cfd5192d..e208854f2637 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java index e8fcc00a05bc..a0eae8c4c8ff 100644 --- a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java index d2b536893012..c8baa9e1b504 100644 --- a/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java index df5e73e26f17..51e14280ffe4 100644 --- a/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,12 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; -import android.content.pm.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java index 1e36cccae45f..57b486abdfd7 100644 --- a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java @@ -14,20 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; -import android.os.Parcel; import android.os.Parcelable; -import android.util.Pair; - -import com.android.internal.util.DataClass; -import com.android.internal.util.Parcelling; - -import java.util.ArrayList; -import java.util.List; /** @hide **/ public interface ParsedIntentInfo extends Parcelable { diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java index 9ff7a167c093..1c816da34019 100644 --- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,23 +14,20 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; import android.os.Parcel; import android.os.Parcelable; -import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; -import com.android.internal.util.Parcelling; -import java.util.ArrayList; -import java.util.List; - -/** @hide **/ +/** + * @hide + **/ @DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false, genBuilder = false, genConstructor = false) @DataClass.Suppress({"setIntentFilter"}) diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java index cb72c2b11189..1e6f6306258c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; +import static com.android.server.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; import android.annotation.NonNull; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java index 2507205df257..8c1d6c8ebaf8 100644 --- a/core/java/android/content/pm/parsing/component/ParsedMainComponent.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java index 6051435bf8f9..9b57f4830671 100644 --- a/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import android.annotation.Nullable; import android.os.Parcel; diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java index 87f75b06f9fd..2a3e6534c1e2 100644 --- a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,15 +14,15 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Configuration; diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java index 6acdb6e872ae..4a6d2c3ac80d 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermission.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java index 22aa0854f2a3..73b5ffaa298d 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; /** @hide */ public interface ParsedPermissionGroup extends ParsedComponent { diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java index 1fa04cfd065c..f47fb75e9d92 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.os.Parcel; diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java index 45038cf225fb..98007ff94972 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -109,14 +109,13 @@ public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedP protected ParsedPermissionImpl(Parcel in) { super(in); - // We use the boot classloader for all classes that we load. - final ClassLoader boot = Object.class.getClassLoader(); this.backgroundPermission = in.readString(); this.group = TextUtils.safeIntern(in.readString()); this.requestRes = in.readInt(); this.protectionLevel = in.readInt(); this.tree = in.readBoolean(); - this.parsedPermissionGroup = in.readParcelable(boot, android.content.pm.parsing.component.ParsedPermissionGroup.class); + this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(), + ParsedPermissionGroup.class); this.knownCerts = sForStringSet.unparcel(in); } diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java index 86c8f02f9fd9..8562fdfe2571 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,14 +14,14 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.content.pm.PermissionInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java index 27a540d25891..ff391ff97fcc 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProcess.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.content.pm.ApplicationInfo; diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java index d404ecfd38c7..96560c7d66ae 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import static java.util.Collections.emptySet; diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java index 5e4cf661b194..d03f15338f7e 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,12 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedProvider.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java index 1211ce274aea..8cc6fc745efb 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProvider.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; import android.content.pm.PathPermission; diff --git a/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java index 774c3fca1cf0..e04fc868e350 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java index de9dd44b3b8f..9d3129be4e78 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,18 +14,18 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageUtils.RIGID_PARSER; -import static android.content.pm.parsing.component.ComponentParseUtils.flag; +import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; +import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java index 6736afaa8714..11696be89367 100644 --- a/core/java/android/content/pm/parsing/component/ParsedService.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java index a85fb5cfcf46..0171c49f1bef 100644 --- a/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java index d27a0ed81a0a..6fe9411a7fb9 100644 --- a/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,17 +14,17 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.component.ComponentParseUtils.flag; +import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ServiceInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseInput.DeferredError; import android.content.pm.parsing.result.ParseResult; diff --git a/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java index e2f5f14ab725..8e3401ed29bd 100644 --- a/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.IntDef; import android.annotation.NonNull; diff --git a/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java index d3c7afbb2012..70d6f24aa81b 100644 --- a/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.os.Parcel; diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java index 28290d75cdfa..2d6c616d7f76 100644 --- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.CheckResult; import android.annotation.NonNull; @@ -35,29 +35,29 @@ import android.content.pm.PackageManager; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; -import android.content.pm.SELinuxUtil; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.component.ComponentParseUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedUsesPermission; -import android.content.pm.pkg.FrameworkPackageUserState; -import android.content.pm.pkg.PackageUserStateUtils; import android.os.Environment; import android.os.UserHandle; import com.android.internal.util.ArrayUtils; +import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.SELinuxUtil; +import com.android.server.pm.pkg.component.ComponentParseUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.component.ParsedUsesPermission; import libcore.util.EmptyArray; @@ -77,7 +77,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PackageInfo generate(ParsingPackageRead pkg, int[] gids, @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime, - long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId) { return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, userId, null); @@ -86,13 +86,13 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PackageInfo generate(ParsingPackageRead pkg, ApexInfo apexInfo, int flags) { return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(), - FrameworkPackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo); + PackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo); } @Nullable private static PackageInfo generateWithComponents(ParsingPackageRead pkg, int[] gids, @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime, - long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId, @Nullable ApexInfo apexInfo) { ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId); if (applicationInfo == null) { @@ -192,7 +192,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PackageInfo generateWithoutComponents(ParsingPackageRead pkg, int[] gids, @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime, - long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId, @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) { if (!checkUseInstalled(pkg, state, flags)) { return null; @@ -207,12 +207,12 @@ public class PackageInfoWithoutStateUtils { * server. * <p> * Prefer {@link #generateWithoutComponents(ParsingPackageRead, int[], int, long, long, Set, - * FrameworkPackageUserState, int, ApexInfo, ApplicationInfo)}. + * PackageUserState, int, ApexInfo, ApplicationInfo)}. */ @NonNull public static PackageInfo generateWithoutComponentsUnchecked(ParsingPackageRead pkg, int[] gids, @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime, - long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId, @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) { PackageInfo pi = new PackageInfo(); pi.packageName = pkg.getPackageName(); @@ -366,7 +366,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ApplicationInfo generateApplicationInfo(ParsingPackageRead pkg, - @PackageManager.ApplicationInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserState state, int userId) { if (pkg == null) { return null; @@ -384,7 +384,7 @@ public class PackageInfoWithoutStateUtils { * This bypasses critical checks that are necessary for usage with data passed outside of system * server. * <p> - * Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, FrameworkPackageUserState, int)}. + * Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, PackageUserState, int)}. * * @param assignUserFields whether to fill the returned {@link ApplicationInfo} with user * specific fields. This can be skipped when building from a system @@ -395,7 +395,7 @@ public class PackageInfoWithoutStateUtils { @NonNull public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg, @PackageManager.ApplicationInfoFlagsBits long flags, - @NonNull FrameworkPackageUserState state, int userId, boolean assignUserFields) { + @NonNull PackageUserState state, int userId, boolean assignUserFields) { // Make shallow copy so we can store the metadata/libraries safely ApplicationInfo ai = ((ParsingPackageHidden) pkg).toAppInfoWithoutState(); @@ -409,7 +409,7 @@ public class PackageInfoWithoutStateUtils { } private static void updateApplicationInfo(ApplicationInfo ai, long flags, - FrameworkPackageUserState state) { + PackageUserState state) { if ((flags & PackageManager.GET_META_DATA) == 0) { ai.metaData = null; } @@ -455,7 +455,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ApplicationInfo generateDelegateApplicationInfo(@Nullable ApplicationInfo ai, @PackageManager.ApplicationInfoFlagsBits long flags, - @NonNull FrameworkPackageUserState state, int userId) { + @NonNull PackageUserState state, int userId) { if (ai == null || !checkUseInstalledOrHidden(flags, state, ai)) { return null; } @@ -472,7 +472,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateDelegateActivityInfo(@Nullable ActivityInfo a, @PackageManager.ComponentInfoFlagsBits long flags, - @NonNull FrameworkPackageUserState state, int userId) { + @NonNull PackageUserState state, int userId) { if (a == null || !checkUseInstalledOrHidden(flags, state, a.applicationInfo)) { return null; } @@ -486,7 +486,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (a == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -507,7 +507,7 @@ public class PackageInfoWithoutStateUtils { * server. * <p> * Prefer {@link #generateActivityInfo(ParsingPackageRead, ParsedActivity, long, - * FrameworkPackageUserState, ApplicationInfo, int)}. + * PackageUserState, ApplicationInfo, int)}. */ @NonNull public static ActivityInfo generateActivityInfoUnchecked(@NonNull ParsedActivity a, @@ -552,14 +552,14 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId) { return generateActivityInfo(pkg, a, flags, state, null, userId); } @Nullable public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (s == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -579,8 +579,8 @@ public class PackageInfoWithoutStateUtils { * This bypasses critical checks that are necessary for usage with data passed outside of system * server. * <p> - * Prefer {@link #generateServiceInfo(ParsingPackageRead, ParsedService, long, - * FrameworkPackageUserState, ApplicationInfo, int)}. + * Prefer {@link #generateServiceInfo(ParsingPackageRead, ParsedService, long, PackageUserState, + * ApplicationInfo, int)}. */ @NonNull public static ServiceInfo generateServiceInfoUnchecked(@NonNull ParsedService s, @@ -603,14 +603,14 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId) { return generateServiceInfo(pkg, s, flags, state, null, userId); } @Nullable public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (p == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -631,7 +631,7 @@ public class PackageInfoWithoutStateUtils { * server. * <p> * Prefer {@link #generateProviderInfo(ParsingPackageRead, ParsedProvider, long, - * FrameworkPackageUserState, ApplicationInfo, int)}. + * PackageUserState, ApplicationInfo, int)}. */ @NonNull public static ProviderInfo generateProviderInfoUnchecked(@NonNull ParsedProvider p, @@ -665,14 +665,14 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId) { return generateProviderInfo(pkg, p, flags, state, null, userId); } /** * @param assignUserFields see {@link #generateApplicationInfoUnchecked(ParsingPackageRead, - * long, FrameworkPackageUserState, int, boolean)} + * long, PackageUserState, int, boolean)} */ @Nullable public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i, @@ -759,7 +759,7 @@ public class PackageInfoWithoutStateUtils { } private static boolean checkUseInstalledOrHidden(long flags, - @NonNull FrameworkPackageUserState state, @Nullable ApplicationInfo appInfo) { + @NonNull PackageUserState state, @Nullable ApplicationInfo appInfo) { // Returns false if the package is hidden system app until installed. if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0 && !state.isInstalled() @@ -888,7 +888,7 @@ public class PackageInfoWithoutStateUtils { } private static boolean checkUseInstalled(ParsingPackageRead pkg, - FrameworkPackageUserState state, @PackageManager.PackageInfoFlagsBits long flags) { + PackageUserState state, @PackageManager.PackageInfoFlagsBits long flags) { // If available for the target user return PackageUserStateUtils.isAvailable(state, flags); } diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index fc9f1a55d84c..18a6435d17c8 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.CallSuper; import android.annotation.NonNull; @@ -26,17 +26,17 @@ import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager.Property; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedUsesPermission; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.component.ParsedUsesPermission; import android.os.Bundle; import android.util.SparseArray; import android.util.SparseIntArray; @@ -375,6 +375,8 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setResetEnabledSettingsOnAppDataCleared( boolean resetEnabledSettingsOnAppDataCleared); + ParsingPackage setLocaleConfigRes(int localeConfigRes); + // TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement // for moving to the next step @CallSuper diff --git a/core/java/android/content/pm/parsing/ParsingPackageHidden.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.java index c49d11e738a7..66e01a6bfb09 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageHidden.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java index 23cae4c04467..c4de862bccd9 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -33,28 +33,6 @@ import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.Property; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedActivityImpl; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedApexSystemServiceImpl; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedAttributionImpl; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedInstrumentationImpl; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionGroupImpl; -import android.content.pm.parsing.component.ParsedPermissionImpl; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedProviderImpl; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedServiceImpl; -import android.content.pm.parsing.component.ParsedUsesPermission; -import android.content.pm.parsing.component.ParsedUsesPermissionImpl; import android.content.res.TypedArray; import android.os.Build; import android.os.Bundle; @@ -81,6 +59,33 @@ import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringList; import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet; import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringValueMap; import com.android.internal.util.Parcelling.BuiltIn.ForStringSet; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedAttributionImpl; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; +import com.android.server.pm.pkg.component.ParsedPermissionImpl; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProviderImpl; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.component.ParsedServiceImpl; +import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageHidden; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import java.security.PublicKey; import java.util.Collections; @@ -559,6 +564,8 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, private UUID mStorageUuid; private long mLongVersionCode; + private int mLocaleConfigRes; + @VisibleForTesting public ParsingPackageImpl(@NonNull String packageName, @NonNull String baseApkPath, @NonNull String path, @Nullable TypedArray manifestArray) { @@ -1136,6 +1143,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, appInfo.setSplitResourcePaths(splitCodePaths); appInfo.setVersionCode(mLongVersionCode); appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess()); + appInfo.setLocaleConfigRes(mLocaleConfigRes); return appInfo; } @@ -1314,6 +1322,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, dest.writeInt(this.memtagMode); dest.writeInt(this.nativeHeapZeroInitialized); sForBoolean.parcel(this.requestRawExternalStorageAccess, dest, flags); + dest.writeInt(this.mLocaleConfigRes); } public ParsingPackageImpl(Parcel in) { @@ -1405,10 +1414,10 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, this.instrumentations = ParsingUtils.createTypedInterfaceList(in, ParsedInstrumentationImpl.CREATOR); this.preferredActivityFilters = sForIntentInfoPairs.unparcel(in); - this.processes = in.readHashMap(boot); + this.processes = in.readHashMap(ParsedProcess.class.getClassLoader()); this.metaData = in.readBundle(boot); this.volumeUuid = sForInternedString.unparcel(in); - this.signingDetails = in.readParcelable(boot, android.content.pm.SigningDetails.class); + this.signingDetails = in.readParcelable(boot); this.mPath = in.readString(); this.queriesIntents = in.createTypedArrayList(Intent.CREATOR); this.queriesPackages = sForInternedStringList.unparcel(in); @@ -1461,6 +1470,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, this.memtagMode = in.readInt(); this.nativeHeapZeroInitialized = in.readInt(); this.requestRawExternalStorageAccess = sForBoolean.unparcel(in); + this.mLocaleConfigRes = in.readInt(); assignDerivedFields(); } @@ -2279,6 +2289,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, return nativeHeapZeroInitialized; } + @Override + public int getLocaleConfigRes() { + return mLocaleConfigRes; + } + @Nullable @Override public Boolean hasRequestRawExternalStorageAccess() { @@ -2936,4 +2951,10 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, resetEnabledSettingsOnAppDataCleared); return this; } + + @Override + public ParsingPackageImpl setLocaleConfigRes(int value) { + mLocaleConfigRes = value; + return this; + } } diff --git a/core/java/android/content/pm/parsing/ParsingPackageInternal.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageInternal.java index ca16fa2d97eb..5457785a8519 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageInternal.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageInternal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.Nullable; import android.content.pm.PackageInfo; diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java index c8113efcc7c1..149711287f32 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,26 +14,26 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.Property; -import android.content.pm.PackageParser; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedUsesPermission; import android.os.Bundle; import android.util.ArraySet; import android.util.Pair; import android.util.SparseIntArray; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedUsesPermission; + import java.security.PublicKey; import java.util.List; import java.util.Map; @@ -49,7 +49,7 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt /** * The names of packages to adopt ownership of permissions from, parsed under {@link - * PackageParser#TAG_ADOPT_PERMISSIONS}. + * ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. * * @see R.styleable#AndroidManifestOriginalPackage_name */ @@ -77,7 +77,7 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link - * PackageParser#TAG_KEY_SETS}. + * ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestKeySet * @see R.styleable#AndroidManifestPublicKey @@ -226,7 +226,7 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link - * PackageParser#TAG_KEY_SETS}. + * ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestUpgradeKeySet */ @@ -343,4 +343,11 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared */ boolean isResetEnabledSettingsOnAppDataCleared(); + + /** + * The resource ID used to provide the application's locales configuration. + * + * @see R.styleable#AndroidManifestApplication_localeConfig + */ + int getLocaleConfigRes(); } diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index f336672ffefa..1ce01f633791 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; @@ -33,7 +33,6 @@ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.annotation.AnyRes; import android.annotation.CheckResult; import android.annotation.IntDef; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StyleableRes; @@ -50,39 +49,13 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; import android.content.pm.Signature; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ComponentParseUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedActivityUtils; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedApexSystemServiceUtils; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedAttributionUtils; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedInstrumentationUtils; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedIntentInfoUtils; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionUtils; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProcessUtils; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedProviderUtils; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedServiceUtils; -import android.content.pm.parsing.component.ParsedUsesPermission; -import android.content.pm.parsing.component.ParsedUsesPermissionImpl; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; +import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseInput.DeferredError; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; -import android.content.pm.permission.CompatibilityPermissionInfo; -import android.content.pm.split.DefaultSplitAssetLoader; -import android.content.pm.split.SplitAssetDependencyLoader; -import android.content.pm.split.SplitAssetLoader; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; @@ -92,7 +65,6 @@ import android.content.res.XmlResourceParser; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.FileUtils; import android.os.Parcel; import android.os.RemoteException; import android.os.SystemProperties; @@ -104,7 +76,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; -import android.util.Base64; import android.util.DisplayMetrics; import android.util.Pair; import android.util.Slog; @@ -117,6 +88,35 @@ import com.android.internal.R; import com.android.internal.os.ClassLoaderFactory; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; +import com.android.server.pm.permission.CompatibilityPermissionInfo; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ComponentParseUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityUtils; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedApexSystemServiceUtils; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedAttributionUtils; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedInstrumentationUtils; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedIntentInfoUtils; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProcessUtils; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProviderUtils; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.component.ParsedServiceUtils; +import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; +import com.android.server.pm.split.DefaultSplitAssetLoader; +import com.android.server.pm.split.SplitAssetDependencyLoader; +import com.android.server.pm.split.SplitAssetLoader; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -129,14 +129,8 @@ import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; @@ -158,10 +152,14 @@ public class ParsingPackageUtils { public static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f; public static final float ASPECT_RATIO_NOT_SET = -1f; - /** File name in an APK for the Android manifest. */ + /** + * File name in an APK for the Android manifest. + */ public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; - /** Path prefix for apps on expanded storage */ + /** + * Path prefix for apps on expanded storage + */ public static final String MNT_EXPAND = "/mnt/expand/"; public static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions"; @@ -214,10 +212,11 @@ public class ParsingPackageUtils { PackageInfo.INSTALL_LOCATION_UNSPECIFIED; public static final int PARSE_DEFAULT_TARGET_SANDBOX = 1; - /** If set to true, we will only allow package files that exactly match - * the DTD. Otherwise, we try to get as much from the package as we - * can without failing. This should normally be set to false, to - * support extensions to the DTD in future versions. */ + /** + * If set to true, we will only allow package files that exactly match the DTD. Otherwise, we + * try to get as much from the package as we can without failing. This should normally be set to + * false, to support extensions to the DTD in future versions. + */ public static final boolean RIGID_PARSER = false; public static final int PARSE_MUST_BE_APK = 1 << 0; @@ -227,8 +226,8 @@ public class ParsingPackageUtils { public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5; public static final int PARSE_ENFORCE_CODE = 1 << 6; /** - * This flag is applied in the ApkLiteParser. Used by OverlayConfigParser to ignore the - * checks of required system property within the overlay tag. + * This flag is applied in the ApkLiteParser. Used by OverlayConfigParser to ignore the checks + * of required system property within the overlay tag. */ public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; public static final int PARSE_CHATTY = 1 << 31; @@ -247,12 +246,6 @@ public class ParsingPackageUtils { public @interface ParseFlags {} /** - * For those names would be used as a part of the file name. Limits size to 223 and reserves 32 - * for the OS. - */ - static final int MAX_FILE_NAME_SIZE = 223; - - /** * @see #parseDefault(ParseInput, File, int, List, boolean) */ @NonNull @@ -266,8 +259,8 @@ public class ParsingPackageUtils { /** * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off - * request, without caching the input object and without querying the internal system state - * for feature support. + * request, without caching the input object and without querying the internal system state for + * feature support. */ @NonNull public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file, @@ -293,8 +286,7 @@ public class ParsingPackageUtils { @NonNull String baseApkPath, @NonNull String path, @NonNull TypedArray manifestArray, boolean isCoreApp) { - return new ParsingPackageImpl(packageName, baseApkPath, path, - manifestArray); + return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray); } }); result = parser.parsePackage(input, file, parseFlags); @@ -337,21 +329,19 @@ public class ParsingPackageUtils { } /** - * Parse the package at the given location. Automatically detects if the - * package is a monolithic style (single APK file) or cluster style - * (directory of APKs). + * Parse the package at the given location. Automatically detects if the package is a monolithic + * style (single APK file) or cluster style (directory of APKs). * <p> - * This performs validity checking on cluster style packages, such as - * requiring identical package name and version codes, a single base APK, - * and unique split names. + * This performs validity checking on cluster style packages, such as requiring identical + * package name and version codes, a single base APK, and unique split names. * <p> - * Note that this <em>does not</em> perform signature verification; that must - * be done separately in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. - * - * If {@code useCaches} is true, the package parser might return a cached - * result from a previous parse of the same {@code packageFile} with the same - * {@code flags}. Note that this method does not check whether {@code packageFile} - * has changed since the last parse, it's up to callers to do so. + * Note that this <em>does not</em> perform signature verification; that must be done separately + * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. + * <p> + * If {@code useCaches} is true, the package parser might return a cached result from a previous + * parse of the same {@code packageFile} with the same {@code flags}. Note that this method does + * not check whether {@code packageFile} has changed since the last parse, it's up to callers to + * do so. */ public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) { if (packageFile.isDirectory()) { @@ -362,13 +352,12 @@ public class ParsingPackageUtils { } /** - * Parse all APKs contained in the given directory, treating them as a - * single package. This also performs validity checking, such as requiring - * identical package name and version codes, a single base APK, and unique - * split names. + * Parse all APKs contained in the given directory, treating them as a single package. This also + * performs validity checking, such as requiring identical package name and version codes, a + * single base APK, and unique split names. * <p> - * Note that this <em>does not</em> perform signature verification; that must - * be done separately in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. + * Note that this <em>does not</em> perform signature verification; that must be done separately + * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. */ private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir, int flags) { @@ -439,8 +428,8 @@ public class ParsingPackageUtils { /** * Parse the given APK file, treating it as as a single monolithic package. * <p> - * Note that this <em>does not</em> perform signature verification; that must - * be done separately in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. + * Note that this <em>does not</em> perform signature verification; that must be done separately + * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. */ private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile, int flags) { @@ -599,9 +588,8 @@ public class ParsingPackageUtils { } /** - * Parse the manifest of a <em>base APK</em>. When adding new features you - * need to consider whether they should be supported by split APKs and child - * packages. + * Parse the manifest of a <em>base APK</em>. When adding new features you need to consider + * whether they should be supported by split APKs and child packages. * * @param apkPath The package apk file path * @param res The resources from which to resolve values @@ -653,9 +641,8 @@ public class ParsingPackageUtils { /** * Parse the manifest of a <em>split APK</em>. * <p> - * Note that split APKs have many more restrictions on what they're capable - * of doing, so many valid features of a base APK have been carefully - * omitted here. + * Note that split APKs have many more restrictions on what they're capable of doing, so many + * valid features of a base APK have been carefully omitted here. * * @param pkg builder to fill * @return false on failure @@ -718,9 +705,8 @@ public class ParsingPackageUtils { * Parse the {@code application} XML tree at the current parse location in a * <em>split APK</em> manifest. * <p> - * Note that split APKs have many more restrictions on what they're capable - * of doing, so many valid features of a base APK have been carefully - * omitted here. + * Note that split APKs have many more restrictions on what they're capable of doing, so many + * valid features of a base APK have been carefully omitted here. */ private ParseResult<ParsingPackage> parseSplitApplication(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, int splitIndex) @@ -1030,7 +1016,8 @@ public class ParsingPackageUtils { } if (!"android".equals(pkg.getPackageName())) { - ParseResult<?> nameResult = validateName(input, str, true, true); + ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, str, + true, true); if (nameResult.isError()) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, "<manifest> specifies bad sharedUserId name \"" + str + "\": " @@ -1101,7 +1088,8 @@ public class ParsingPackageUtils { + " must define a public-key value on first use at " + parser.getPositionDescription()); } else if (encodedKey != null) { - PublicKey currentKey = parsePublicKey(encodedKey); + PublicKey currentKey = + FrameworkParsingPackageUtils.parsePublicKey(encodedKey); if (currentKey == null) { Slog.w(TAG, "No recognized valid key in 'public-key' tag at " + parser.getPositionDescription() + " key-set " @@ -1534,8 +1522,8 @@ public class ParsingPackageUtils { targetCode = minCode; } - ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion( - targetVers, targetCode, SDK_CODENAMES, input); + ParseResult<Integer> targetSdkVersionResult = FrameworkParsingPackageUtils + .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input); if (targetSdkVersionResult.isError()) { return input.error(targetSdkVersionResult); } @@ -1548,8 +1536,8 @@ public class ParsingPackageUtils { return input.error(deferResult); } - ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode, - SDK_VERSION, SDK_CODENAMES, input); + ParseResult<Integer> minSdkVersionResult = FrameworkParsingPackageUtils + .computeMinSdkVersion(minVers, minCode, SDK_VERSION, SDK_CODENAMES, input); if (minSdkVersionResult.isError()) { return input.error(minSdkVersionResult); } @@ -1644,146 +1632,6 @@ public class ParsingPackageUtils { return input.success(minExtensionVersions); } - /** - * Computes the minSdkVersion to use at runtime. If the package is not - * compatible with this platform, populates {@code outError[0]} with an - * error message. - * <p> - * If {@code minCode} is not specified, e.g. the value is {@code null}, - * then behavior varies based on the {@code platformSdkVersion}: - * <ul> - * <li>If the platform SDK version is greater than or equal to the - * {@code minVers}, returns the {@code mniVers} unmodified. - * <li>Otherwise, returns -1 to indicate that the package is not - * compatible with this platform. - * </ul> - * <p> - * Otherwise, the behavior varies based on whether the current platform - * is a pre-release version, e.g. the {@code platformSdkCodenames} array - * has length > 0: - * <ul> - * <li>If this is a pre-release platform and the value specified by - * {@code targetCode} is contained within the array of allowed pre-release - * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. - * <li>If this is a released platform, this method will return -1 to - * indicate that the package is not compatible with this platform. - * </ul> - * - * @param minVers minSdkVersion number, if specified in the application - * manifest, or 1 otherwise - * @param minCode minSdkVersion code, if specified in the application - * manifest, or {@code null} otherwise - * @param platformSdkVersion platform SDK version number, typically - * Build.VERSION.SDK_INT - * @param platformSdkCodenames array of allowed prerelease SDK codenames - * for this platform - * @return the minSdkVersion to use at runtime if successful - */ - public static ParseResult<Integer> computeMinSdkVersion(@IntRange(from = 1) int minVers, - @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion, - @NonNull String[] platformSdkCodenames, @NonNull ParseInput input) { - // If it's a release SDK, make sure we meet the minimum SDK requirement. - if (minCode == null) { - if (minVers <= platformSdkVersion) { - return input.success(minVers); - } - - // We don't meet the minimum SDK requirement. - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires newer sdk version #" + minVers - + " (current version is #" + platformSdkVersion + ")"); - } - - // If it's a pre-release SDK and the codename matches this platform, we - // definitely meet the minimum SDK requirement. - if (matchTargetCode(platformSdkCodenames, minCode)) { - return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); - } - - // Otherwise, we're looking at an incompatible pre-release SDK. - if (platformSdkCodenames.length > 0) { - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires development platform " + minCode - + " (current platform is any of " - + Arrays.toString(platformSdkCodenames) + ")"); - } else { - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires development platform " + minCode - + " but this is a release platform."); - } - } - - /** - * Computes the targetSdkVersion to use at runtime. If the package is not - * compatible with this platform, populates {@code outError[0]} with an - * error message. - * <p> - * If {@code targetCode} is not specified, e.g. the value is {@code null}, - * then the {@code targetVers} will be returned unmodified. - * <p> - * Otherwise, the behavior varies based on whether the current platform - * is a pre-release version, e.g. the {@code platformSdkCodenames} array - * has length > 0: - * <ul> - * <li>If this is a pre-release platform and the value specified by - * {@code targetCode} is contained within the array of allowed pre-release - * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. - * <li>If this is a released platform, this method will return -1 to - * indicate that the package is not compatible with this platform. - * </ul> - * - * @param targetVers targetSdkVersion number, if specified in the - * application manifest, or 0 otherwise - * @param targetCode targetSdkVersion code, if specified in the application - * manifest, or {@code null} otherwise - * @param platformSdkCodenames array of allowed pre-release SDK codenames - * for this platform - * @return the targetSdkVersion to use at runtime if successful - */ - public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers, - @Nullable String targetCode, @NonNull String[] platformSdkCodenames, - @NonNull ParseInput input) { - // If it's a release SDK, return the version number unmodified. - if (targetCode == null) { - return input.success(targetVers); - } - - // If it's a pre-release SDK and the codename matches this platform, it - // definitely targets this SDK. - if (matchTargetCode(platformSdkCodenames, targetCode)) { - return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); - } - - // Otherwise, we're looking at an incompatible pre-release SDK. - if (platformSdkCodenames.length > 0) { - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires development platform " + targetCode - + " (current platform is any of " - + Arrays.toString(platformSdkCodenames) + ")"); - } else { - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires development platform " + targetCode - + " but this is a release platform."); - } - } - - /** - * Matches a given {@code targetCode} against a set of release codeNames. Target codes can - * either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form - * {@code [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}). - */ - private static boolean matchTargetCode(@NonNull String[] codeNames, - @NonNull String targetCode) { - final String targetCodeName; - final int targetCodeIdx = targetCode.indexOf('.'); - if (targetCodeIdx == -1) { - targetCodeName = targetCode; - } else { - targetCodeName = targetCode.substring(0, targetCodeIdx); - } - return ArrayUtils.contains(codeNames, targetCodeName); - } - private static ParseResult<ParsingPackage> parseRestrictUpdateHash(int flags, ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) { if ((flags & PARSE_IS_SYSTEM_DIR) != 0) { @@ -1925,12 +1773,11 @@ public class ParsingPackageUtils { * Parse the {@code application} XML tree at the current parse location in a * <em>base APK</em> manifest. * <p> - * When adding new features, carefully consider if they should also be - * supported by split APKs. - * - * This method should avoid using a getter for fields set by this method. Prefer assigning - * a local variable and using it. Otherwise there's an ordering problem which can be broken - * if any code moves around. + * When adding new features, carefully consider if they should also be supported by split APKs. + * <p> + * This method should avoid using a getter for fields set by this method. Prefer assigning a + * local variable and using it. Otherwise there's an ordering problem which can be broken if any + * code moves around. */ private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) @@ -2276,7 +2123,7 @@ public class ParsingPackageUtils { /** * Collection of single-line, no (or little) logic assignments. Separated for readability. - * + * <p> * Flags are separated by type and by default value. They are sorted alphabetically within each * section. */ @@ -2339,6 +2186,7 @@ public class ParsingPackageUtils { .setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa)) .setDataExtractionRules( resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa)) + .setLocaleConfigRes(resId(R.styleable.AndroidManifestApplication_localeConfig, sa)) // Strings .setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa)) .setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa)) @@ -2892,7 +2740,7 @@ public class ParsingPackageUtils { R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName); String propValue = sa.getString( R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue); - if (!checkRequiredSystemProperties(propName, propValue)) { + if (!FrameworkParsingPackageUtils.checkRequiredSystemProperties(propName, propValue)) { String message = "Skipping target and overlay pair " + target + " and " + pkg.getBaseApkPath() + ": overlay ignored due to required system property: " @@ -3055,60 +2903,6 @@ public class ParsingPackageUtils { } /** - * Check if the given name is valid. - * - * @param name The name to check. - * @param requireSeparator {@code true} if the name requires containing a separator at least. - * @param requireFilename {@code true} to apply file name validation to the given name. It also - * limits length of the name to the {@link #MAX_FILE_NAME_SIZE}. - * @return Success if it's valid. - */ - public static String validateName(String name, boolean requireSeparator, - boolean requireFilename) { - final int N = name.length(); - boolean hasSep = false; - boolean front = true; - for (int i = 0; i < N; i++) { - final char c = name.charAt(i); - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - front = false; - continue; - } - if (!front) { - if ((c >= '0' && c <= '9') || c == '_') { - continue; - } - } - if (c == '.') { - hasSep = true; - front = true; - continue; - } - return "bad character '" + c + "'"; - } - if (requireFilename) { - if (!FileUtils.isValidExtFilename(name)) { - return "Invalid filename"; - } else if (N > MAX_FILE_NAME_SIZE) { - return "the length of the name is greater than " + MAX_FILE_NAME_SIZE; - } - } - return hasSep || !requireSeparator ? null : "must have at least one '.' separator"; - } - - /** - * @see #validateName(String, boolean, boolean) - */ - public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator, - boolean requireFilename) { - final String errorMessage = validateName(name, requireSeparator, requireFilename); - if (errorMessage != null) { - return input.error(errorMessage); - } - return input.success(null); - } - - /** * Parse a meta data defined on the enclosing tag. * <p>Meta data can be defined by either <meta-data> or <property> elements. */ @@ -3168,114 +2962,6 @@ public class ParsingPackageUtils { } /** - * @return {@link PublicKey} of a given encoded public key. - */ - public static final PublicKey parsePublicKey(final String encodedPublicKey) { - if (encodedPublicKey == null) { - Slog.w(TAG, "Could not parse null public key"); - return null; - } - - try { - return parsePublicKey(Base64.decode(encodedPublicKey, Base64.DEFAULT)); - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); - return null; - } - } - - /** - * @return {@link PublicKey} of the given byte array of a public key. - */ - public static final PublicKey parsePublicKey(final byte[] publicKey) { - if (publicKey == null) { - Slog.w(TAG, "Could not parse null public key"); - return null; - } - - final EncodedKeySpec keySpec; - try { - keySpec = new X509EncodedKeySpec(publicKey); - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); - return null; - } - - /* First try the key as an RSA key. */ - try { - final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return keyFactory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException e) { - Slog.wtf(TAG, "Could not parse public key: RSA KeyFactory not included in build"); - } catch (InvalidKeySpecException e) { - // Not a RSA public key. - } - - /* Now try it as a ECDSA key. */ - try { - final KeyFactory keyFactory = KeyFactory.getInstance("EC"); - return keyFactory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException e) { - Slog.wtf(TAG, "Could not parse public key: EC KeyFactory not included in build"); - } catch (InvalidKeySpecException e) { - // Not a ECDSA public key. - } - - /* Now try it as a DSA key. */ - try { - final KeyFactory keyFactory = KeyFactory.getInstance("DSA"); - return keyFactory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException e) { - Slog.wtf(TAG, "Could not parse public key: DSA KeyFactory not included in build"); - } catch (InvalidKeySpecException e) { - // Not a DSA public key. - } - - /* Not a supported key type */ - return null; - } - - /** - * Returns {@code true} if both the property name and value are empty or if the given system - * property is set to the specified value. Properties can be one or more, and if properties are - * more than one, they must be separated by comma, and count of names and values must be equal, - * and also every given system property must be set to the corresponding value. - * In all other cases, returns {@code false} - */ - public static boolean checkRequiredSystemProperties(@Nullable String rawPropNames, - @Nullable String rawPropValues) { - if (TextUtils.isEmpty(rawPropNames) || TextUtils.isEmpty(rawPropValues)) { - if (!TextUtils.isEmpty(rawPropNames) || !TextUtils.isEmpty(rawPropValues)) { - // malformed condition - incomplete - Slog.w(TAG, "Disabling overlay - incomplete property :'" + rawPropNames - + "=" + rawPropValues + "' - require both requiredSystemPropertyName" - + " AND requiredSystemPropertyValue to be specified."); - return false; - } - // no valid condition set - so no exclusion criteria, overlay will be included. - return true; - } - - final String[] propNames = rawPropNames.split(","); - final String[] propValues = rawPropValues.split(","); - - if (propNames.length != propValues.length) { - Slog.w(TAG, "Disabling overlay - property :'" + rawPropNames - + "=" + rawPropValues + "' - require both requiredSystemPropertyName" - + " AND requiredSystemPropertyValue lists to have the same size."); - return false; - } - for (int i = 0; i < propNames.length; i++) { - // Check property value: make sure it is both set and equal to expected value - final String currValue = SystemProperties.get(propNames[i]); - if (!TextUtils.equals(currValue, propValues[i])) { - return false; - } - } - return true; - } - - /** * Collect certificates from all the APKs described in the given package. Also asserts that * all APK contents are signed correctly and consistently. * @@ -3519,7 +3205,7 @@ public class ParsingPackageUtils { ArraySet<PublicKey> keys = new ArraySet<>(M); for (int j = 0; j < M; ++j) { - PublicKey pk = (PublicKey) in.readSerializable(java.security.PublicKey.class.getClassLoader(), java.security.PublicKey.class); + PublicKey pk = (PublicKey) in.readSerializable(); keys.add(pk); } diff --git a/core/java/android/content/pm/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java index 6dfb268c3d64..95fec369b95a 100644 --- a/core/java/android/content/pm/parsing/ParsingUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,14 +14,14 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; -import static android.content.pm.parsing.ParsingPackageUtils.RIGID_PARSER; +import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedIntentInfoImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.XmlResourceParser; @@ -138,7 +138,7 @@ public class ParsingUtils { final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size); for (int i = 0; i < size; ++i) { list.add(Pair.create(source.readString(), source.readParcelable( - ParsedIntentInfoImpl.class.getClassLoader(), android.content.pm.parsing.component.ParsedIntentInfo.class))); + ParsedIntentInfoImpl.class.getClassLoader(), ParsedIntentInfo.class))); } return list; diff --git a/core/java/android/content/pm/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java index 625b9d1bb479..a323e2098e54 100644 --- a/core/java/android/content/pm/parsing/PkgWithoutStateAppInfo.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/PkgWithoutStatePackageInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java index 7d758a858ea5..2bc4ee7cbc9a 100644 --- a/core/java/android/content/pm/parsing/PkgWithoutStatePackageInfo.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,14 +29,14 @@ import android.content.pm.PackageInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.pkg.FrameworkPackageUserState; import com.android.internal.R; +import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; import java.util.List; @@ -82,7 +82,7 @@ public interface PkgWithoutStatePackageInfo { * provide the same information as {@link ActivityInfo}. Effective state can be queried through * {@link android.content.pm.PackageManager#getActivityInfo(ComponentName, int)} or by * combining state from from com.android.server.pm.pkg.PackageState and - * {@link FrameworkPackageUserState}. + * {@link PackageUserState}. * * @see ActivityInfo * @see PackageInfo#activities @@ -153,7 +153,7 @@ public interface PkgWithoutStatePackageInfo { * provide the same information as {@link ProviderInfo}. Effective state can be queried through * {@link android.content.pm.PackageManager#getProviderInfo(ComponentName, int)} or by * combining state from from com.android.server.pm.pkg.PackageState and - * {@link FrameworkPackageUserState}. + * {@link PackageUserState}. * * @see ProviderInfo * @see PackageInfo#providers @@ -168,7 +168,7 @@ public interface PkgWithoutStatePackageInfo { * provide the same information as {@link ActivityInfo}. Effective state can be queried through * {@link android.content.pm.PackageManager#getReceiverInfo(ComponentName, int)} or by * combining state from from com.android.server.pm.pkg.PackageState and - * {@link FrameworkPackageUserState}. + * {@link PackageUserState}. * * Since they share several attributes, receivers are parsed as {@link ParsedActivity}, even * though they represent different functionality. @@ -222,7 +222,7 @@ public interface PkgWithoutStatePackageInfo { * provide the same information as {@link ServiceInfo}. Effective state can be queried through * {@link android.content.pm.PackageManager#getServiceInfo(ComponentName, int)} or by * combining state from from com.android.server.pm.pkg.PackageState and - * {@link FrameworkPackageUserState}. + * {@link PackageUserState}. * * @see ServiceInfo * @see PackageInfo#services diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java index 47cf28b1d9d2..2bd7cf848196 100644 --- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.pm.split; +package com.android.server.pm.split; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingPackageUtils.ParseFlags; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java index a0c3f752243c..ae42e0980fb7 100644 --- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.pm.split; +package com.android.server.pm.split; import android.annotation.NonNull; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingPackageUtils.ParseFlags; +import android.content.pm.split.SplitDependencyLoader; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; + import libcore.io.IoUtils; import java.io.IOException; diff --git a/core/java/android/content/pm/split/SplitAssetLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetLoader.java index d314e06cb4a8..845015916e60 100644 --- a/core/java/android/content/pm/split/SplitAssetLoader.java +++ b/services/core/java/com/android/server/pm/split/SplitAssetLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.pm.split; +package com.android.server.pm.split; import android.content.res.ApkAssets; import android.content.res.AssetManager; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index d47f510c8338..e07812036a0c 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -22,8 +22,8 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedIntentInfo; import android.os.Build; import android.text.TextUtils; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index d1603f54eb37..d0b50d271140 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -31,8 +31,6 @@ import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.pkg.PackageUserStateUtils; import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; @@ -63,6 +61,8 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index 27a16e9bfdda..17a5fd07f920 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -94,6 +94,7 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/"; private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/"; private static final String CONFIG_FILE_NAME = "device_state_configuration.xml"; + private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS"; /** Interface that allows reading the device state configuration. */ interface ReadableConfig { @@ -141,8 +142,8 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, for (int i = 0; i < configFlagStrings.size(); i++) { final String configFlagString = configFlagStrings.get(i); switch (configFlagString) { - case "FLAG_CANCEL_STICKY_REQUESTS": - flags |= DeviceState.FLAG_CANCEL_STICKY_REQUESTS; + case FLAG_CANCEL_OVERRIDE_REQUESTS: + flags |= DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; break; default: Slog.w(TAG, "Parsed unknown flag with name: " diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 28f65cf6d1a0..7dd942507a16 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5401,18 +5401,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!mVibrator.hasVibrator()) { return false; } - final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0; - if (hapticsDisabled && !always) { - return false; - } - VibrationEffect effect = getVibrationEffect(effectId); if (effect == null) { return false; } - - mVibrator.vibrate(uid, packageName, effect, reason, getVibrationAttributes(effectId)); + VibrationAttributes attrs = getVibrationAttributes(effectId); + if (always) { + attrs = new VibrationAttributes.Builder(attrs) + .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .build(); + } + mVibrator.vibrate(uid, packageName, effect, reason, attrs); return true; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 4b2770cef476..abfa016aef43 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -102,7 +102,6 @@ import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; @@ -290,7 +289,6 @@ public final class PowerManagerService extends SystemService private BatteryManagerInternal mBatteryManagerInternal; private DisplayManagerInternal mDisplayManagerInternal; private IBatteryStats mBatteryStats; - private IAppOpsService mAppOps; private WindowManagerPolicy mPolicy; private Notifier mNotifier; private WirelessChargerDetector mWirelessChargerDetector; @@ -298,7 +296,7 @@ public final class PowerManagerService extends SystemService private DreamManagerInternal mDreamManager; private LogicalLight mAttentionLight; - private InattentiveSleepWarningController mInattentiveSleepWarningOverlayController; + private final InattentiveSleepWarningController mInattentiveSleepWarningOverlayController; private final AmbientDisplaySuppressionController mAmbientDisplaySuppressionController; private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_POWER); @@ -318,10 +316,10 @@ public final class PowerManagerService extends SystemService // Table of all suspend blockers. // There should only be a few of these. - private final ArrayList<SuspendBlocker> mSuspendBlockers = new ArrayList<SuspendBlocker>(); + private final ArrayList<SuspendBlocker> mSuspendBlockers = new ArrayList<>(); // Table of all wake locks acquired by applications. - private final ArrayList<WakeLock> mWakeLocks = new ArrayList<WakeLock>(); + private final ArrayList<WakeLock> mWakeLocks = new ArrayList<>(); // A bitfield that summarizes the state of all active wakelocks. private int mWakeLockSummary; @@ -354,8 +352,6 @@ public final class PowerManagerService extends SystemService private long mLastScreenBrightnessBoostTime; private boolean mScreenBrightnessBoostInProgress; - private DisplayGroupPowerChangeListener mDisplayGroupPowerChangeListener; - // The suspend blocker used to keep the CPU alive while the device is booting. private final SuspendBlocker mBootingSuspendBlocker; @@ -938,7 +934,7 @@ public final class PowerManagerService extends SystemService * Handler for asynchronous operations performed by the power manager. */ Handler createHandler(Looper looper, Handler.Callback callback) { - return new Handler(looper, callback, true /*async*/); + return new Handler(looper, callback, /* async= */ true); } void invalidateIsInteractiveCaches() { @@ -973,7 +969,7 @@ public final class PowerManagerService extends SystemService mInjector = injector; mHandlerThread = new ServiceThread(TAG, - Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/); + Process.THREAD_PRIORITY_DISPLAY, /* allowIo= */ false); mHandlerThread.start(); mHandler = injector.createHandler(mHandlerThread.getLooper(), new PowerManagerHandlerCallback()); @@ -1160,18 +1156,18 @@ public final class PowerManagerService extends SystemService } } - public void systemReady(IAppOpsService appOps) { + public void systemReady() { synchronized (mLock) { mSystemReady = true; - mAppOps = appOps; mDreamManager = getLocalService(DreamManagerInternal.class); mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class); mPolicy = getLocalService(WindowManagerPolicy.class); mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class); mAttentionDetector.systemReady(mContext); mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP, new PowerGroup()); - mDisplayGroupPowerChangeListener = new DisplayGroupPowerChangeListener(); - mDisplayManagerInternal.registerDisplayGroupListener(mDisplayGroupPowerChangeListener); + DisplayGroupPowerChangeListener displayGroupPowerChangeListener = + new DisplayGroupPowerChangeListener(); + mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener); SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper()); @@ -1723,6 +1719,7 @@ public final class PowerManagerService extends SystemService } // Called from native code. + @SuppressWarnings("unused") private void userActivityFromNative(long eventTime, int event, int displayId, int flags) { userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID); } @@ -1757,7 +1754,7 @@ public final class PowerManagerService extends SystemService if (userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_ATTENTION, - 0 /* flags */, + /* flags= */ 0, Process.SYSTEM_UID)) { updatePowerStateLocked(); } @@ -2046,6 +2043,7 @@ public final class PowerManagerService extends SystemService } } + @SuppressWarnings("deprecation") @GuardedBy("mLock") private void setGlobalWakefulnessLocked(int wakefulness, long eventTime, int reason, int uid, int opUid, String opPackageName, String details) { @@ -2491,9 +2489,8 @@ public final class PowerManagerService extends SystemService * Updates the value of mWakeLockSummary to summarize the state of all active wake locks. * Note that most wake-locks are ignored when the system is asleep. * - * This function must have no other side-effects. + * This function must have no other side effects. */ - @SuppressWarnings("deprecation") @GuardedBy("mLock") private void updateWakeLockSummaryLocked(int dirty) { if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS | DIRTY_DISPLAY_GROUP_WAKEFULNESS)) @@ -2596,6 +2593,7 @@ public final class PowerManagerService extends SystemService } /** Get wake lock summary flags that correspond to the given wake lock. */ + @SuppressWarnings("deprecation") private int getWakeLockSummaryFlags(WakeLock wakeLock) { switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.PARTIAL_WAKE_LOCK: @@ -3170,7 +3168,7 @@ public final class PowerManagerService extends SystemService if (mDreamManager != null) { // Restart the dream whenever the sandman is summoned. if (startDreaming) { - mDreamManager.stopDream(false /*immediate*/); + mDreamManager.stopDream(/* immediate= */ false); mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING); } isDreaming = mDreamManager.isDreaming(); @@ -3254,7 +3252,7 @@ public final class PowerManagerService extends SystemService // Stop dream. if (isDreaming) { - mDreamManager.stopDream(false /*immediate*/); + mDreamManager.stopDream(/* immediate= */ false); } } @@ -3518,7 +3516,7 @@ public final class PowerManagerService extends SystemService mDirty |= DIRTY_PROXIMITY_POSITIVE; userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, - 0 /* flags */, Process.SYSTEM_UID); + /* flags= */ 0, Process.SYSTEM_UID); updatePowerStateLocked(); } } @@ -4302,7 +4300,7 @@ public final class PowerManagerService extends SystemService if (sQuiescent) { // Pass the optional "quiescent" argument to the bootloader to let it know // that it should not turn the screen/lights on. - if (reason != ""){ + if (!"".equals(reason)) { reason += ","; } reason = reason + "quiescent"; @@ -5412,8 +5410,8 @@ public final class PowerManagerService extends SystemService ws = new WorkSource(); // XXX should WorkSource have a way to set uids as an int[] instead of adding them // one at a time? - for (int i = 0; i < uids.length; i++) { - ws.add(uids[i]); + for (int uid : uids) { + ws.add(uid); } } updateWakeLockWorkSource(lock, ws, null); @@ -5954,7 +5952,8 @@ public final class PowerManagerService extends SystemService // if uid is of root's, we permit this operation straight away if (uid != Process.ROOT_UID) { if (!Settings.checkAndNoteWriteSettingsOperation(mContext, uid, - Settings.getPackageNameForUid(mContext, uid), true)) { + Settings.getPackageNameForUid(mContext, uid), /* attributionTag= */ null, + /* throwException= */ true)) { return; } } @@ -6118,6 +6117,7 @@ public final class PowerManagerService extends SystemService for (String arg : args) { if (arg.equals("--proto")) { isDumpProto = true; + break; } } try { diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index ef0079e0c01f..ca675973b2fd 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -35,7 +35,6 @@ import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; - import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils; @@ -313,12 +312,12 @@ public final class PowerStatsLogger extends Handler { return mStartWallTime; } - public PowerStatsLogger(Context context, File dataStoragePath, + public PowerStatsLogger(Context context, Looper looper, File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - super(Looper.getMainLooper()); + super(looper); mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime(); if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime); mPowerStatsHALWrapper = powerStatsHALWrapper; diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index bb52c1dc1a29..9953ca8f9b65 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -28,6 +28,7 @@ import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.UserHandle; import android.power.PowerStatsInternal; import android.util.Slog; @@ -79,6 +80,9 @@ public class PowerStatsService extends SystemService { private StatsPullAtomCallbackImpl mPullAtomCallback; @Nullable private PowerStatsInternal mPowerStatsInternal; + @Nullable + @GuardedBy("this") + private Looper mLooper; @VisibleForTesting static class Injector { @@ -127,12 +131,12 @@ public class PowerStatsService extends SystemService { } } - PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String meterCacheFilename, + PowerStatsLogger createPowerStatsLogger(Context context, Looper looper, + File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - return new PowerStatsLogger(context, dataStoragePath, + return new PowerStatsLogger(context, looper, dataStoragePath, meterFilename, meterCacheFilename, modelFilename, modelCacheFilename, residencyFilename, residencyCacheFilename, @@ -229,11 +233,11 @@ public class PowerStatsService extends SystemService { mDataStoragePath = mInjector.createDataStoragePath(); // Only start logger and triggers if initialization is successful. - mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath, - mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(), - mInjector.createModelFilename(), mInjector.createModelCacheFilename(), - mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(), - getPowerStatsHal()); + mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, getLooper(), + mDataStoragePath, mInjector.createMeterFilename(), + mInjector.createMeterCacheFilename(), mInjector.createModelFilename(), + mInjector.createModelCacheFilename(), mInjector.createResidencyFilename(), + mInjector.createResidencyCacheFilename(), getPowerStatsHal()); mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger); mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger); } else { @@ -245,6 +249,17 @@ public class PowerStatsService extends SystemService { return mInjector.getPowerStatsHALWrapperImpl(); } + private Looper getLooper() { + synchronized (this) { + if (mLooper == null) { + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + return thread.getLooper(); + } + return mLooper; + } + } + public PowerStatsService(Context context) { this(context, new Injector()); } @@ -260,9 +275,7 @@ public class PowerStatsService extends SystemService { private final Handler mHandler; LocalService() { - HandlerThread thread = new HandlerThread(TAG); - thread.start(); - mHandler = new Handler(thread.getLooper()); + mHandler = new Handler(getLooper()); } diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java index 068626588745..21d4cbbbcca7 100644 --- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; +import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Log; @@ -211,6 +212,30 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn } } + void checkRecognitionSupport( + Intent recognizerIntent, + IRecognitionSupportCallback callback) { + + if (!mConnected) { + try { + callback.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to report the connection broke to the caller.", e); + e.printStackTrace(); + } + return; + } + run(service -> service.checkRecognitionSupport(recognizerIntent, callback)); + } + + void triggerModelDownload(Intent recognizerIntent) { + if (!mConnected) { + Slog.e(TAG, "#downloadModel failed due to connection."); + return; + } + run(service -> service.triggerModelDownload(recognizerIntent)); + } + void shutdown() { synchronized (mLock) { if (this.mListener == null) { diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 5442e5b0413f..ae23b9e46d23 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -33,6 +33,7 @@ import android.permission.PermissionManager; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; import android.speech.IRecognitionServiceManagerCallback; +import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Slog; @@ -169,6 +170,18 @@ final class SpeechRecognitionManagerServiceImpl extends clientToken.unlinkToDeath(deathRecipient, 0); } } + + @Override + public void checkRecognitionSupport( + Intent recognizerIntent, + IRecognitionSupportCallback callback) { + service.checkRecognitionSupport(recognizerIntent, callback); + } + + @Override + public void triggerModelDownload(Intent recognizerIntent) { + service.triggerModelDownload(recognizerIntent); + } }); } catch (RemoteException e) { Slog.e(TAG, "Error creating a speech recognition session", e); diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 7f50cd6ddb4f..b7ca4defda5f 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -121,7 +121,6 @@ import android.os.IThermalService; import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -231,7 +230,6 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -2332,11 +2330,7 @@ public class StatsPullAtomService extends SystemService { List<ProcessMemoryState> managedProcessList = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); - managedProcessList.sort(Comparator.comparingInt(x -> x.oomScore)); for (ProcessMemoryState process : managedProcessList) { - if (process.uid == Process.SYSTEM_UID) { - continue; - } KernelAllocationStats.ProcessDmabuf proc = KernelAllocationStats.getDmabufAllocations(process.pid); if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) { @@ -2353,6 +2347,32 @@ public class StatsPullAtomService extends SystemService { proc.mappedSizeKb, proc.mappedBuffersCount)); } + SparseArray<String> processCmdlines = getProcessCmdlines(); + managedProcessList.forEach(managedProcess -> processCmdlines.delete(managedProcess.pid)); + int size = processCmdlines.size(); + for (int i = 0; i < size; ++i) { + int pid = processCmdlines.keyAt(i); + int uid = getUidForPid(pid); + // ignore root processes (unlikely to be interesting) + if (uid <= 0) { + continue; + } + KernelAllocationStats.ProcessDmabuf proc = + KernelAllocationStats.getDmabufAllocations(pid); + if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) { + continue; + } + pulledData.add( + FrameworkStatsLog.buildStatsEvent( + atomTag, + uid, + processCmdlines.valueAt(i), + -1001 /*Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.*/, + proc.retainedSizeKb, + proc.retainedBuffersCount, + proc.mappedSizeKb, + proc.mappedBuffersCount)); + } return StatsManager.PULL_SUCCESS; } @@ -3435,7 +3455,11 @@ public class StatsPullAtomService extends SystemService { convertTimeZoneSuggestionToProtoBytes( metricsState.getLatestTelephonySuggestion()), convertTimeZoneSuggestionToProtoBytes( - metricsState.getLatestGeolocationSuggestion()) + metricsState.getLatestGeolocationSuggestion()), + metricsState.isTelephonyTimeZoneFallbackSupported(), + metricsState.getDeviceTimeZoneId(), + metricsState.isEnhancedMetricsCollectionEnabled(), + metricsState.getGeoDetectionRunInBackgroundEnabled() )); } catch (RuntimeException e) { Slog.e(TAG, "Getting time zone detection state failed: ", e); @@ -3482,6 +3506,14 @@ public class StatsPullAtomService extends SystemService { android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_ORDINALS, zoneIdOrdinal); } + String[] zoneIds = suggestion.getZoneIds(); + if (zoneIds != null) { + for (String zoneId : zoneIds) { + protoOutputStream.write( + android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_IDS, + zoneId); + } + } } protoOutputStream.flush(); closeQuietly(byteArrayOutputStream); @@ -4587,7 +4619,8 @@ public class StatsPullAtomService extends SystemService { int matchContentFrameRateUserPreference = displayManager.getMatchContentFrameRateUserPreference(); byte[] userDisabledHdrTypes = toBytes(displayManager.getUserDisabledHdrTypes()); - Display.Mode userPreferredDisplayMode = displayManager.getUserPreferredDisplayMode(); + Display.Mode userPreferredDisplayMode = + displayManager.getGlobalUserPreferredDisplayMode(); int userPreferredWidth = userPreferredDisplayMode != null ? userPreferredDisplayMode.getPhysicalWidth() : -1; int userPreferredHeight = userPreferredDisplayMode != null diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index e98fa28634a4..fe7416782262 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -528,12 +528,13 @@ public final class TextClassificationManagerService extends ITextClassifierServi callback.onFailure(); return; } - textClassifierServiceConsumer.accept(serviceState.mService); + consumeServiceNoExceptLocked(textClassifierServiceConsumer, serviceState.mService); } else { serviceState.mPendingRequests.add( new PendingRequest( methodName, - () -> textClassifierServiceConsumer.accept(serviceState.mService), + () -> consumeServiceNoExceptLocked( + textClassifierServiceConsumer, serviceState.mService), callback::onFailure, callback.asBinder(), this, serviceState, @@ -542,6 +543,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + private static void consumeServiceNoExceptLocked( + @NonNull ThrowingConsumer<ITextClassifierService> textClassifierServiceConsumer, + @Nullable ITextClassifierService service) { + try { + textClassifierServiceConsumer.accept(service); + } catch (RuntimeException | Error e) { + Slog.e(LOG_TAG, "Exception when consume textClassifierService: " + e); + } + } + private static ITextClassifierCallback wrap(ITextClassifierCallback orig) { return new CallbackWrapper(orig); } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index b23f11aa8597..36ab111dfccb 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -281,15 +281,7 @@ public class LocationTimeZoneManagerService extends Binder { LocationTimeZoneProvider primary = mPrimaryProviderConfig.createProvider(); LocationTimeZoneProvider secondary = mSecondaryProviderConfig.createProvider(); LocationTimeZoneProviderController.MetricsLogger metricsLogger = - new LocationTimeZoneProviderController.MetricsLogger() { - @Override - public void onStateChange( - @LocationTimeZoneProviderController.State String state) { - // TODO b/200279201 - wire this up to metrics code - // No-op. - } - }; - + new RealControllerMetricsLogger(); boolean recordStateChanges = mServiceConfigAccessor.getRecordStateChangesForTests(); LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(mThreadingDomain, metricsLogger, diff --git a/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java b/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java new file mode 100644 index 000000000000..9cb36efb8679 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/location/RealControllerMetricsLogger.java @@ -0,0 +1,80 @@ +/* + * 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.timezonedetector.location; + +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__CERTAIN; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__DESTROYED; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__FAILED; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__INITIALIZING; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__PROVIDERS_INITIALIZING; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__STOPPED; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNCERTAIN; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNKNOWN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_PROVIDERS_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNKNOWN; + +import com.android.internal.util.FrameworkStatsLog; +import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; + +/** + * The real implementation of {@link LocationTimeZoneProviderController.MetricsLogger} which logs + * using {@link FrameworkStatsLog}. + */ +final class RealControllerMetricsLogger + implements LocationTimeZoneProviderController.MetricsLogger { + + RealControllerMetricsLogger() { + } + + @Override + public void onStateChange(@State String state) { + FrameworkStatsLog.write( + FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED, + metricsState(state)); + } + + private static int metricsState(@State String state) { + switch (state) { + case STATE_PROVIDERS_INITIALIZING: + // Disable lint check (line length) for generated long constant name. + // CHECKSTYLE:OFF Generated code + return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__PROVIDERS_INITIALIZING; + // CHECKSTYLE:ON Generated code + case STATE_STOPPED: + return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__STOPPED; + case STATE_INITIALIZING: + return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__INITIALIZING; + case STATE_CERTAIN: + return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__CERTAIN; + case STATE_UNCERTAIN: + return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNCERTAIN; + case STATE_DESTROYED: + return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__DESTROYED; + case STATE_FAILED: + return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__FAILED; + case STATE_UNKNOWN: + default: + return LOCATION_TIME_ZONE_PROVIDER_CONTROLLER_STATE_CHANGED__STATE__UNKNOWN; + } + } +} diff --git a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java index e19ec8472e0a..fe543adf6302 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java @@ -41,12 +41,12 @@ import com.android.server.timezonedetector.location.LocationTimeZoneProvider.Pro * The real implementation of {@link ProviderMetricsLogger} which logs using * {@link FrameworkStatsLog}. */ -public class RealProviderMetricsLogger implements ProviderMetricsLogger { +final class RealProviderMetricsLogger implements ProviderMetricsLogger { @IntRange(from = 0, to = 1) private final int mProviderIndex; - public RealProviderMetricsLogger(@IntRange(from = 0, to = 1) int providerIndex) { + RealProviderMetricsLogger(@IntRange(from = 0, to = 1) int providerIndex) { mProviderIndex = providerIndex; } diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 59f8e54a0408..593250cb9293 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -123,6 +123,7 @@ public class TrustAgentWrapper { public void handleMessage(Message msg) { switch (msg.what) { case MSG_GRANT_TRUST: + // TODO(b/213631675): Respect FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE if (!isConnected()) { Log.w(TAG, "Agent is not connected, cannot grant trust: " + mName.flattenToShortString()); @@ -460,6 +461,19 @@ public class TrustAgentWrapper { } /** + * @see android.service.trust.TrustAgentService#onUserRequestedUnlock() + */ + public void onUserRequestedUnlock() { + try { + if (mTrustAgentService != null) { + mTrustAgentService.onUserRequestedUnlock(); + } + } catch (RemoteException e) { + onError(e); + } + } + + /** * @see android.service.trust.TrustAgentService#onUnlockLockout(int) */ public void onUnlockLockout(int timeoutMs) { diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 4b71742c86c8..150eebb8276e 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -46,6 +46,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; @@ -122,6 +123,9 @@ public class TrustManagerService extends SystemService { private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13; private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14; private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15; + public static final int MSG_USER_REQUESTED_UNLOCK = 16; + + private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except"; private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000; private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser"; @@ -635,11 +639,18 @@ public class TrustManagerService extends SystemService { } } + private void refreshDeviceLockedForUser(int userId) { + refreshDeviceLockedForUser(userId, UserHandle.USER_NULL); + } + /** * Update the user's locked state. Only applicable to users with a real keyguard * ({@link UserInfo#supportsSwitchToByUser}) and unsecured managed profiles. + * + * If this is called due to an unlock operation set unlockedUser to prevent the lock from + * being prematurely reset for that user while keyguard is still in the process of going away. */ - private void refreshDeviceLockedForUser(int userId) { + private void refreshDeviceLockedForUser(int userId, int unlockedUser) { if (userId != UserHandle.USER_ALL && userId < UserHandle.USER_SYSTEM) { Log.e(TAG, "refreshDeviceLockedForUser(userId=" + userId + "): Invalid user handle," + " must be USER_ALL or a specific user.", new Throwable("here")); @@ -675,6 +686,7 @@ public class TrustManagerService extends SystemService { boolean trusted = aggregateIsTrusted(id); boolean showingKeyguard = true; boolean biometricAuthenticated = false; + boolean currentUserIsUnlocked = false; if (mCurrentUser == id) { synchronized(mUsersUnlockedByBiometric) { @@ -683,10 +695,17 @@ public class TrustManagerService extends SystemService { try { showingKeyguard = wm.isKeyguardLocked(); } catch (RemoteException e) { + Log.w(TAG, "Unable to check keyguard lock state", e); } + currentUserIsUnlocked = unlockedUser == id; } - boolean deviceLocked = secure && showingKeyguard && !trusted && - !biometricAuthenticated; + final boolean deviceLocked = secure && showingKeyguard && !trusted + && !biometricAuthenticated; + if (deviceLocked && currentUserIsUnlocked) { + // keyguard is finishing but may not have completed going away yet + continue; + } + setDeviceLockedForUser(id, deviceLocked); } } @@ -963,6 +982,15 @@ public class TrustManagerService extends SystemService { } } + private void dispatchUserRequestedUnlock(int userId) { + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo info = mActiveAgents.valueAt(i); + if (info.userId == userId) { + info.agent.onUserRequestedUnlock(); + } + } + } + private void dispatchUnlockLockout(int timeoutMs, int userId) { for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); @@ -1092,6 +1120,12 @@ public class TrustManagerService extends SystemService { } @Override + public void reportUserRequestedUnlock(int userId) throws RemoteException { + enforceReportPermission(); + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, userId).sendToTarget(); + } + + @Override public void reportUnlockLockout(int timeoutMs, int userId) throws RemoteException { enforceReportPermission(); mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_LOCKOUT, timeoutMs, userId) @@ -1306,13 +1340,20 @@ public class TrustManagerService extends SystemService { } @Override - public void clearAllBiometricRecognized(BiometricSourceType biometricSource) { + public void clearAllBiometricRecognized( + BiometricSourceType biometricSource, int unlockedUser) { enforceReportPermission(); synchronized(mUsersUnlockedByBiometric) { mUsersUnlockedByBiometric.clear(); } - mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, UserHandle.USER_ALL, - 0 /* arg2 */).sendToTarget(); + Message message = mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, + UserHandle.USER_ALL, 0 /* arg2 */); + if (unlockedUser >= 0) { + Bundle bundle = new Bundle(); + bundle.putInt(REFRESH_DEVICE_LOCKED_EXCEPT_USER, unlockedUser); + message.setData(bundle); + } + message.sendToTarget(); } }; @@ -1364,6 +1405,9 @@ public class TrustManagerService extends SystemService { case MSG_DISPATCH_UNLOCK_ATTEMPT: dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2); break; + case MSG_USER_REQUESTED_UNLOCK: + dispatchUserRequestedUnlock(msg.arg1); + break; case MSG_DISPATCH_UNLOCK_LOCKOUT: dispatchUnlockLockout(msg.arg1, msg.arg2); break; @@ -1404,9 +1448,11 @@ public class TrustManagerService extends SystemService { break; case MSG_REFRESH_DEVICE_LOCKED_FOR_USER: if (msg.arg2 == 1) { - updateTrust(msg.arg1, 0 /* flags */, true); + updateTrust(msg.arg1, 0 /* flags */, true /* isFromUnlock */); } - refreshDeviceLockedForUser(msg.arg1); + final int unlockedUser = msg.getData().getInt( + REFRESH_DEVICE_LOCKED_EXCEPT_USER, UserHandle.USER_NULL); + refreshDeviceLockedForUser(msg.arg1, unlockedUser); break; case MSG_SCHEDULE_TRUST_TIMEOUT: handleScheduleTrustTimeout(msg.arg1, msg.arg2); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 568e4b8de3ba..e786fa270a37 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -69,6 +69,7 @@ import android.media.tv.TvInputManager; import android.media.tv.TvInputService; import android.media.tv.TvStreamConfig; import android.media.tv.TvTrackInfo; +import android.media.tv.tunerresourcemanager.TunerResourceManager; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -1850,18 +1851,19 @@ public final class TvInputManagerService extends SystemService { } @Override - public void setIAppNotificationEnabled(IBinder sessionToken, boolean enabled, int userId) { + public void setInteractiveAppNotificationEnabled( + IBinder sessionToken, boolean enabled, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "setIAppNotificationEnabled"); + userId, "setInteractiveAppNotificationEnabled"); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { try { getSessionLocked(sessionToken, callingUid, resolvedUserId) - .setIAppNotificationEnabled(enabled); + .setInteractiveAppNotificationEnabled(enabled); } catch (RemoteException | SessionNotFoundException e) { - Slog.e(TAG, "error in setIAppNotificationEnabled", e); + Slog.e(TAG, "error in setInteractiveAppNotificationEnabled", e); } } } finally { @@ -2538,6 +2540,31 @@ public final class TvInputManagerService extends SystemService { } @Override + public int getClientPriority(int useCase, String sessionId) { + final int callingPid = Binder.getCallingPid(); + final long identity = Binder.clearCallingIdentity(); + try { + int clientPid = TvInputManager.UNKNOWN_CLIENT_PID; + if (sessionId != null) { + synchronized (mLock) { + try { + clientPid = getClientPidLocked(sessionId); + } catch (ClientPidNotFoundException e) { + Slog.e(TAG, "error in getClientPriority", e); + } + } + } else { + clientPid = callingPid; + } + TunerResourceManager trm = (TunerResourceManager) + mContext.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE); + return trm.getClientPriority(useCase, clientPid); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public List<TunedInfo> getCurrentTunedInfos(@UserIdInt int userId) { if (mContext.checkCallingPermission(android.Manifest.permission.ACCESS_TUNED_INFO) != PackageManager.PERMISSION_GRANTED) { @@ -2585,9 +2612,9 @@ public final class TvInputManagerService extends SystemService { @GuardedBy("mLock") private int getClientPidLocked(String sessionId) - throws IllegalStateException { + throws ClientPidNotFoundException { if (mSessionIdToSessionStateMap.get(sessionId) == null) { - throw new IllegalStateException("Client Pid not found with sessionId " + throw new ClientPidNotFoundException("Client Pid not found with sessionId " + sessionId); } return mSessionIdToSessionStateMap.get(sessionId).callingPid; @@ -3512,6 +3539,23 @@ public final class TvInputManagerService extends SystemService { } @Override + public void onSignalStrength(int strength) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onSignalStrength(" + strength + ")"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onSignalStrength(strength, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onSignalStrength", e); + } + } + } + + @Override public void onTuned(Uri channelUri) { synchronized (mLock) { if (DEBUG) { diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java index a2bf2fe7df5e..6058d8873e94 100644 --- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java @@ -34,15 +34,15 @@ import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; -import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppManager; -import android.media.tv.interactive.ITvIAppManagerCallback; -import android.media.tv.interactive.ITvIAppService; -import android.media.tv.interactive.ITvIAppServiceCallback; -import android.media.tv.interactive.ITvIAppSession; -import android.media.tv.interactive.ITvIAppSessionCallback; -import android.media.tv.interactive.TvIAppInfo; +import android.media.tv.interactive.ITvInteractiveAppClient; +import android.media.tv.interactive.ITvInteractiveAppManagerCallback; +import android.media.tv.interactive.ITvInteractiveAppService; +import android.media.tv.interactive.ITvInteractiveAppServiceCallback; +import android.media.tv.interactive.ITvInteractiveAppSession; +import android.media.tv.interactive.ITvInteractiveAppSessionCallback; import android.media.tv.interactive.TvIAppService; +import android.media.tv.interactive.TvInteractiveAppInfo; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -53,6 +53,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.view.InputChannel; @@ -112,54 +113,55 @@ public class TvIAppManagerService extends SystemService { } @GuardedBy("mLock") - private void buildTvIAppServiceListLocked(int userId, String[] updatedPackages) { + private void buildTvInteractiveAppServiceListLocked(int userId, String[] updatedPackages) { UserState userState = getOrCreateUserStateLocked(userId); userState.mPackageSet.clear(); if (DEBUG) { - Slogf.d(TAG, "buildTvIAppServiceListLocked"); + Slogf.d(TAG, "buildTvInteractiveAppServiceListLocked"); } PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServicesAsUser( new Intent(TvIAppService.SERVICE_INTERFACE), PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId); - List<TvIAppInfo> iAppList = new ArrayList<>(); + List<TvInteractiveAppInfo> iAppList = new ArrayList<>(); for (ResolveInfo ri : services) { ServiceInfo si = ri.serviceInfo; - // TODO: add BIND_TV_IAPP permission and check it here + // TODO: add BIND_TV_INTERACTIVE_APP permission and check it here ComponentName component = new ComponentName(si.packageName, si.name); try { - TvIAppInfo info = new TvIAppInfo.Builder(mContext, component).build(); + TvInteractiveAppInfo info = + new TvInteractiveAppInfo(mContext, component); iAppList.add(info); } catch (Exception e) { - Slogf.e(TAG, "failed to load TV IApp service " + si.name, e); + Slogf.e(TAG, "failed to load TV Interactive App service " + si.name, e); continue; } userState.mPackageSet.add(si.packageName); } // sort the iApp list by iApp service id - Collections.sort(iAppList, Comparator.comparing(TvIAppInfo::getId)); - Map<String, TvIAppState> iAppMap = new HashMap<>(); + Collections.sort(iAppList, Comparator.comparing(TvInteractiveAppInfo::getId)); + Map<String, TvInteractiveAppState> iAppMap = new HashMap<>(); ArrayMap<String, Integer> tiasAppCount = new ArrayMap<>(iAppMap.size()); - for (TvIAppInfo info : iAppList) { + for (TvInteractiveAppInfo info : iAppList) { String iAppServiceId = info.getId(); if (DEBUG) { Slogf.d(TAG, "add " + iAppServiceId); } - // Running count of IApp for each IApp service + // Running count of Interactive App for each Interactive App service Integer count = tiasAppCount.get(iAppServiceId); count = count == null ? 1 : count + 1; tiasAppCount.put(iAppServiceId, count); - TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId); + TvInteractiveAppState iAppState = userState.mIAppMap.get(iAppServiceId); if (iAppState == null) { - iAppState = new TvIAppState(); + iAppState = new TvInteractiveAppState(); } iAppState.mInfo = info; - iAppState.mUid = getIAppUid(info); + iAppState.mUid = getInteractiveAppUid(info); iAppState.mComponentName = info.getComponent(); iAppMap.put(iAppServiceId, iAppState); iAppState.mIAppNumber = count; @@ -167,14 +169,14 @@ public class TvIAppManagerService extends SystemService { for (String iAppServiceId : iAppMap.keySet()) { if (!userState.mIAppMap.containsKey(iAppServiceId)) { - notifyIAppServiceAddedLocked(userState, iAppServiceId); + notifyInteractiveAppServiceAddedLocked(userState, iAppServiceId); } else if (updatedPackages != null) { // Notify the package updates ComponentName component = iAppMap.get(iAppServiceId).mInfo.getComponent(); for (String updatedPackage : updatedPackages) { if (component.getPackageName().equals(updatedPackage)) { updateServiceConnectionLocked(component, userId); - notifyIAppServiceUpdatedLocked(userState, iAppServiceId); + notifyInteractiveAppServiceUpdatedLocked(userState, iAppServiceId); break; } } @@ -183,12 +185,12 @@ public class TvIAppManagerService extends SystemService { for (String iAppServiceId : userState.mIAppMap.keySet()) { if (!iAppMap.containsKey(iAppServiceId)) { - TvIAppInfo info = userState.mIAppMap.get(iAppServiceId).mInfo; + TvInteractiveAppInfo info = userState.mIAppMap.get(iAppServiceId).mInfo; ServiceState serviceState = userState.mServiceStateMap.get(info.getComponent()); if (serviceState != null) { abortPendingCreateSessionRequestsLocked(serviceState, iAppServiceId, userId); } - notifyIAppServiceRemovedLocked(userState, iAppServiceId); + notifyInteractiveAppServiceRemovedLocked(userState, iAppServiceId); } } @@ -197,48 +199,56 @@ public class TvIAppManagerService extends SystemService { } @GuardedBy("mLock") - private void notifyIAppServiceAddedLocked(UserState userState, String iAppServiceId) { + private void notifyInteractiveAppServiceAddedLocked(UserState userState, String iAppServiceId) { if (DEBUG) { - Slog.d(TAG, "notifyIAppServiceAddedLocked(iAppServiceId=" + iAppServiceId + ")"); + Slog.d(TAG, "notifyInteractiveAppServiceAddedLocked(iAppServiceId=" + + iAppServiceId + ")"); } int n = userState.mCallbacks.beginBroadcast(); for (int i = 0; i < n; ++i) { try { - userState.mCallbacks.getBroadcastItem(i).onIAppServiceAdded(iAppServiceId); + userState.mCallbacks.getBroadcastItem(i) + .onInteractiveAppServiceAdded(iAppServiceId); } catch (RemoteException e) { - Slog.e(TAG, "failed to report added IApp service to callback", e); + Slog.e(TAG, "failed to report added Interactive App service to callback", e); } } userState.mCallbacks.finishBroadcast(); } @GuardedBy("mLock") - private void notifyIAppServiceRemovedLocked(UserState userState, String iAppServiceId) { + private void notifyInteractiveAppServiceRemovedLocked( + UserState userState, String iAppServiceId) { if (DEBUG) { - Slog.d(TAG, "notifyIAppServiceRemovedLocked(iAppServiceId=" + iAppServiceId + ")"); + Slog.d(TAG, "notifyInteractiveAppServiceRemovedLocked(iAppServiceId=" + + iAppServiceId + ")"); } int n = userState.mCallbacks.beginBroadcast(); for (int i = 0; i < n; ++i) { try { - userState.mCallbacks.getBroadcastItem(i).onIAppServiceRemoved(iAppServiceId); + userState.mCallbacks.getBroadcastItem(i) + .onInteractiveAppServiceRemoved(iAppServiceId); } catch (RemoteException e) { - Slog.e(TAG, "failed to report removed IApp service to callback", e); + Slog.e(TAG, "failed to report removed Interactive App service to callback", e); } } userState.mCallbacks.finishBroadcast(); } @GuardedBy("mLock") - private void notifyIAppServiceUpdatedLocked(UserState userState, String iAppServiceId) { + private void notifyInteractiveAppServiceUpdatedLocked( + UserState userState, String iAppServiceId) { if (DEBUG) { - Slog.d(TAG, "notifyIAppServiceUpdatedLocked(iAppServiceId=" + iAppServiceId + ")"); + Slog.d(TAG, "notifyInteractiveAppServiceUpdatedLocked(iAppServiceId=" + + iAppServiceId + ")"); } int n = userState.mCallbacks.beginBroadcast(); for (int i = 0; i < n; ++i) { try { - userState.mCallbacks.getBroadcastItem(i).onIAppServiceUpdated(iAppServiceId); + userState.mCallbacks.getBroadcastItem(i) + .onInteractiveAppServiceUpdated(iAppServiceId); } catch (RemoteException e) { - Slog.e(TAG, "failed to report updated IApp service to callback", e); + Slog.e(TAG, "failed to report updated Interactive App service to callback", e); } } userState.mCallbacks.finishBroadcast(); @@ -262,7 +272,7 @@ public class TvIAppManagerService extends SystemService { userState.mCallbacks.finishBroadcast(); } - private int getIAppUid(TvIAppInfo info) { + private int getInteractiveAppUid(TvInteractiveAppInfo info) { try { return getContext().getPackageManager().getApplicationInfo( info.getServiceInfo().packageName, 0).uid; @@ -286,18 +296,18 @@ public class TvIAppManagerService extends SystemService { registerBroadcastReceivers(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { synchronized (mLock) { - buildTvIAppServiceListLocked(mCurrentUserId, null); + buildTvInteractiveAppServiceListLocked(mCurrentUserId, null); } } } private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { - private void buildTvIAppServiceList(String[] packages) { + private void buildTvInteractiveAppServiceList(String[] packages) { int userId = getChangingUserId(); synchronized (mLock) { if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { - buildTvIAppServiceListLocked(userId, packages); + buildTvInteractiveAppServiceListLocked(userId, packages); } } } @@ -305,9 +315,9 @@ public class TvIAppManagerService extends SystemService { @Override public void onPackageUpdateFinished(String packageName, int uid) { if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")"); - // This callback is invoked when the TV iApp service is reinstalled. + // This callback is invoked when the TV interactive App service is reinstalled. // In this case, isReplacing() always returns true. - buildTvIAppServiceList(new String[] { packageName }); + buildTvInteractiveAppServiceList(new String[] { packageName }); } @Override @@ -318,7 +328,7 @@ public class TvIAppManagerService extends SystemService { // This callback is invoked when the media on which some packages exist become // available. if (isReplacing()) { - buildTvIAppServiceList(packages); + buildTvInteractiveAppServiceList(packages); } } @@ -331,7 +341,7 @@ public class TvIAppManagerService extends SystemService { + ")"); } if (isReplacing()) { - buildTvIAppServiceList(packages); + buildTvInteractiveAppServiceList(packages); } } @@ -339,17 +349,19 @@ public class TvIAppManagerService extends SystemService { public void onSomePackagesChanged() { if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()"); if (isReplacing()) { - if (DEBUG) Slogf.d(TAG, "Skipped building TV iApp list due to replacing"); - // When the package is updated, buildTvIAppServiceListLocked is called in other - // methods instead. + if (DEBUG) { + Slogf.d(TAG, "Skipped building TV interactive App list due to replacing"); + } + // When the package is updated, buildTvInteractiveAppServiceListLocked is called + // in other methods instead. return; } - buildTvIAppServiceList(null); + buildTvInteractiveAppServiceList(null); } @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { - // The iApp list needs to be updated in any cases, regardless of whether + // The interactive App list needs to be updated in any cases, regardless of whether // it happened to the whole package or a specific component. Returning true so that // the update can be handled in {@link #onSomePackagesChanged}. return true; @@ -401,7 +413,7 @@ public class TvIAppManagerService extends SystemService { unbindServiceOfUserLocked(mCurrentUserId); mCurrentUserId = userId; - buildTvIAppServiceListLocked(userId, null); + buildTvInteractiveAppServiceListLocked(userId, null); } } @@ -486,7 +498,7 @@ public class TvIAppManagerService extends SystemService { @GuardedBy("mLock") private void startProfileLocked(int userId) { mRunningProfiles.add(userId); - buildTvIAppServiceListLocked(userId, null); + buildTvInteractiveAppServiceListLocked(userId, null); } @GuardedBy("mLock") @@ -601,13 +613,14 @@ public class TvIAppManagerService extends SystemService { } @GuardedBy("mLock") - private ITvIAppSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) { + private ITvInteractiveAppSession getSessionLocked( + IBinder sessionToken, int callingUid, int userId) { return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId)); } @GuardedBy("mLock") - private ITvIAppSession getSessionLocked(SessionState sessionState) { - ITvIAppSession session = sessionState.mSession; + private ITvInteractiveAppSession getSessionLocked(SessionState sessionState) { + ITvInteractiveAppSession session = sessionState.mSession; if (session == null) { throw new IllegalStateException("Session not yet created for token " + sessionState.mSessionToken); @@ -618,15 +631,15 @@ public class TvIAppManagerService extends SystemService { private final class BinderService extends ITvIAppManager.Stub { @Override - public List<TvIAppInfo> getTvIAppServiceList(int userId) { + public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId) { final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), - Binder.getCallingUid(), userId, "getTvIAppServiceList"); + Binder.getCallingUid(), userId, "getTvInteractiveAppServiceList"); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(resolvedUserId); - List<TvIAppInfo> iAppList = new ArrayList<>(); - for (TvIAppState state : userState.mIAppMap.values()) { + List<TvInteractiveAppInfo> iAppList = new ArrayList<>(); + for (TvInteractiveAppState state : userState.mIAppMap.values()) { iAppList.add(state.mInfo); } return iAppList; @@ -645,7 +658,7 @@ public class TvIAppManagerService extends SystemService { try { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(resolvedUserId); - TvIAppState iAppState = userState.mIAppMap.get(tiasId); + TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId); if (iAppState == null) { Slogf.e(TAG, "failed to prepare TIAS - unknown TIAS id " + tiasId); return; @@ -673,16 +686,52 @@ public class TvIAppManagerService extends SystemService { } @Override - public void notifyAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) { + public void registerAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) { + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), + Binder.getCallingUid(), userId, "registerAppLinkInfo"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(resolvedUserId); + TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId); + if (iAppState == null) { + Slogf.e(TAG, "failed to registerAppLinkInfo - unknown TIAS id " + + tiasId); + return; + } + ComponentName componentName = iAppState.mInfo.getComponent(); + ServiceState serviceState = userState.mServiceStateMap.get(componentName); + if (serviceState == null) { + serviceState = new ServiceState( + componentName, tiasId, resolvedUserId); + serviceState.addPendingAppLink(appLinkInfo, true); + userState.mServiceStateMap.put(componentName, serviceState); + updateServiceConnectionLocked(componentName, resolvedUserId); + } else if (serviceState.mService != null) { + serviceState.mService.registerAppLinkInfo(appLinkInfo); + } else { + serviceState.addPendingAppLink(appLinkInfo, true); + updateServiceConnectionLocked(componentName, resolvedUserId); + } + } + } catch (RemoteException e) { + Slogf.e(TAG, "error in registerAppLinkInfo", e); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void unregisterAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) { final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), - Binder.getCallingUid(), userId, "notifyAppLinkInfo"); + Binder.getCallingUid(), userId, "unregisterAppLinkInfo"); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(resolvedUserId); - TvIAppState iAppState = userState.mIAppMap.get(tiasId); + TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId); if (iAppState == null) { - Slogf.e(TAG, "failed to notifyAppLinkInfo - unknown TIAS id " + Slogf.e(TAG, "failed to unregisterAppLinkInfo - unknown TIAS id " + tiasId); return; } @@ -691,18 +740,18 @@ public class TvIAppManagerService extends SystemService { if (serviceState == null) { serviceState = new ServiceState( componentName, tiasId, resolvedUserId); - serviceState.addPendingAppLink(appLinkInfo); + serviceState.addPendingAppLink(appLinkInfo, false); userState.mServiceStateMap.put(componentName, serviceState); updateServiceConnectionLocked(componentName, resolvedUserId); } else if (serviceState.mService != null) { - serviceState.mService.notifyAppLinkInfo(appLinkInfo); + serviceState.mService.unregisterAppLinkInfo(appLinkInfo); } else { - serviceState.addPendingAppLink(appLinkInfo); + serviceState.addPendingAppLink(appLinkInfo, false); updateServiceConnectionLocked(componentName, resolvedUserId); } } } catch (RemoteException e) { - Slogf.e(TAG, "error in notifyAppLinkInfo", e); + Slogf.e(TAG, "error in unregisterAppLinkInfo", e); } finally { Binder.restoreCallingIdentity(identity); } @@ -716,7 +765,7 @@ public class TvIAppManagerService extends SystemService { try { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(resolvedUserId); - TvIAppState iAppState = userState.mIAppMap.get(tiasId); + TvInteractiveAppState iAppState = userState.mIAppMap.get(tiasId); if (iAppState == null) { Slogf.e(TAG, "failed to sendAppLinkCommand - unknown TIAS id " + tiasId); @@ -743,7 +792,8 @@ public class TvIAppManagerService extends SystemService { } @Override - public void createSession(final ITvIAppClient client, final String iAppServiceId, int type, + public void createSession( + final ITvInteractiveAppClient client, final String iAppServiceId, int type, int seq, int userId) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -760,7 +810,7 @@ public class TvIAppManagerService extends SystemService { return; } UserState userState = getOrCreateUserStateLocked(resolvedUserId); - TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId); + TvInteractiveAppState iAppState = userState.mIAppMap.get(iAppServiceId); if (iAppState == null) { Slogf.w(TAG, "Failed to find state for iAppServiceId=" + iAppServiceId); sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq); @@ -906,13 +956,123 @@ public class TvIAppManagerService extends SystemService { } @Override - public void startIApp(IBinder sessionToken, int userId) { + public void notifyVideoAvailable(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyVideoAvailable"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyVideoAvailable(); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyVideoAvailable", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void notifyVideoUnavailable(IBinder sessionToken, int reason, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyVideoUnavailable"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyVideoUnavailable(reason); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyVideoUnavailable", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void notifyContentAllowed(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyContentAllowed"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyContentAllowed(); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyContentAllowed", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void notifyContentBlocked(IBinder sessionToken, String rating, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyContentBlocked"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyContentBlocked(rating); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyContentBlocked", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void notifySignalStrength(IBinder sessionToken, int strength, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifySignalStrength"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifySignalStrength(strength); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifySignalStrength", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void startInteractiveApp(IBinder sessionToken, int userId) { if (DEBUG) { Slogf.d(TAG, "BinderService#start(userId=%d)", userId); } final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "startIApp"); + userId, "startInteractiveApp"); SessionState sessionState = null; final long identity = Binder.clearCallingIdentity(); try { @@ -920,7 +1080,7 @@ public class TvIAppManagerService extends SystemService { try { sessionState = getSessionStateLocked(sessionToken, callingUid, resolvedUserId); - getSessionLocked(sessionState).startIApp(); + getSessionLocked(sessionState).startInteractiveApp(); } catch (RemoteException | SessionNotFoundException e) { Slogf.e(TAG, "error in start", e); } @@ -931,13 +1091,13 @@ public class TvIAppManagerService extends SystemService { } @Override - public void stopIApp(IBinder sessionToken, int userId) { + public void stopInteractiveApp(IBinder sessionToken, int userId) { if (DEBUG) { Slogf.d(TAG, "BinderService#stop(userId=%d)", userId); } final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "stopIApp"); + userId, "stopInteractiveApp"); SessionState sessionState = null; final long identity = Binder.clearCallingIdentity(); try { @@ -945,7 +1105,7 @@ public class TvIAppManagerService extends SystemService { try { sessionState = getSessionStateLocked(sessionToken, callingUid, resolvedUserId); - getSessionLocked(sessionState).stopIApp(); + getSessionLocked(sessionState).stopInteractiveApp(); } catch (RemoteException | SessionNotFoundException e) { Slogf.e(TAG, "error in stop", e); } @@ -956,6 +1116,31 @@ public class TvIAppManagerService extends SystemService { } @Override + public void resetInteractiveApp(IBinder sessionToken, int userId) { + if (DEBUG) { + Slogf.d(TAG, "BinderService#reset(userId=%d)", userId); + } + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "resetInteractiveApp"); + SessionState sessionState = null; + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).resetInteractiveApp(); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in reset", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void createBiInteractiveApp( IBinder sessionToken, Uri biIAppUri, Bundle params, int userId) { if (DEBUG) { @@ -1008,6 +1193,31 @@ public class TvIAppManagerService extends SystemService { } @Override + public void setTeletextAppEnabled(IBinder sessionToken, boolean enable, int userId) { + if (DEBUG) { + Slogf.d(TAG, "setTeletextAppEnabled(enable=%d)", enable); + } + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "setTeletextAppEnabled"); + SessionState sessionState = null; + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).setTeletextAppEnabled(enable); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in setTeletextAppEnabled", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void sendCurrentChannelUri(IBinder sessionToken, Uri channelUri, int userId) { if (DEBUG) { Slogf.d(TAG, "sendCurrentChannelUri(channelUri=%s)", channelUri.toString()); @@ -1108,6 +1318,31 @@ public class TvIAppManagerService extends SystemService { } @Override + public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) { + if (DEBUG) { + Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId); + } + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "sendCurrentTvInputId"); + SessionState sessionState = null; + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).sendCurrentTvInputId(inputId); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in sendCurrentTvInputId", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void setSurface(IBinder sessionToken, Surface surface, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, @@ -1202,7 +1437,7 @@ public class TvIAppManagerService extends SystemService { } @Override - public void registerCallback(final ITvIAppManagerCallback callback, int userId) { + public void registerCallback(final ITvInteractiveAppManagerCallback callback, int userId) { int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, @@ -1221,7 +1456,7 @@ public class TvIAppManagerService extends SystemService { } @Override - public void unregisterCallback(ITvIAppManagerCallback callback, int userId) { + public void unregisterCallback(ITvInteractiveAppManagerCallback callback, int userId) { final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), Binder.getCallingUid(), userId, "unregisterCallback"); final long identity = Binder.clearCallingIdentity(); @@ -1247,7 +1482,7 @@ public class TvIAppManagerService extends SystemService { try { getSessionLocked(sessionToken, callingUid, resolvedUserId) .createMediaView(windowToken, frame); - } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) { + } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in createMediaView", e); } } @@ -1267,7 +1502,7 @@ public class TvIAppManagerService extends SystemService { try { getSessionLocked(sessionToken, callingUid, resolvedUserId) .relayoutMediaView(frame); - } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) { + } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in relayoutMediaView", e); } } @@ -1287,7 +1522,7 @@ public class TvIAppManagerService extends SystemService { try { getSessionLocked(sessionToken, callingUid, resolvedUserId) .removeMediaView(); - } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) { + } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in removeMediaView", e); } } @@ -1298,8 +1533,9 @@ public class TvIAppManagerService extends SystemService { } @GuardedBy("mLock") - private void sendSessionTokenToClientLocked(ITvIAppClient client, String iAppServiceId, - IBinder sessionToken, InputChannel channel, int seq) { + private void sendSessionTokenToClientLocked( + ITvInteractiveAppClient client, String iAppServiceId, IBinder sessionToken, + InputChannel channel, int seq) { try { client.onSessionCreated(iAppServiceId, sessionToken, channel, seq); } catch (RemoteException e) { @@ -1308,8 +1544,8 @@ public class TvIAppManagerService extends SystemService { } @GuardedBy("mLock") - private boolean createSessionInternalLocked(ITvIAppService service, IBinder sessionToken, - int userId) { + private boolean createSessionInternalLocked( + ITvInteractiveAppService service, IBinder sessionToken, int userId) { UserState userState = getOrCreateUserStateLocked(userId); SessionState sessionState = userState.mSessionStateMap.get(sessionToken); if (DEBUG) { @@ -1319,7 +1555,7 @@ public class TvIAppManagerService extends SystemService { InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString()); // Set up a callback to send the session token. - ITvIAppSessionCallback callback = new SessionCallback(sessionState, channels); + ITvInteractiveAppSessionCallback callback = new SessionCallback(sessionState, channels); boolean created = true; // Create a session. When failed, send a null token immediately. @@ -1426,7 +1662,8 @@ public class TvIAppManagerService extends SystemService { } boolean shouldBind = (!serviceState.mSessionTokens.isEmpty()) - || (serviceState.mPendingPrepare) || (!serviceState.mPendingAppLinkInfo.isEmpty()); + || (serviceState.mPendingPrepare) + || (!serviceState.mPendingAppLinkInfo.isEmpty()); if (serviceState.mService == null && shouldBind) { // This means that the service is not yet connected but its state indicates that we @@ -1440,7 +1677,8 @@ public class TvIAppManagerService extends SystemService { Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")"); } - Intent i = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component); + Intent i = + new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component); serviceState.mBound = mContext.bindServiceAsUser( i, serviceState.mConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, @@ -1458,20 +1696,20 @@ public class TvIAppManagerService extends SystemService { private static final class UserState { private final int mUserId; - // A mapping from the TV IApp ID to its TvIAppState. - private Map<String, TvIAppState> mIAppMap = new HashMap<>(); + // A mapping from the TV Interactive App ID to its TvInteractiveAppState. + private Map<String, TvInteractiveAppState> mIAppMap = new HashMap<>(); // A mapping from the token of a client to its state. private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>(); - // A mapping from the name of a TV IApp service to its state. + // A mapping from the name of a TV Interactive App service to its state. private final Map<ComponentName, ServiceState> mServiceStateMap = new HashMap<>(); - // A mapping from the token of a TV IApp session to its state. + // A mapping from the token of a TV Interactive App session to its state. private final Map<IBinder, SessionState> mSessionStateMap = new HashMap<>(); - // A set of all TV IApp service packages. + // A set of all TV Interactive App service packages. private final Set<String> mPackageSet = new HashSet<>(); // A list of callbacks. - private final RemoteCallbackList<ITvIAppManagerCallback> mCallbacks = + private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks = new RemoteCallbackList<>(); private UserState(int userId) { @@ -1479,20 +1717,20 @@ public class TvIAppManagerService extends SystemService { } } - private static final class TvIAppState { + private static final class TvInteractiveAppState { private String mIAppServiceId; private ComponentName mComponentName; - private TvIAppInfo mInfo; + private TvInteractiveAppInfo mInfo; private int mUid; private int mIAppNumber; } private final class SessionState implements IBinder.DeathRecipient { private final IBinder mSessionToken; - private ITvIAppSession mSession; + private ITvInteractiveAppSession mSession; private final String mIAppServiceId; private final int mType; - private final ITvIAppClient mClient; + private final ITvInteractiveAppClient mClient; private final int mSeq; private final ComponentName mComponent; @@ -1507,8 +1745,8 @@ public class TvIAppManagerService extends SystemService { private final int mUserId; private SessionState(IBinder sessionToken, String iAppServiceId, int type, - ComponentName componentName, ITvIAppClient client, int seq, int callingUid, - int callingPid, int userId) { + ComponentName componentName, ITvInteractiveAppClient client, int seq, + int callingUid, int callingPid, int userId) { mSessionToken = sessionToken; mIAppServiceId = iAppServiceId; mComponent = componentName; @@ -1572,12 +1810,12 @@ public class TvIAppManagerService extends SystemService { private final ServiceConnection mConnection; private final ComponentName mComponent; private final String mIAppServiceId; - private final List<Bundle> mPendingAppLinkInfo = new ArrayList<>(); + private final List<Pair<Bundle, Boolean>> mPendingAppLinkInfo = new ArrayList<>(); private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>(); private boolean mPendingPrepare = false; private Integer mPendingPrepareType = null; - private ITvIAppService mService; + private ITvInteractiveAppService mService; private ServiceCallback mCallback; private boolean mBound; private boolean mReconnecting; @@ -1591,12 +1829,12 @@ public class TvIAppManagerService extends SystemService { mComponent = component; mPendingPrepare = pendingPrepare; mPendingPrepareType = prepareType; - mConnection = new IAppServiceConnection(component, userId); + mConnection = new InteractiveAppServiceConnection(component, userId); mIAppServiceId = tias; } - private void addPendingAppLink(Bundle info) { - mPendingAppLinkInfo.add(info); + private void addPendingAppLink(Bundle info, boolean register) { + mPendingAppLinkInfo.add(Pair.create(info, register)); } private void addPendingAppLinkCommand(Bundle command) { @@ -1604,11 +1842,11 @@ public class TvIAppManagerService extends SystemService { } } - private final class IAppServiceConnection implements ServiceConnection { + private final class InteractiveAppServiceConnection implements ServiceConnection { private final ComponentName mComponent; private final int mUserId; - private IAppServiceConnection(ComponentName component, int userId) { + private InteractiveAppServiceConnection(ComponentName component, int userId) { mComponent = component; mUserId = userId; } @@ -1626,7 +1864,7 @@ public class TvIAppManagerService extends SystemService { return; } ServiceState serviceState = userState.mServiceStateMap.get(mComponent); - serviceState.mService = ITvIAppService.Stub.asInterface(service); + serviceState.mService = ITvInteractiveAppService.Stub.asInterface(service); if (serviceState.mPendingPrepare) { final long identity = Binder.clearCallingIdentity(); @@ -1642,15 +1880,20 @@ public class TvIAppManagerService extends SystemService { } if (!serviceState.mPendingAppLinkInfo.isEmpty()) { - for (Iterator<Bundle> it = serviceState.mPendingAppLinkInfo.iterator(); + for (Iterator<Pair<Bundle, Boolean>> it = + serviceState.mPendingAppLinkInfo.iterator(); it.hasNext(); ) { - Bundle appLinkInfo = it.next(); + Pair<Bundle, Boolean> appLinkInfoPair = it.next(); final long identity = Binder.clearCallingIdentity(); try { - serviceState.mService.notifyAppLinkInfo(appLinkInfo); + if (appLinkInfoPair.second) { + serviceState.mService.registerAppLinkInfo(appLinkInfoPair.first); + } else { + serviceState.mService.unregisterAppLinkInfo(appLinkInfoPair.first); + } it.remove(); } catch (RemoteException e) { - Slogf.e(TAG, "error in notifyAppLinkInfo(" + appLinkInfo + Slogf.e(TAG, "error in notifyAppLinkInfo(" + appLinkInfoPair + ") when onServiceConnected", e); } finally { Binder.restoreCallingIdentity(identity); @@ -1715,7 +1958,7 @@ public class TvIAppManagerService extends SystemService { } } - private final class ServiceCallback extends ITvIAppServiceCallback.Stub { + private final class ServiceCallback extends ITvInteractiveAppServiceCallback.Stub { private final ComponentName mComponent; private final int mUserId; @@ -1740,7 +1983,7 @@ public class TvIAppManagerService extends SystemService { } } - private final class SessionCallback extends ITvIAppSessionCallback.Stub { + private final class SessionCallback extends ITvInteractiveAppSessionCallback.Stub { private final SessionState mSessionState; private final InputChannel[] mInputChannels; @@ -1750,7 +1993,7 @@ public class TvIAppManagerService extends SystemService { } @Override - public void onSessionCreated(ITvIAppSession session) { + public void onSessionCreated(ITvInteractiveAppSession session) { if (DEBUG) { Slogf.d(TAG, "onSessionCreated(iAppServiceId=" + mSessionState.mIAppServiceId + ")"); @@ -1828,7 +2071,8 @@ public class TvIAppManagerService extends SystemService { } @Override - public void onCommandRequest(@TvIAppService.IAppServiceCommandType String cmdType, + public void onCommandRequest( + @TvIAppService.InteractiveAppServiceCommandType String cmdType, Bundle parameters) { synchronized (mLock) { if (DEBUG) { @@ -1932,6 +2176,23 @@ public class TvIAppManagerService extends SystemService { } @Override + public void onRequestCurrentTvInputId() { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onRequestCurrentTvInputId"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onRequestCurrentTvInputId(mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onRequestCurrentTvInputId", e); + } + } + } + + @Override public void onAdRequest(AdRequest request) { synchronized (mLock) { if (DEBUG) { @@ -1984,8 +2245,25 @@ public class TvIAppManagerService extends SystemService { } } + @Override + public void onTeletextAppStateChanged(int state) { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onTeletextAppStateChanged (state=" + state + ")"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onTeletextAppStateChanged(state, mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onTeletextAppStateChanged", e); + } + } + } + @GuardedBy("mLock") - private boolean addSessionTokenToClientStateLocked(ITvIAppSession session) { + private boolean addSessionTokenToClientStateLocked(ITvInteractiveAppSession session) { try { session.asBinder().linkToDeath(mSessionState, 0); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 63f4c68b11f6..af705d597af2 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -289,6 +289,23 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override + public boolean transferOwner(int resourceType, int currentOwnerId, int newOwnerId) { + enforceTunerAccessPermission("transferOwner"); + enforceTrmAccessPermission("transferOwner"); + synchronized (mLock) { + if (!checkClientExists(currentOwnerId)) { + Slog.e(TAG, "currentOwnerId:" + currentOwnerId + " does not exit"); + return false; + } + if (!checkClientExists(newOwnerId)) { + Slog.e(TAG, "newOwnerId:" + newOwnerId + " does not exit"); + return false; + } + return transferOwnerInternal(resourceType, currentOwnerId, newOwnerId); + } + } + + @Override public boolean requestDemux(@NonNull TunerDemuxRequest request, @NonNull int[] demuxHandle) throws RemoteException { enforceTunerAccessPermission("requestDemux"); @@ -388,7 +405,11 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (fe == null) { throw new RemoteException("Releasing frontend does not exist."); } - if (fe.getOwnerClientId() != clientId) { + int ownerClientId = fe.getOwnerClientId(); + ClientProfile ownerProfile = getClientProfile(ownerClientId); + if (ownerClientId != clientId + && (ownerProfile != null + && !ownerProfile.getShareFeClientIds().contains(clientId))) { throw new RemoteException( "Client is not the current owner of the releasing fe."); } @@ -619,6 +640,21 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } + @Override + public int getClientPriority(int useCase, int pid) throws RemoteException { + enforceTrmAccessPermission("getClientPriority"); + synchronized (mLock) { + return TunerResourceManagerService.this.getClientPriority( + useCase, checkIsForeground(pid)); + } + } + @Override + public int getConfigPriority(int useCase, boolean isForeground) throws RemoteException { + enforceTrmAccessPermission("getConfigPriority"); + synchronized (mLock) { + return TunerResourceManagerService.this.getClientPriority(useCase, isForeground); + } + } } /** @@ -976,6 +1012,83 @@ public class TunerResourceManagerService extends SystemService implements IBinde getClientProfile(targetClientId).shareFrontend(selfClientId); } + private boolean transferFeOwner(int currentOwnerId, int newOwnerId) { + ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId); + ClientProfile newOwnerProfile = getClientProfile(newOwnerId); + // change the owner of all the inUse frontend + newOwnerProfile.shareFrontend(currentOwnerId); + currentOwnerProfile.stopSharingFrontend(newOwnerId); + for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) { + getFrontendResource(inUseHandle).setOwner(newOwnerId); + } + // double check there is no other resources tied to the previous owner + for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) { + int ownerId = getFrontendResource(inUseHandle).getOwnerClientId(); + if (ownerId != newOwnerId) { + Slog.e(TAG, "something is wrong in transferFeOwner:" + inUseHandle + + ", " + ownerId + ", " + newOwnerId); + return false; + } + } + return true; + } + + private boolean transferFeCiCamOwner(int currentOwnerId, int newOwnerId) { + ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId); + ClientProfile newOwnerProfile = getClientProfile(newOwnerId); + + // link ciCamId to the new profile + int ciCamId = currentOwnerProfile.getInUseCiCamId(); + newOwnerProfile.useCiCam(ciCamId); + + // set the new owner Id + CiCamResource ciCam = getCiCamResource(ciCamId); + ciCam.setOwner(newOwnerId); + + // unlink cicam resource from the original owner profile + currentOwnerProfile.releaseCiCam(); + return true; + } + + private boolean transferLnbOwner(int currentOwnerId, int newOwnerId) { + ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId); + ClientProfile newOwnerProfile = getClientProfile(newOwnerId); + + Set<Integer> inUseLnbHandles = new HashSet<>(); + for (Integer lnbHandle : currentOwnerProfile.getInUseLnbHandles()) { + // link lnb handle to the new profile + newOwnerProfile.useLnb(lnbHandle); + + // set new owner Id + LnbResource lnb = getLnbResource(lnbHandle); + lnb.setOwner(newOwnerId); + + inUseLnbHandles.add(lnbHandle); + } + + // unlink lnb handles from the original owner + for (Integer lnbHandle : inUseLnbHandles) { + currentOwnerProfile.releaseLnb(lnbHandle); + } + + return true; + } + + @VisibleForTesting + protected boolean transferOwnerInternal(int resourceType, int currentOwnerId, int newOwnerId) { + switch (resourceType) { + case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: + return transferFeOwner(currentOwnerId, newOwnerId); + case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: + return transferFeCiCamOwner(currentOwnerId, newOwnerId); + case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: + return transferLnbOwner(currentOwnerId, newOwnerId); + default: + Slog.e(TAG, "transferOwnerInternal. unsupported resourceType: " + resourceType); + return false; + } + } + @VisibleForTesting protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) { if (DEBUG) { diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index e0cc8e182079..f29c40f74353 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -39,10 +39,13 @@ import android.net.vcn.VcnConfig; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnManager.VcnErrorCode; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; import android.provider.Settings; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -57,6 +60,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -148,6 +152,10 @@ public class Vcn extends Handler { @NonNull private final VcnContentResolver mContentResolver; @NonNull private final ContentObserver mMobileDataSettingsObserver; + @NonNull + private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners = + new ArrayMap<>(); + /** * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs. * @@ -221,6 +229,9 @@ public class Vcn extends Handler { // Update mIsMobileDataEnabled before starting handling of NetworkRequests. mIsMobileDataEnabled = getMobileDataStatus(); + // Register mobile data state listeners. + updateMobileDataStateListeners(); + // Register to receive cached and future NetworkRequests mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); } @@ -348,6 +359,12 @@ public class Vcn extends Handler { gatewayConnection.teardownAsynchronously(); } + // Unregister MobileDataStateListeners + for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) { + getTelephonyManager().unregisterTelephonyCallback(listener); + } + mMobileDataStateListeners.clear(); + mCurrentStatus = VCN_STATUS_CODE_INACTIVE; } @@ -454,11 +471,40 @@ public class Vcn extends Handler { gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); } + updateMobileDataStateListeners(); + // Update the mobile data state after updating the subscription snapshot as a change in // subIds for a subGroup may affect the mobile data state. handleMobileDataToggled(); } + private void updateMobileDataStateListeners() { + final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup); + final HandlerExecutor executor = new HandlerExecutor(this); + + // Register new callbacks + for (int subId : subIdsInGroup) { + if (!mMobileDataStateListeners.containsKey(subId)) { + final VcnUserMobileDataStateListener listener = + new VcnUserMobileDataStateListener(); + + getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener); + mMobileDataStateListeners.put(subId, listener); + } + } + + // Unregister old callbacks + Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator = + mMobileDataStateListeners.entrySet().iterator(); + while (iterator.hasNext()) { + final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next(); + if (!subIdsInGroup.contains(entry.getKey())) { + getTelephonyManager().unregisterTelephonyCallback(entry.getValue()); + iterator.remove(); + } + } + } + private void handleMobileDataToggled() { final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled; mIsMobileDataEnabled = getMobileDataStatus(); @@ -493,11 +539,8 @@ public class Vcn extends Handler { } private boolean getMobileDataStatus() { - final TelephonyManager genericTelMan = - mVcnContext.getContext().getSystemService(TelephonyManager.class); - for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { - if (genericTelMan.createForSubscriptionId(subId).isDataEnabled()) { + if (getTelephonyManagerForSubid(subId).isDataEnabled()) { return true; } } @@ -517,6 +560,14 @@ public class Vcn extends Handler { return request.canBeSatisfiedBy(builder.build()); } + private TelephonyManager getTelephonyManager() { + return mVcnContext.getContext().getSystemService(TelephonyManager.class); + } + + private TelephonyManager getTelephonyManagerForSubid(int subid) { + return getTelephonyManager().createForSubscriptionId(subid); + } + private String getLogPrefix() { return "[" + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) @@ -670,6 +721,16 @@ public class Vcn extends Handler { } } + @VisibleForTesting(visibility = Visibility.PRIVATE) + class VcnUserMobileDataStateListener extends TelephonyCallback + implements TelephonyCallback.UserMobileDataStateListener { + + @Override + public void onUserMobileDataStateChanged(boolean enabled) { + sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED)); + } + } + /** External dependencies used by Vcn, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java index bd8d13b87125..c96c1ee01a6d 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -20,8 +20,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; -import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static com.android.server.VcnManagementService.LOCAL_LOG; @@ -44,7 +44,8 @@ import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.VcnContext; -import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; import java.util.Set; /** @hide */ @@ -76,7 +77,7 @@ class NetworkPriorityClassifier { public static int calculatePriorityClass( VcnContext vcnContext, UnderlyingNetworkRecord networkRecord, - LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, + List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -94,7 +95,7 @@ class NetworkPriorityClassifier { } int priorityIndex = 0; - for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkPriorities) { + for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) { if (checkMatchesPriorityRule( vcnContext, nwPriority, @@ -119,10 +120,32 @@ class NetworkPriorityClassifier { TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig) { - // TODO: Check Network Quality reported by metric monitors/probers. - final NetworkCapabilities caps = networkRecord.networkCapabilities; - if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) { + final boolean isSelectedUnderlyingNetwork = + currentlySelected != null + && Objects.equals(currentlySelected.network, networkRecord.network); + + final int meteredMatch = networkPriority.getMetered(); + final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED); + if (meteredMatch == MATCH_REQUIRED && !isMetered + || meteredMatch == MATCH_FORBIDDEN && isMetered) { + return false; + } + + // Fails bandwidth requirements if either (a) less than exit threshold, or (b), not + // selected, but less than entry threshold + if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps() + || (caps.getLinkUpstreamBandwidthKbps() + < networkPriority.getMinEntryUpstreamBandwidthKbps() + && !isSelectedUnderlyingNetwork)) { + return false; + } + + if (caps.getLinkDownstreamBandwidthKbps() + < networkPriority.getMinExitDownstreamBandwidthKbps() + || (caps.getLinkDownstreamBandwidthKbps() + < networkPriority.getMinEntryDownstreamBandwidthKbps() + && !isSelectedUnderlyingNetwork)) { return false; } @@ -166,19 +189,19 @@ class NetworkPriorityClassifier { } // TODO: Move the Network Quality check to the network metric monitor framework. - if (networkPriority.getNetworkQuality() - > getWifiQuality(networkRecord, currentlySelected, carrierConfig)) { + if (!isWifiRssiAcceptable(networkRecord, currentlySelected, carrierConfig)) { return false; } - if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) { + if (!networkPriority.getSsids().isEmpty() + && !networkPriority.getSsids().contains(caps.getSsid())) { return false; } return true; } - private static int getWifiQuality( + private static boolean isWifiRssiAcceptable( UnderlyingNetworkRecord networkRecord, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig) { @@ -189,14 +212,14 @@ class NetworkPriorityClassifier { if (isSelectedNetwork && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) { - return NETWORK_QUALITY_OK; + return true; } if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) { - return NETWORK_QUALITY_OK; + return true; } - return NETWORK_QUALITY_ANY; + return false; } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -226,26 +249,31 @@ class NetworkPriorityClassifier { .getSystemService(TelephonyManager.class) .createForSubscriptionId(subId); - if (!networkPriority.getAllowedOperatorPlmnIds().isEmpty()) { + if (!networkPriority.getOperatorPlmnIds().isEmpty()) { final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator(); - if (!networkPriority.getAllowedOperatorPlmnIds().contains(plmnId)) { + if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) { return false; } } - if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) { + if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) { final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId(); - if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) { + if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) { return false; } } - if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) { + final int roamingMatch = networkPriority.getRoaming(); + final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING); + if (roamingMatch == MATCH_REQUIRED && !isRoaming + || roamingMatch == MATCH_FORBIDDEN && isRoaming) { return false; } - if (networkPriority.requireOpportunistic()) { - if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) { + final int opportunisticMatch = networkPriority.getOpportunistic(); + final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds()); + if (opportunisticMatch == MATCH_REQUIRED) { + if (!isOpportunistic) { return false; } @@ -265,6 +293,8 @@ class NetworkPriorityClassifier { .contains(SubscriptionManager.getActiveDataSubscriptionId())) { return false; } + } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) { + return false; } return true; diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java index df2f0d58565e..c0488b18cb65 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java @@ -32,7 +32,7 @@ import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscription import com.android.server.vcn.VcnContext; import java.util.Comparator; -import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; /** @@ -77,7 +77,7 @@ public class UnderlyingNetworkRecord { static Comparator<UnderlyingNetworkRecord> getComparator( VcnContext vcnContext, - LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, + List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -87,7 +87,7 @@ public class UnderlyingNetworkRecord { NetworkPriorityClassifier.calculatePriorityClass( vcnContext, left, - underlyingNetworkPriorities, + underlyingNetworkTemplates, subscriptionGroup, snapshot, currentlySelected, @@ -96,7 +96,7 @@ public class UnderlyingNetworkRecord { NetworkPriorityClassifier.calculatePriorityClass( vcnContext, right, - underlyingNetworkPriorities, + underlyingNetworkTemplates, subscriptionGroup, snapshot, currentlySelected, @@ -133,7 +133,7 @@ public class UnderlyingNetworkRecord { void dump( VcnContext vcnContext, IndentingPrintWriter pw, - LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, + List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -145,7 +145,7 @@ public class UnderlyingNetworkRecord { NetworkPriorityClassifier.calculatePriorityClass( vcnContext, this, - underlyingNetworkPriorities, + underlyingNetworkTemplates, subscriptionGroup, snapshot, currentlySelected, diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index f481772d7a7f..a528f063e875 100644 --- a/services/core/java/com/android/server/vibrator/VibrationScaler.java +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -74,6 +74,12 @@ final class VibrationScaler { public int getExternalVibrationScale(int usageHint) { int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + + if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + // Bypassing user settings, or it has changed between checking and scaling. Use default. + return SCALE_NONE; + } + int scaleLevel = currentIntensity - defaultIntensity; if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) { @@ -97,6 +103,12 @@ final class VibrationScaler { public <T extends VibrationEffect> T scale(VibrationEffect effect, int usageHint) { int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + + if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + // Bypassing user settings, or it has changed between checking and scaling. Use default. + currentIntensity = defaultIntensity; + } + int newEffectStrength = intensityToEffectStrength(currentIntensity); effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude); ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity); @@ -121,6 +133,12 @@ final class VibrationScaler { */ public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) { int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + + if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + // Bypassing user settings, or it has changed between checking and scaling. Use default. + currentIntensity = mSettingsController.getDefaultIntensity(usageHint); + } + int newEffectStrength = intensityToEffectStrength(currentIntensity); return prebaked.applyEffectStrength(newEffectStrength); } diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index df6ffa2bd009..c54d490b5162 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -315,7 +315,9 @@ final class VibrationSettings { } int intensity = getCurrentIntensity(usage); - if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { + if ((intensity == Vibrator.VIBRATION_INTENSITY_OFF) + && !attrs.isFlagSet( + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) { return Vibration.Status.IGNORED_FOR_SETTINGS; } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 27566b301a6e..a95b6c955d63 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -80,6 +80,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); + private static final int ATTRIBUTES_ALL_BYPASS_FLAGS = + VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY + | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; /** Lifecycle responsible for initializing this class at the right system server phases. */ public static class Lifecycle extends SystemService { @@ -975,12 +978,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { usage = VibrationAttributes.USAGE_TOUCH; } int flags = attrs.getFlags(); - if (attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { + if ((flags & ATTRIBUTES_ALL_BYPASS_FLAGS) != 0) { if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE) || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { - // Remove bypass policy flag from attributes if the app does not have permissions. - flags &= ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; + // Remove bypass flags from attributes if the app does not have permissions. + flags &= ~ATTRIBUTES_ALL_BYPASS_FLAGS; } } if ((usage == attrs.getUsage()) && (flags == attrs.getFlags())) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 2ec42b41fc9f..ee2cc7bd7486 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -81,7 +81,9 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.SELinux; +import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -92,6 +94,7 @@ import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; import android.system.ErrnoException; import android.system.Os; +import android.util.ArrayMap; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -420,7 +423,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + which); } - needsExtraction = wallpaper.primaryColors == null; + needsExtraction = wallpaper.primaryColors == null || wallpaper.mIsColorExtractedFromDim; } if (needsExtraction) { @@ -491,12 +494,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; + float dimAmount; + + synchronized (mLock) { + wallpaper.mIsColorExtractedFromDim = false; + } if (wallpaper.equals(mFallbackWallpaper)) { synchronized (mLock) { if (mFallbackWallpaper.primaryColors != null) return; } - final WallpaperColors colors = extractDefaultImageWallpaperColors(); + final WallpaperColors colors = extractDefaultImageWallpaperColors(wallpaper); synchronized (mLock) { mFallbackWallpaper.primaryColors = colors; } @@ -513,18 +521,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub defaultImageWallpaper = true; } wallpaperId = wallpaper.wallpaperId; + dimAmount = wallpaper.mWallpaperDimAmount; } WallpaperColors colors = null; if (cropFile != null) { Bitmap bitmap = BitmapFactory.decodeFile(cropFile); if (bitmap != null) { - colors = WallpaperColors.fromBitmap(bitmap); + colors = WallpaperColors.fromBitmap(bitmap, dimAmount); bitmap.recycle(); } } else if (defaultImageWallpaper) { // There is no crop and source file because this is default image wallpaper. - colors = extractDefaultImageWallpaperColors(); + colors = extractDefaultImageWallpaperColors(wallpaper); } if (colors == null) { @@ -544,11 +553,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private WallpaperColors extractDefaultImageWallpaperColors() { + private WallpaperColors extractDefaultImageWallpaperColors(WallpaperData wallpaper) { if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors"); + float dimAmount; synchronized (mLock) { if (mCacheDefaultImageWallpaperColors != null) return mCacheDefaultImageWallpaperColors; + dimAmount = wallpaper.mWallpaperDimAmount; } WallpaperColors colors = null; @@ -561,7 +572,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final BitmapFactory.Options options = new BitmapFactory.Options(); final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); if (bitmap != null) { - colors = WallpaperColors.fromBitmap(bitmap); + colors = WallpaperColors.fromBitmap(bitmap, dimAmount); bitmap.recycle(); } } catch (OutOfMemoryError e) { @@ -948,6 +959,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperObserver wallpaperObserver; /** + * The dim amount to be applied to the wallpaper. + */ + float mWallpaperDimAmount = 0.0f; + + /** + * A map to keep track of the dimming set by different applications. The key is the calling + * UID and the value is the dim amount. + */ + ArrayMap<Integer, Float> mUidToDimAmount = new ArrayMap<>(); + + /** + * Whether we need to extract the wallpaper colors again to calculate the dark hints + * after dimming is applied. + */ + boolean mIsColorExtractedFromDim; + + /** * List of callbacks registered they should each be notified when the wallpaper is changed. */ private RemoteCallbackList<IWallpaperManagerCallback> callbacks @@ -1487,6 +1515,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, "Failed to register local colors areas", e); } } + + if (mWallpaper.mWallpaperDimAmount != 0f) { + try { + connector.mEngine.applyDimming(mWallpaper.mWallpaperDimAmount); + notifyWallpaperColorsChanged(mWallpaper, FLAG_SYSTEM); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to dim wallpaper", e); + } + } } } @@ -2536,6 +2573,98 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (purgeAreas.size() > 0) engine.removeLocalColorsAreas(purgeAreas); } + /** + * Returns true if the lock screen wallpaper exists (different wallpaper from the system) + */ + @Override + public boolean lockScreenWallpaperExists() { + synchronized (mLock) { + return mLockWallpaperMap.get(mCurrentUserId) != null; + } + } + + /** + * Sets wallpaper dim amount for the calling UID. This only applies to FLAG_SYSTEM wallpaper as + * the lock screen does not have a wallpaper component, so we use mWallpaperMap. + * + * @param dimAmount Dim amount which would be blended with the system default dimming. + */ + @Override + public void setWallpaperDimAmount(float dimAmount) throws RemoteException { + checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT); + int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); + WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId); + + if (dimAmount == 0.0f) { + wallpaper.mUidToDimAmount.remove(uid); + } else { + wallpaper.mUidToDimAmount.put(uid, dimAmount); + } + + float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount); + wallpaper.mWallpaperDimAmount = maxDimAmount; + // Also set the dim amount to the lock screen wallpaper if the lock and home screen + // do not share the same wallpaper + if (lockWallpaper != null) { + lockWallpaper.mWallpaperDimAmount = maxDimAmount; + } + + if (wallpaper.connection != null) { + wallpaper.connection.forEachDisplayConnector(connector -> { + if (connector.mEngine != null) { + try { + connector.mEngine.applyDimming(maxDimAmount); + } catch (RemoteException e) { + Slog.w(TAG, + "Can't apply dimming on wallpaper display connector", e); + } + } + }); + // Need to extract colors again to re-calculate dark hints after + // applying dimming. + wallpaper.mIsColorExtractedFromDim = true; + notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM); + if (lockWallpaper != null) { + lockWallpaper.mIsColorExtractedFromDim = true; + notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK); + } + saveSettingsLocked(wallpaper.userId); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public float getWallpaperDimAmount() { + checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT); + synchronized (mLock) { + WallpaperData data = mWallpaperMap.get(mCurrentUserId); + return data.mWallpaperDimAmount; + } + } + + /** + * Gets the highest dim amount among all the calling UIDs that set the wallpaper dim amount. + * Return 0f as default value to indicate no application has dimmed the wallpaper. + * + * @param uidToDimAmountMap Map of UIDs to dim amounts + */ + private float getHighestDimAmountFromMap(ArrayMap<Integer, Float> uidToDimAmountMap) { + float maxDimAmount = 0.0f; + for (Map.Entry<Integer, Float> entry : uidToDimAmountMap.entrySet()) { + if (entry.getValue() > maxDimAmount) { + maxDimAmount = entry.getValue(); + } + } + return maxDimAmount; + } + @Override public WallpaperColors getWallpaperColors(int which, int userId, int displayId) throws RemoteException { @@ -2562,7 +2691,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (wallpaperData == null) { return null; } - shouldExtract = wallpaperData.primaryColors == null; + shouldExtract = wallpaperData.primaryColors == null + || wallpaperData.mIsColorExtractedFromDim; } if (shouldExtract) { @@ -2664,6 +2794,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub lockWP.cropHint.set(sysWP.cropHint); lockWP.allowBackup = sysWP.allowBackup; lockWP.primaryColors = sysWP.primaryColors; + lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount; // Migrate the bitmap files outright; no need to copy try { @@ -3191,6 +3322,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom); } + out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount); + int dimAmountsCount = wallpaper.mUidToDimAmount.size(); + out.attributeInt(null, "dimAmountsCount", dimAmountsCount); + if (dimAmountsCount > 0) { + int index = 0; + for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) { + out.attributeInt(null, "dimUID" + index, entry.getKey()); + out.attributeFloat(null, "dimValue" + index, entry.getValue()); + index++; + } + } + if (wallpaper.primaryColors != null) { int colorsCount = wallpaper.primaryColors.getMainColors().size(); out.attributeInt(null, "colorsCount", colorsCount); @@ -3267,6 +3410,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return parser.getAttributeInt(null, name, defValue); } + private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) { + return parser.getAttributeFloat(null, name, defValue); + } + /** * Sometimes it is expected the wallpaper map may not have a user's data. E.g. This could * happen during user switch. The async user switch observer may not have received @@ -3471,6 +3618,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0); wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0); wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0); + wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f); + int dimAmountsCount = getAttributeInt(parser, "dimAmountsCount", 0); + if (dimAmountsCount > 0) { + ArrayMap<Integer, Float> allDimAmounts = new ArrayMap<>(dimAmountsCount); + for (int i = 0; i < dimAmountsCount; i++) { + int uid = getAttributeInt(parser, "dimUID" + i, 0); + float dimValue = getAttributeFloat(parser, "dimValue" + i, 0f); + allDimAmounts.put(uid, dimValue); + } + wallpaper.mUidToDimAmount = allDimAmounts; + } int colorsCount = getAttributeInt(parser, "colorsCount", 0); int allColorsCount = getAttributeInt(parser, "allColorsCount", 0); if (allColorsCount > 0) { @@ -3637,6 +3795,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return false; } + @Override // Binder call + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ShellCallback callback, + ResultReceiver resultReceiver) { + new WallpaperManagerShellCommand(WallpaperManagerService.this).exec(this, in, out, err, + args, callback, resultReceiver); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -3664,6 +3830,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); + pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); + pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim); + pw.println(" mUidToDimAmount:"); + for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) { + pw.print(" UID="); pw.print(entry.getKey()); + pw.print(" dimAmount="); pw.println(entry.getValue()); + } if (wallpaper.connection != null) { WallpaperConnection conn = wallpaper.connection; pw.print(" Wallpaper connection "); @@ -3695,6 +3868,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub pw.print(" mCropHint="); pw.println(wallpaper.cropHint); pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); + pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); } pw.println("Fallback wallpaper state:"); pw.print(" User "); pw.print(mFallbackWallpaper.userId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java new file mode 100644 index 000000000000..fc827b40f3a1 --- /dev/null +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java @@ -0,0 +1,95 @@ +/* + * 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.wallpaper; + +import android.os.RemoteException; +import android.os.ShellCommand; +import android.util.Log; + +import java.io.PrintWriter; + +/** + * Shell Command class to run adb commands on the wallpaper service + */ +public class WallpaperManagerShellCommand extends ShellCommand { + private static final String TAG = "WallpaperManagerShellCommand"; + + private final WallpaperManagerService mService; + + public WallpaperManagerShellCommand(WallpaperManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + onHelp(); + return 1; + } + switch(cmd) { + case "set-dim-amount": + return setWallpaperDimAmount(); + case "get-dim-amount": + return getWallpaperDimAmount(); + case "-h": + case "help": + onHelp(); + return 0; + default: + return handleDefaultCommands(cmd); + } + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Wallpaper manager commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println(" set-dim-amount DIMMING"); + pw.println(" Sets the current dimming value to DIMMING (a number between 0 and 1)."); + pw.println(); + pw.println(" get-dim-amount"); + pw.println(" Get the current wallpaper dim amount."); + } + + /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + */ + private int setWallpaperDimAmount() { + float dimAmount = Float.parseFloat(getNextArgRequired()); + try { + mService.setWallpaperDimAmount(dimAmount); + } catch (RemoteException e) { + Log.e(TAG, "Can't set wallpaper dim amount"); + } + getOutPrintWriter().println("Dimming the wallpaper to: " + dimAmount); + return 0; + } + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + */ + private int getWallpaperDimAmount() { + float dimAmount = mService.getWallpaperDimAmount(); + getOutPrintWriter().println("The current wallpaper dim amount is: " + dimAmount); + return 0; + } +} diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 8f703c5c7761..0396a1101c64 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -45,6 +45,7 @@ import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MA import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowTracing.WINSCOPE_EXT; +import static com.android.server.wm.utils.RegionUtils.forEachRect; import android.accessibilityservice.AccessibilityTrace; import android.animation.ObjectAnimator; @@ -100,7 +101,6 @@ import com.android.internal.util.TraceBuffer; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; -import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow; import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal; import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks; import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback; @@ -133,22 +133,19 @@ final class AccessibilityController { private static final Rect EMPTY_RECT = new Rect(); private static final float[] sTempFloats = new float[9]; - private final SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>(); - private final SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver = + private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>(); + private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver = new SparseArray<>(); private SparseArray<IBinder> mFocusedWindow = new SparseArray<>(); private int mFocusedDisplay = -1; private boolean mIsImeVisible = false; // Set to true if initializing window population complete. private boolean mAllObserversInitialized = true; - private final AccessibilityWindowsPopulator mAccessibilityWindowsPopulator; AccessibilityController(WindowManagerService service) { mService = service; mAccessibilityTracing = AccessibilityController.getAccessibilityControllerInternal(service); - - mAccessibilityWindowsPopulator = new AccessibilityWindowsPopulator(mService, this); } boolean setMagnificationCallbacks(int displayId, MagnificationCallbacks callbacks) { @@ -212,9 +209,7 @@ final class AccessibilityController { } mWindowsForAccessibilityObserver.remove(displayId); } - mAccessibilityWindowsPopulator.setWindowsNotification(true); - observer = new WindowsForAccessibilityObserver(mService, displayId, callback, - mAccessibilityWindowsPopulator); + observer = new WindowsForAccessibilityObserver(mService, displayId, callback); mWindowsForAccessibilityObserver.put(displayId, observer); mAllObserversInitialized &= observer.mInitialized; } else { @@ -229,10 +224,6 @@ final class AccessibilityController { } } mWindowsForAccessibilityObserver.remove(displayId); - - if (mWindowsForAccessibilityObserver.size() <= 0) { - mAccessibilityWindowsPopulator.setWindowsNotification(false); - } } } @@ -318,6 +309,11 @@ final class AccessibilityController { if (displayMagnifier != null) { displayMagnifier.onDisplaySizeChanged(displayContent); } + final WindowsForAccessibilityObserver windowsForA11yObserver = + mWindowsForAccessibilityObserver.get(displayId); + if (windowsForA11yObserver != null) { + windowsForA11yObserver.scheduleComputeChangedWindows(); + } } void onAppWindowTransition(int displayId, int transition) { @@ -345,6 +341,11 @@ final class AccessibilityController { if (displayMagnifier != null) { displayMagnifier.onWindowTransition(windowState, transition); } + final WindowsForAccessibilityObserver windowsForA11yObserver = + mWindowsForAccessibilityObserver.get(displayId); + if (windowsForA11yObserver != null) { + windowsForA11yObserver.scheduleComputeChangedWindows(); + } } void onWindowFocusChangedNot(int displayId) { @@ -454,19 +455,6 @@ final class AccessibilityController { return null; } - boolean getMagnificationSpecForDisplay(int displayId, MagnificationSpec outSpec) { - if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { - mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForDisplay", - FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId); - } - final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); - if (displayMagnifier == null) { - return false; - } - - return displayMagnifier.getMagnificationSpec(outSpec); - } - boolean hasCallbacks() { if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { @@ -768,25 +756,6 @@ final class AccessibilityController { return spec; } - boolean getMagnificationSpec(MagnificationSpec outSpec) { - if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { - mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpec", - FLAGS_MAGNIFICATION_CALLBACK); - } - MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec(); - if (spec == null) { - return false; - } - - outSpec.setTo(spec); - if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { - mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpec", - FLAGS_MAGNIFICATION_CALLBACK, "outSpec={" + outSpec + "}"); - } - - return true; - } - void getMagnificationRegion(Region outMagnificationRegion) { if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationRegion", @@ -1434,18 +1403,20 @@ final class AccessibilityController { private static final boolean DEBUG = false; - private final List<AccessibilityWindow> mTempA11yWindows = new ArrayList<>(); + private final SparseArray<WindowState> mTempWindowStates = new SparseArray<>(); private final Set<IBinder> mTempBinderSet = new ArraySet<>(); + private final RectF mTempRectF = new RectF(); + + private final Matrix mTempMatrix = new Matrix(); + private final Point mTempPoint = new Point(); private final Region mTempRegion = new Region(); private final Region mTempRegion1 = new Region(); - private final Region mTempRegion2 = new Region(); - private final WindowManagerService mService; private final Handler mHandler; @@ -1460,11 +1431,10 @@ final class AccessibilityController { // Set to true if initializing window population complete. private boolean mInitialized; - private final AccessibilityWindowsPopulator mA11yWindowsPopulator; WindowsForAccessibilityObserver(WindowManagerService windowManagerService, - int displayId, WindowsForAccessibilityCallback callback, - AccessibilityWindowsPopulator accessibilityWindowsPopulator) { + int displayId, + WindowsForAccessibilityCallback callback) { mService = windowManagerService; mCallback = callback; mDisplayId = displayId; @@ -1473,7 +1443,6 @@ final class AccessibilityController { AccessibilityController.getAccessibilityControllerInternal(mService); mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration .getSendRecurringAccessibilityEventsInterval(); - mA11yWindowsPopulator = accessibilityWindowsPopulator; computeChangedWindows(true); } @@ -1497,6 +1466,52 @@ final class AccessibilityController { } } + boolean shellRootIsAbove(WindowState windowState, ShellRoot shellRoot) { + int wsLayer = mService.mPolicy.getWindowLayerLw(windowState); + int shellLayer = mService.mPolicy.getWindowLayerFromTypeLw(shellRoot.getWindowType(), + true); + return shellLayer >= wsLayer; + } + + int addShellRootsIfAbove(WindowState windowState, ArrayList<ShellRoot> shellRoots, + int shellRootIndex, List<WindowInfo> windows, Set<IBinder> addedWindows, + Region unaccountedSpace, boolean focusedWindowAdded) { + while (shellRootIndex < shellRoots.size() + && shellRootIsAbove(windowState, shellRoots.get(shellRootIndex))) { + ShellRoot shellRoot = shellRoots.get(shellRootIndex); + shellRootIndex++; + final WindowInfo info = shellRoot.getWindowInfo(); + if (info == null) { + continue; + } + + info.layer = addedWindows.size(); + windows.add(info); + addedWindows.add(info.token); + unaccountedSpace.op(info.regionInScreen, unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + if (unaccountedSpace.isEmpty() && focusedWindowAdded) { + break; + } + } + return shellRootIndex; + } + + private ArrayList<ShellRoot> getSortedShellRoots( + SparseArray<ShellRoot> originalShellRoots) { + ArrayList<ShellRoot> sortedShellRoots = new ArrayList<>(originalShellRoots.size()); + for (int i = originalShellRoots.size() - 1; i >= 0; --i) { + sortedShellRoots.add(originalShellRoots.valueAt(i)); + } + + sortedShellRoots.sort((left, right) -> + mService.mPolicy.getWindowLayerFromTypeLw(right.getWindowType(), true) + - mService.mPolicy.getWindowLayerFromTypeLw(left.getWindowType(), + true)); + + return sortedShellRoots; + } + /** * Check if windows have changed, and send them to the accessibility subsystem if they have. * @@ -1546,29 +1561,44 @@ final class AccessibilityController { Region unaccountedSpace = mTempRegion; unaccountedSpace.set(0, 0, screenWidth, screenHeight); - final List<AccessibilityWindow> visibleWindows = mTempA11yWindows; - mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked( - mDisplayId, visibleWindows); + final SparseArray<WindowState> visibleWindows = mTempWindowStates; + populateVisibleWindowsOnScreen(visibleWindows); Set<IBinder> addedWindows = mTempBinderSet; addedWindows.clear(); boolean focusedWindowAdded = false; final int visibleWindowCount = visibleWindows.size(); + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments = new ArrayList<>(); + + ArrayList<ShellRoot> shellRoots = getSortedShellRoots(dc.mShellRoots); // Iterate until we figure out what is touchable for the entire screen. - for (int i = 0; i < visibleWindowCount; i++) { - final AccessibilityWindow a11yWindow = visibleWindows.get(i); - final Region regionInWindow = new Region(); - a11yWindow.getTouchableRegionInWindow(regionInWindow); - if (windowMattersToAccessibility(a11yWindow, regionInWindow, - unaccountedSpace)) { - addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows); - if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) { - updateUnaccountedSpace(a11yWindow, unaccountedSpace); + int shellRootIndex = 0; + for (int i = visibleWindowCount - 1; i >= 0; i--) { + final WindowState windowState = visibleWindows.valueAt(i); + int prevShellRootIndex = shellRootIndex; + shellRootIndex = addShellRootsIfAbove(windowState, shellRoots, shellRootIndex, + windows, addedWindows, unaccountedSpace, focusedWindowAdded); + + // If a Shell Root was added, it could have accounted for all the space already. + if (shellRootIndex > prevShellRootIndex && unaccountedSpace.isEmpty() + && focusedWindowAdded) { + break; + } + + final Region regionInScreen = new Region(); + computeWindowRegionInScreen(windowState, regionInScreen); + if (windowMattersToAccessibility(windowState, + regionInScreen, unaccountedSpace, + skipRemainingWindowsForTaskFragments)) { + addPopulatedWindowInfo(windowState, regionInScreen, windows, addedWindows); + if (windowMattersToUnaccountedSpaceComputation(windowState)) { + updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace, + skipRemainingWindowsForTaskFragments); } - focusedWindowAdded |= a11yWindow.isFocused(); - } else if (a11yWindow.isUntouchableNavigationBar()) { + focusedWindowAdded |= windowState.isFocused(); + } else if (isUntouchableNavigationBar(windowState, mTempRegion1)) { // If this widow is navigation bar without touchable region, accounting the // region of navigation bar inset because all touch events from this region // would be received by launcher, i.e. this region is a un-touchable one @@ -1617,39 +1647,47 @@ final class AccessibilityController { // Some windows should be excluded from unaccounted space computation, though they still // should be reported - private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) { + private boolean windowMattersToUnaccountedSpaceComputation(WindowState windowState) { // Do not account space of trusted non-touchable windows, except the split-screen // divider. // If it's not trusted, touch events are not sent to the windows behind it. - if (((a11yWindow.getFlags() & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) - && (a11yWindow.getType() != TYPE_DOCK_DIVIDER) - && a11yWindow.isTrustedOverlay()) { + if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) + && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER) + && windowState.isTrustedOverlay()) { return false; } - if (a11yWindow.getType() == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { + if (windowState.mAttrs.type + == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { return false; } return true; } - private boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow, - Region regionInScreen, Region unaccountedSpace) { - if (a11yWindow.ignoreRecentsAnimationForAccessibility()) { + private boolean windowMattersToAccessibility(WindowState windowState, + Region regionInScreen, Region unaccountedSpace, + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) { + final RecentsAnimationController controller = mService.getRecentsAnimationController(); + if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) { return false; } - if (a11yWindow.isFocused()) { + if (windowState.isFocused()) { return true; } + // If the window is part of a task that we're finished with - ignore. + final TaskFragment taskFragment = windowState.getTaskFragment(); + if (taskFragment != null + && skipRemainingWindowsForTaskFragments.contains(taskFragment)) { + return false; + } + // Ignore non-touchable windows, except the split-screen divider, which is // occasionally non-touchable but still useful for identifying split-screen - // mode and the PIP menu. - if (((a11yWindow.getFlags() - & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) - && (a11yWindow.getType() != TYPE_DOCK_DIVIDER - && !a11yWindow.isPIPMenu())) { + // mode. + if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) + && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) { return false; } @@ -1659,36 +1697,88 @@ final class AccessibilityController { } // Add windows of certain types not covered by modal windows. - if (isReportedWindowType(a11yWindow.getType())) { + if (isReportedWindowType(windowState.mAttrs.type)) { return true; } return false; } - private void updateUnaccountedSpace(AccessibilityWindow a11yWindow, - Region unaccountedSpace) { - if (a11yWindow.getType() - != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { - // Account for the space this window takes if the window - // is not an accessibility overlay which does not change - // the reported windows. - final Region touchableRegion = mTempRegion2; - a11yWindow.getTouchableRegionInScreen(touchableRegion); - unaccountedSpace.op(touchableRegion, unaccountedSpace, - Region.Op.REVERSE_DIFFERENCE); - // Account for the space of letterbox. - final Region letterboxBounds = mTempRegion1; - if (a11yWindow.setLetterBoxBoundsIfNeeded(letterboxBounds)) { - unaccountedSpace.op(letterboxBounds, - unaccountedSpace, Region.Op.REVERSE_DIFFERENCE); + private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen, + Region unaccountedSpace, + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) { + // Account for the space this window takes if the window + // is not an accessibility overlay which does not change + // the reported windows. + unaccountedSpace.op(regionInScreen, unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + + // If a window is modal it prevents other windows from being touched + if ((windowState.mAttrs.flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) { + if (!windowState.hasTapExcludeRegion()) { + // Account for all space in the task, whether the windows in it are + // touchable or not. The modal window blocks all touches from the task's + // area. + unaccountedSpace.op(windowState.getDisplayFrame(), unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + } else { + // If a window has tap exclude region, we need to account it. + final Region displayRegion = new Region(windowState.getDisplayFrame()); + final Region tapExcludeRegion = new Region(); + windowState.getTapExcludeRegion(tapExcludeRegion); + displayRegion.op(tapExcludeRegion, displayRegion, + Region.Op.REVERSE_DIFFERENCE); + unaccountedSpace.op(displayRegion, unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); } + + final TaskFragment taskFragment = windowState.getTaskFragment(); + if (taskFragment != null) { + // If the window is associated with a particular task, we can skip the + // rest of the windows for that task. + skipRemainingWindowsForTaskFragments.add(taskFragment); + } else if (!windowState.hasTapExcludeRegion()) { + // If the window is not associated with a particular task, then it is + // globally modal. In this case we can skip all remaining windows when + // it doesn't has tap exclude region. + unaccountedSpace.setEmpty(); + } + } + + // Account for the space of letterbox. + if (windowState.areAppWindowBoundsLetterboxed()) { + unaccountedSpace.op(getLetterboxBounds(windowState), unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); } } - private static void addPopulatedWindowInfo(AccessibilityWindow a11yWindow, - Region regionInScreen, List<WindowInfo> out, Set<IBinder> tokenOut) { - final WindowInfo window = a11yWindow.getWindowInfo(); + private void computeWindowRegionInScreen(WindowState windowState, Region outRegion) { + // Get the touchable frame. + Region touchableRegion = mTempRegion1; + windowState.getTouchableRegion(touchableRegion); + + // Map the frame to get what appears on the screen. + Matrix matrix = mTempMatrix; + populateTransformationMatrix(windowState, matrix); + + forEachRect(touchableRegion, rect -> { + // Move to origin as all transforms are captured by the matrix. + RectF windowFrame = mTempRectF; + windowFrame.set(rect); + windowFrame.offset(-windowState.getFrame().left, -windowState.getFrame().top); + + matrix.mapRect(windowFrame); + + // Union all rects. + outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top, + (int) windowFrame.right, (int) windowFrame.bottom)); + }); + } + + private static void addPopulatedWindowInfo(WindowState windowState, Region regionInScreen, + List<WindowInfo> out, Set<IBinder> tokenOut) { + final WindowInfo window = windowState.getWindowInfo(); window.regionInScreen.set(regionInScreen); window.layer = tokenOut.size(); out.add(window); @@ -1715,6 +1805,23 @@ final class AccessibilityController { && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION); } + private void populateVisibleWindowsOnScreen(SparseArray<WindowState> outWindows) { + final List<WindowState> tempWindowStatesList = new ArrayList<>(); + final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId); + if (dc == null) { + return; + } + + dc.forAllWindows(w -> { + if (w.isVisible()) { + tempWindowStatesList.add(w); + } + }, false /* traverseTopToBottom */); + for (int i = 0; i < tempWindowStatesList.size(); i++) { + outWindows.put(i, tempWindowStatesList.get(i)); + } + } + private WindowState getTopFocusWindow() { return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus; } diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java deleted file mode 100644 index f31ae06f62e9..000000000000 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ /dev/null @@ -1,625 +0,0 @@ -/* - * 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_DOCK_DIVIDER; - -import static com.android.server.wm.utils.RegionUtils.forEachRect; - -import android.annotation.NonNull; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.util.Slog; -import android.util.SparseArray; -import android.view.IWindow; -import android.view.InputWindowHandle; -import android.view.MagnificationSpec; -import android.view.WindowInfo; -import android.view.WindowManager; -import android.window.WindowInfosListener; - -import com.android.internal.annotations.GuardedBy; - -import java.util.ArrayList; -import java.util.List; - -/** - * This class is the accessibility windows population adapter. - */ -public final class AccessibilityWindowsPopulator extends WindowInfosListener { - - private static final String TAG = AccessibilityWindowsPopulator.class.getSimpleName(); - // If the surface flinger callback is not coming within in 2 frames time, i.e. about - // 35ms, then assuming the windows become stable. - private static final int SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS = 35; - // To avoid the surface flinger callbacks always comes within in 2 frames, then no windows - // are reported to the A11y framework, and the animation duration time is 500ms, so setting - // this value as the max timeout value to force computing changed windows. - private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 500; - - private static final float[] sTempFloats = new float[9]; - - private final WindowManagerService mService; - private final AccessibilityController mAccessibilityController; - @GuardedBy("mLock") - private final SparseArray<List<InputWindowHandle>> mInputWindowHandlesOnDisplays = - new SparseArray<>(); - @GuardedBy("mLock") - private final SparseArray<Matrix> mMagnificationSpecInverseMatrix = new SparseArray<>(); - @GuardedBy("mLock") - private final SparseArray<DisplayInfo> mDisplayInfos = new SparseArray<>(); - @GuardedBy("mLock") - private final List<InputWindowHandle> mVisibleWindows = new ArrayList<>(); - @GuardedBy("mLock") - private boolean mWindowsNotificationEnabled = false; - private final Object mLock = new Object(); - private final Handler mHandler; - - AccessibilityWindowsPopulator(WindowManagerService service, - AccessibilityController accessibilityController) { - mService = service; - mAccessibilityController = accessibilityController; - mHandler = new MyHandler(mService.mH.getLooper()); - - register(); - } - - /** - * Gets the visible windows list with the window layer on the specified display. - * - * @param displayId The display. - * @param outWindows The visible windows list. The z-order of each window in the list - * is from the top to bottom. - */ - public void populateVisibleWindowsOnScreenLocked(int displayId, - List<AccessibilityWindow> outWindows) { - List<InputWindowHandle> inputWindowHandles; - final Matrix inverseMatrix = new Matrix(); - final Matrix displayMatrix = new Matrix(); - - synchronized (mLock) { - inputWindowHandles = mInputWindowHandlesOnDisplays.get(displayId); - if (inputWindowHandles == null) { - outWindows.clear(); - - return; - } - inverseMatrix.set(mMagnificationSpecInverseMatrix.get(displayId)); - - final DisplayInfo displayInfo = mDisplayInfos.get(displayId); - if (displayInfo != null) { - displayMatrix.set(displayInfo.mTransform); - } else { - Slog.w(TAG, "The displayInfo of this displayId (" + displayId + ") called " - + "back from the surface fligner is null"); - } - } - - final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); - final ShellRoot shellroot = dc.mShellRoots.get(WindowManager.SHELL_ROOT_LAYER_PIP); - final IBinder pipMenuIBinder = - shellroot != null ? shellroot.getAccessibilityWindowToken() : null; - - for (final InputWindowHandle windowHandle : inputWindowHandles) { - final AccessibilityWindow accessibilityWindow = - AccessibilityWindow.initializeData(mService, windowHandle, inverseMatrix, - pipMenuIBinder, displayMatrix); - - outWindows.add(accessibilityWindow); - } - } - - @Override - public void onWindowInfosChanged(InputWindowHandle[] windowHandles, - DisplayInfo[] displayInfos) { - synchronized (mLock) { - mVisibleWindows.clear(); - for (InputWindowHandle window : windowHandles) { - if (window.visible && window.getWindow() != null) { - mVisibleWindows.add(window); - } - } - - mDisplayInfos.clear(); - for (final DisplayInfo displayInfo : displayInfos) { - mDisplayInfos.put(displayInfo.mDisplayId, displayInfo); - } - - if (mWindowsNotificationEnabled) { - if (!mHandler.hasMessages( - MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT)) { - mHandler.sendEmptyMessageDelayed( - MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT, - WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS); - } - populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked(); - } - } - } - - /** - * Sets to notify the accessibilityController to compute changed windows on - * the display after populating the visible windows if the windows reported - * from the surface flinger changes. - * - * @param register {@code true} means starting windows population. - */ - public void setWindowsNotification(boolean register) { - synchronized (mLock) { - if (mWindowsNotificationEnabled == register) { - return; - } - mWindowsNotificationEnabled = register; - if (mWindowsNotificationEnabled) { - populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked(); - } else { - releaseResources(); - } - } - } - - private void populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked() { - final SparseArray<List<InputWindowHandle>> tempWindowHandleList = new SparseArray<>(); - - for (final InputWindowHandle windowHandle : mVisibleWindows) { - List<InputWindowHandle> inputWindowHandles = tempWindowHandleList.get( - windowHandle.displayId); - - if (inputWindowHandles == null) { - inputWindowHandles = new ArrayList<>(); - tempWindowHandleList.put(windowHandle.displayId, inputWindowHandles); - generateMagnificationSpecInverseMatrixLocked(windowHandle.displayId); - } - inputWindowHandles.add(windowHandle); - } - - final List<Integer> displayIdsForWindowsChanged = new ArrayList<>(); - - getDisplaysForWindowsChangedLocked(displayIdsForWindowsChanged, tempWindowHandleList, - mInputWindowHandlesOnDisplays); - // Clones all windows from the callback of the surface flinger. - mInputWindowHandlesOnDisplays.clear(); - for (int i = 0; i < tempWindowHandleList.size(); i++) { - final int displayId = tempWindowHandleList.keyAt(i); - mInputWindowHandlesOnDisplays.put(displayId, tempWindowHandleList.get(displayId)); - } - - if (displayIdsForWindowsChanged.size() > 0) { - if (!mHandler.hasMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED)) { - mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED, - displayIdsForWindowsChanged).sendToTarget(); - } - - return; - } - mHandler.removeMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE); - mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE, - SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS); - } - - private void getDisplaysForWindowsChangedLocked(List<Integer> outDisplayIdsForWindowsChanged, - SparseArray<List<InputWindowHandle>> newWindowsList, - SparseArray<List<InputWindowHandle>> oldWindowsList) { - for (int i = 0; i < newWindowsList.size(); i++) { - final int displayId = newWindowsList.keyAt(i); - final List<InputWindowHandle> newWindows = newWindowsList.get(displayId); - final List<InputWindowHandle> oldWindows = oldWindowsList.get(displayId); - - if (hasWindowsChangedLocked(newWindows, oldWindows)) { - outDisplayIdsForWindowsChanged.add(displayId); - } - } - } - - private boolean hasWindowsChangedLocked(List<InputWindowHandle> newWindows, - List<InputWindowHandle> oldWindows) { - if (oldWindows == null || oldWindows.size() != newWindows.size()) { - return true; - } - - final int windowsCount = newWindows.size(); - // Since we always traverse windows from high to low layer, - // the old and new windows at the same index should be the - // same, otherwise something changed. - for (int i = 0; i < windowsCount; i++) { - final InputWindowHandle newWindow = newWindows.get(i); - final InputWindowHandle oldWindow = oldWindows.get(i); - - if (!newWindow.getWindow().asBinder().equals(oldWindow.getWindow().asBinder())) { - return true; - } - } - - return false; - } - - private void generateMagnificationSpecInverseMatrixLocked(int displayId) { - MagnificationSpec spec = new MagnificationSpec(); - if (!mAccessibilityController.getMagnificationSpecForDisplay(displayId, spec)) { - return; - } - sTempFloats[Matrix.MSCALE_X] = spec.scale; - sTempFloats[Matrix.MSKEW_Y] = 0; - sTempFloats[Matrix.MSKEW_X] = 0; - sTempFloats[Matrix.MSCALE_Y] = spec.scale; - sTempFloats[Matrix.MTRANS_X] = spec.offsetX; - sTempFloats[Matrix.MTRANS_Y] = spec.offsetY; - sTempFloats[Matrix.MPERSP_0] = 0; - sTempFloats[Matrix.MPERSP_1] = 0; - sTempFloats[Matrix.MPERSP_2] = 1; - - final Matrix tempMatrix = new Matrix(); - tempMatrix.setValues(sTempFloats); - - final Matrix inverseMatrix = new Matrix(); - final boolean result = tempMatrix.invert(inverseMatrix); - - if (!result) { - Slog.e(TAG, "Can't inverse the magnification spec matrix with the " - + "magnification spec = " + spec + " on the displayId = " + displayId); - return; - } - mMagnificationSpecInverseMatrix.set(displayId, inverseMatrix); - } - - private void notifyWindowsChanged(@NonNull List<Integer> displayIdsForWindowsChanged) { - mHandler.removeMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT); - - for (int i = 0; i < displayIdsForWindowsChanged.size(); i++) { - mAccessibilityController.performComputeChangedWindowsNot( - displayIdsForWindowsChanged.get(i), false); - } - } - - private void forceUpdateWindows() { - final List<Integer> displayIdsForWindowsChanged = new ArrayList<>(); - - synchronized (mLock) { - for (int i = 0; i < mInputWindowHandlesOnDisplays.size(); i++) { - final int displayId = mInputWindowHandlesOnDisplays.keyAt(i); - displayIdsForWindowsChanged.add(displayId); - } - } - notifyWindowsChanged(displayIdsForWindowsChanged); - } - - @GuardedBy("mLock") - private void releaseResources() { - mInputWindowHandlesOnDisplays.clear(); - mMagnificationSpecInverseMatrix.clear(); - mVisibleWindows.clear(); - mDisplayInfos.clear(); - mWindowsNotificationEnabled = false; - mHandler.removeCallbacksAndMessages(null); - } - - private class MyHandler extends Handler { - public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 1; - public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE = 2; - public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT = 3; - - MyHandler(Looper looper) { - super(looper, null, false); - } - - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MESSAGE_NOTIFY_WINDOWS_CHANGED: { - final List<Integer> displayIdsForWindowsChanged = (List<Integer>) message.obj; - notifyWindowsChanged(displayIdsForWindowsChanged); - } break; - - case MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE: { - forceUpdateWindows(); - } break; - - case MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT: { - Slog.w(TAG, "Windows change within in 2 frames continuously over 500 ms " - + "and notify windows changed immediately"); - mHandler.removeMessages( - MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE); - - forceUpdateWindows(); - } break; - } - } - } - - /** - * This class represents information about a window from the - * surface flinger to the accessibility framework. - */ - public static class AccessibilityWindow { - private static final Region TEMP_REGION = new Region(); - private static final RectF TEMP_RECTF = new RectF(); - // Data - private IWindow mWindow; - private int mDisplayId; - private int mFlags; - private int mType; - private int mPrivateFlags; - private boolean mIsPIPMenu; - private boolean mIsFocused; - private boolean mShouldMagnify; - private boolean mIgnoreDuetoRecentsAnimation; - private boolean mIsTrustedOverlay; - private final Region mTouchableRegionInScreen = new Region(); - private final Region mTouchableRegionInWindow = new Region(); - private final Region mLetterBoxBounds = new Region(); - private WindowInfo mWindowInfo; - - /** - * Returns the instance after initializing the internal data. - * @param service The window manager service. - * @param inputWindowHandle The window from the surface flinger. - * @param inverseMatrix The magnification spec inverse matrix. - */ - public static AccessibilityWindow initializeData(WindowManagerService service, - InputWindowHandle inputWindowHandle, Matrix inverseMatrix, IBinder pipIBinder, - Matrix displayMatrix) { - final IWindow window = inputWindowHandle.getWindow(); - final WindowState windowState = window != null ? service.mWindowMap.get( - window.asBinder()) : null; - - final AccessibilityWindow instance = new AccessibilityWindow(); - - instance.mWindow = inputWindowHandle.getWindow(); - instance.mDisplayId = inputWindowHandle.displayId; - instance.mFlags = inputWindowHandle.layoutParamsFlags; - instance.mType = inputWindowHandle.layoutParamsType; - instance.mIsPIPMenu = inputWindowHandle.getWindow().asBinder().equals(pipIBinder); - - // TODO (b/199357848): gets the private flag of the window from other way. - instance.mPrivateFlags = windowState != null ? windowState.mAttrs.privateFlags : 0; - // TODO (b/199358208) : using new way to implement the focused window. - instance.mIsFocused = windowState != null && windowState.isFocused(); - instance.mShouldMagnify = windowState == null || windowState.shouldMagnify(); - - final RecentsAnimationController controller = service.getRecentsAnimationController(); - instance.mIgnoreDuetoRecentsAnimation = windowState != null && controller != null - && controller.shouldIgnoreForAccessibility(windowState); - instance.mIsTrustedOverlay = inputWindowHandle.trustedOverlay; - - // TODO (b/199358388) : gets the letterbox bounds of the window from other way. - if (windowState != null && windowState.areAppWindowBoundsLetterboxed()) { - getLetterBoxBounds(windowState, instance.mLetterBoxBounds); - } - - final Rect windowFrame = new Rect(inputWindowHandle.frameLeft, - inputWindowHandle.frameTop, inputWindowHandle.frameRight, - inputWindowHandle.frameBottom); - getTouchableRegionInWindow(instance.mShouldMagnify, inputWindowHandle.touchableRegion, - instance.mTouchableRegionInWindow, windowFrame, inverseMatrix, displayMatrix); - getUnMagnifiedTouchableRegion(instance.mShouldMagnify, - inputWindowHandle.touchableRegion, instance.mTouchableRegionInScreen, - inverseMatrix, displayMatrix); - instance.mWindowInfo = windowState != null - ? windowState.getWindowInfo() : getWindowInfoForWindowlessWindows(instance); - - return instance; - } - - /** - * Returns the touchable region in the screen. - * @param outRegion The touchable region. - */ - public void getTouchableRegionInScreen(Region outRegion) { - outRegion.set(mTouchableRegionInScreen); - } - - /** - * Returns the touchable region in the window. - * @param outRegion The touchable region. - */ - public void getTouchableRegionInWindow(Region outRegion) { - outRegion.set(mTouchableRegionInWindow); - } - - /** - * @return the layout parameter flag {@link android.view.WindowManager.LayoutParams#flags}. - */ - public int getFlags() { - return mFlags; - } - - /** - * @return the layout parameter type {@link android.view.WindowManager.LayoutParams#type}. - */ - public int getType() { - return mType; - } - - /** - * @return the layout parameter private flag - * {@link android.view.WindowManager.LayoutParams#privateFlags}. - */ - public int getPrivateFlag() { - return mPrivateFlags; - } - - /** - * @return the windowInfo {@link WindowInfo}. - */ - public WindowInfo getWindowInfo() { - return mWindowInfo; - } - - /** - * Gets the letter box bounds if activity bounds are letterboxed - * or letterboxed for display cutout. - * - * @return {@code true} there's a letter box bounds. - */ - public Boolean setLetterBoxBoundsIfNeeded(Region outBounds) { - if (mLetterBoxBounds.isEmpty()) { - return false; - } - - outBounds.set(mLetterBoxBounds); - return true; - } - - /** - * @return true if this window should be magnified. - */ - public boolean shouldMagnify() { - return mShouldMagnify; - } - - /** - * @return true if this window is focused. - */ - public boolean isFocused() { - return mIsFocused; - } - - /** - * @return true if it's running the recent animation but not the target app. - */ - public boolean ignoreRecentsAnimationForAccessibility() { - return mIgnoreDuetoRecentsAnimation; - } - - /** - * @return true if this window is the trusted overlay. - */ - public boolean isTrustedOverlay() { - return mIsTrustedOverlay; - } - - /** - * @return true if this window is the navigation bar with the gesture mode. - */ - public boolean isUntouchableNavigationBar() { - if (mType != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR) { - return false; - } - - return mTouchableRegionInScreen.isEmpty(); - } - - /** - * @return true if this window is PIP menu. - */ - public boolean isPIPMenu() { - return mIsPIPMenu; - } - - private static void getTouchableRegionInWindow(boolean shouldMagnify, Region inRegion, - Region outRegion, Rect frame, Matrix inverseMatrix, Matrix displayMatrix) { - // Some modal windows, like the activity with Theme.dialog, has the full screen - // as its touchable region, but its window frame is smaller than the touchable - // region. The region we report should be the touchable area in the window frame - // for the consistency and match developers expectation. - // So we need to make the intersection between the frame and touchable region to - // obtain the real touch region in the screen. - Region touchRegion = TEMP_REGION; - touchRegion.set(inRegion); - touchRegion.op(frame, Region.Op.INTERSECT); - - getUnMagnifiedTouchableRegion(shouldMagnify, touchRegion, outRegion, inverseMatrix, - displayMatrix); - } - - /** - * Gets the un-magnified touchable region. If this window can be magnified and magnifying, - * we will transform the input touchable region by applying the inverse matrix of the - * magnification spec to get the un-magnified touchable region. - * @param shouldMagnify The window can be magnified. - * @param inRegion The touchable region of this window. - * @param outRegion The un-magnified touchable region of this window. - * @param inverseMatrix The inverse matrix of the magnification spec. - * @param displayMatrix The display transform matrix which takes display coordinates to - * logical display coordinates. - */ - private static void getUnMagnifiedTouchableRegion(boolean shouldMagnify, Region inRegion, - Region outRegion, Matrix inverseMatrix, Matrix displayMatrix) { - if ((!shouldMagnify || inverseMatrix.isIdentity()) && displayMatrix.isIdentity()) { - outRegion.set(inRegion); - return; - } - - forEachRect(inRegion, rect -> { - // Move to origin as all transforms are captured by the matrix. - RectF windowFrame = TEMP_RECTF; - windowFrame.set(rect); - - inverseMatrix.mapRect(windowFrame); - displayMatrix.mapRect(windowFrame); - // Union all rects. - outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top, - (int) windowFrame.right, (int) windowFrame.bottom)); - }); - } - - private static WindowInfo getWindowInfoForWindowlessWindows(AccessibilityWindow window) { - WindowInfo windowInfo = WindowInfo.obtain(); - windowInfo.displayId = window.mDisplayId; - windowInfo.type = window.mType; - windowInfo.token = window.mWindow.asBinder(); - windowInfo.hasFlagWatchOutsideTouch = (window.mFlags - & WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH) != 0; - windowInfo.inPictureInPicture = false; - - // There only are two windowless windows now, one is split window, and the other - // one is PIP. - if (windowInfo.type == TYPE_DOCK_DIVIDER) { - windowInfo.title = "Splitscreen Divider"; - } else if (window.mIsPIPMenu) { - windowInfo.title = "Picture-in-Picture menu"; - } - return windowInfo; - } - - private static void getLetterBoxBounds(WindowState windowState, Region outRegion) { - final Rect letterboxInsets = windowState.mActivityRecord.getLetterboxInsets(); - final Rect nonLetterboxRect = windowState.getBounds(); - - nonLetterboxRect.inset(letterboxInsets); - outRegion.set(windowState.getBounds()); - outRegion.op(nonLetterboxRect, Region.Op.DIFFERENCE); - } - - @Override - public String toString() { - String builder = "A11yWindow=[" + mWindow.asBinder() - + ", displayId=" + mDisplayId - + ", flag=0x" + Integer.toHexString(mFlags) - + ", type=" + mType - + ", privateFlag=0x" + Integer.toHexString(mPrivateFlags) - + ", focused=" + mIsFocused - + ", shouldMagnify=" + mShouldMagnify - + ", ignoreDuetoRecentsAnimation=" + mIgnoreDuetoRecentsAnimation - + ", isTrustedOverlay=" + mIsTrustedOverlay - + ", regionInScreen=" + mTouchableRegionInScreen - + ", touchableRegion=" + mTouchableRegionInWindow - + ", letterBoxBounds=" + mLetterBoxBounds - + ", isPIPMenu=" + mIsPIPMenu - + ", windowInfo=" + mWindowInfo - + "]"; - - return builder; - } - } -} diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java index 1bb9ca770c45..4df2e1782e4f 100644 --- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java @@ -53,8 +53,8 @@ public abstract class ActivityInterceptorCallback { */ @IntDef(suffix = { "_ORDERED_ID" }, value = { FIRST_ORDERED_ID, - COMMUNAL_MODE_ORDERED_ID, PERMISSION_POLICY_ORDERED_ID, + VIRTUAL_DEVICE_SERVICE_ORDERED_ID, LAST_ORDERED_ID // Update this when adding new ids }) @Retention(RetentionPolicy.SOURCE) @@ -66,20 +66,21 @@ public abstract class ActivityInterceptorCallback { static final int FIRST_ORDERED_ID = 0; /** - * The identifier for {@link com.android.server.communal.CommunalManagerService} interceptor. + * The identifier for {@link com.android.server.policy.PermissionPolicyService} interceptor */ - public static final int COMMUNAL_MODE_ORDERED_ID = 1; + public static final int PERMISSION_POLICY_ORDERED_ID = 1; /** - * The identifier for {@link com.android.server.policy.PermissionPolicyService} interceptor + * The identifier for {@link com.android.server.companion.virtual.VirtualDeviceManagerService} + * interceptor. */ - public static final int PERMISSION_POLICY_ORDERED_ID = 2; + public static final int VIRTUAL_DEVICE_SERVICE_ORDERED_ID = 3; /** * The final id, used by the framework to determine the valid range of ids. Update this when * adding new ids. */ - static final int LAST_ORDERED_ID = PERMISSION_POLICY_ORDERED_ID; + static final int LAST_ORDERED_ID = VIRTUAL_DEVICE_SERVICE_ORDERED_ID; /** * Data class for storing the various arguments needed for activity interception. diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 4b33f0ec1723..a8dd856c2191 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -11,8 +11,6 @@ import static android.app.WaitResult.LAUNCH_STATE_WARM; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ACTIVITY_START; @@ -483,10 +481,6 @@ class ActivityMetricsLogger { case WINDOWING_MODE_FULLSCREEN: mWindowState = WINDOW_STATE_STANDARD; break; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: - mWindowState = WINDOW_STATE_SIDE_BY_SIDE; - break; case WINDOWING_MODE_FREEFORM: mWindowState = WINDOW_STATE_FREEFORM; break; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0b0b70481589..86ef8d2b26fc 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1546,10 +1546,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void onDisplayChanged(DisplayContent dc) { DisplayContent prevDc = mDisplayContent; super.onDisplayChanged(dc); - if (prevDc == null || prevDc == mDisplayContent) { + if (prevDc == mDisplayContent) { return; } + mDisplayContent.onRunningActivityChanged(); + + if (prevDc == null) { + return; + } + prevDc.onRunningActivityChanged(); + // TODO(b/169035022): move to a more-appropriate place. mTransitionController.collect(this); if (prevDc.mOpeningApps.remove(this)) { @@ -1623,6 +1630,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Trigger TaskInfoChanged to update the camera compat UI. getTask().dispatchTaskInfoChangedIfNeeded(true /* force */); + // TaskOrganizerController#onTaskInfoChanged adds pending task events to the queue waiting + // for the surface placement to be ready. So need to trigger surface placement to dispatch + // events to avoid stale state for the camera compat control. + getDisplayContent().setLayoutNeeded(); + mWmService.mWindowPlacerLocked.performSurfacePlacement(); } void updateCameraCompatStateFromUser(@CameraCompatControlState int state) { @@ -2134,7 +2146,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return activityInfo != null ? activityInfo.applicationInfo : null; }); - final int typeParameter = mWmService.mStartingSurfaceController + final int typeParameter = StartingSurfaceController .makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated, useEmpty, useLegacy, activityAllDrawn); @@ -2752,9 +2764,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } boolean isResizeable() { + return isResizeable(/* checkPictureInPictureSupport */ true); + } + + boolean isResizeable(boolean checkPictureInPictureSupport) { return mAtmService.mForceResizableActivities || ActivityInfo.isResizeableMode(info.resizeMode) - || info.supportsPictureInPicture() + || (info.supportsPictureInPicture() && checkPictureInPictureSupport) // If the activity can be embedded, it should inherit the bounds of task fragment. || isEmbedded(); } @@ -3900,6 +3916,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Reset the last saved PiP snap fraction on removal. mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent); + mDisplayContent.onRunningActivityChanged(); mWmService.mEmbeddedWindowController.onActivityRemoved(this); mRemovingFromDisplay = false; } @@ -6616,13 +6633,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return splashScreenThemeResId; } + void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch, + boolean startActivity, ActivityRecord sourceRecord) { + showStartingWindow(prev, newTask, taskSwitch, isProcessRunning(), startActivity, + sourceRecord); + } + /** * @param prev Previous activity which contains a starting window. + * @param processRunning Whether the client process is running. * @param startActivity Whether this activity is just created from starter. * @param sourceRecord The source activity which start this activity. */ void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch, - boolean startActivity, ActivityRecord sourceRecord) { + boolean processRunning, boolean startActivity, ActivityRecord sourceRecord) { if (mTaskOverlay) { // We don't show starting window for overlay activities. return; @@ -6647,7 +6671,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && task.getActivity((r) -> !r.finishing && r != this) == null; final boolean scheduled = addStartingWindow(packageName, resolvedTheme, - prev, newTask || newSingleActivity, taskSwitch, isProcessRunning(), + prev, newTask || newSingleActivity, taskSwitch, processRunning, allowTaskSnapshot(), activityCreated, mSplashScreenStyleEmpty, allDrawn); if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) { Slog.d(TAG, "Scheduled starting window for " + this); @@ -7660,10 +7684,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // orientation with insets applied. return; } - // Activity should be resizable if the task is. + // Not using Task#isResizeable() or ActivityRecord#isResizeable() directly because app + // compatibility testing showed that android:supportsPictureInPicture="true" alone is not + // sufficient signal for not letterboxing an app. + // TODO(214602463): Remove multi-window check since orientation and aspect ratio + // restrictions should always be applied in multi-window. final boolean isResizeable = task != null - ? task.isResizeable() || isResizeable() - : isResizeable(); + // Activity should be resizable if the task is. + ? task.isResizeable(/* checkPictureInPictureSupport */ false) + || isResizeable(/* checkPictureInPictureSupport */ false) + : isResizeable(/* checkPictureInPictureSupport */ false); if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable) { // Ignore orientation request for resizable apps in multi window. return; @@ -8179,8 +8209,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final float maxAspectRatio = info.getMaxAspectRatio(); final Task rootTask = getRootTask(); final float minAspectRatio = getMinAspectRatio(); + // Not using ActivityRecord#isResizeable() directly because app compatibility testing + // showed that android:supportsPictureInPicture="true" alone is not sufficient signal for + // not letterboxing an app. + // TODO(214602463): Remove multi-window check since orientation and aspect ratio + // restrictions should always be applied in multi-window. if (task == null || rootTask == null - || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets() + || (inMultiWindowMode() && isResizeable(/* checkPictureInPictureSupport */ false) && !fixedOrientationLetterboxed) || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1) || isInVrUiMode(getConfiguration())) { diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index 9353f6dfdabf..16813485b988 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -26,6 +26,7 @@ import android.compat.annotation.Disabled; import android.os.IBinder; import android.os.InputConstants; import android.os.Looper; +import android.os.Process; import android.util.Slog; import android.view.InputChannel; import android.view.InputEvent; @@ -113,14 +114,13 @@ class ActivityRecordInputSink { } private InputWindowHandle createInputWindowHandle() { - InputWindowHandle inputWindowHandle = new InputWindowHandle( - mActivityRecord.getInputApplicationHandle(false), + InputWindowHandle inputWindowHandle = new InputWindowHandle(null, mActivityRecord.getDisplayId()); inputWindowHandle.replaceTouchableRegionWithCrop( mActivityRecord.getParentSurfaceControl()); inputWindowHandle.name = mName; - inputWindowHandle.ownerUid = mActivityRecord.getUid(); - inputWindowHandle.ownerPid = mActivityRecord.getPid(); + inputWindowHandle.ownerUid = Process.myUid(); + inputWindowHandle.ownerPid = Process.myPid(); inputWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index ecc85878353a..87fb2902dddb 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -455,6 +455,10 @@ public class ActivityStartController { // Lock the loop to ensure the activities launched in a sequence. synchronized (mService.mGlobalLock) { mService.deferWindowLayout(); + // To avoid creating multiple starting window when creating starting multiples + // activities, we defer the creation of the starting window once all start request + // are processed + mService.mWindowManager.mStartingSurfaceController.beginDeferAddStartingWindow(); try { for (int i = 0; i < starters.length; i++) { final int startResult = starters[i].setResultTo(resultTo) @@ -480,6 +484,7 @@ public class ActivityStartController { } } } finally { + mService.mWindowManager.mStartingSurfaceController.endDeferAddStartingWindow(); mService.continueWindowLayout(); } } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 65f9e83dc5df..e119a9a0373a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -30,8 +30,6 @@ import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; @@ -1926,27 +1924,6 @@ class ActivityStarter { private void computeLaunchParams(ActivityRecord r, ActivityRecord sourceRecord, Task targetTask) { - final Task sourceRootTask = mSourceRootTask != null ? mSourceRootTask - : mRootWindowContainer.getTopDisplayFocusedRootTask(); - if (sourceRootTask != null && sourceRootTask.inSplitScreenWindowingMode() - && (mOptions == null - || mOptions.getLaunchWindowingMode() == WINDOWING_MODE_UNDEFINED)) { - int windowingMode = - targetTask != null ? targetTask.getWindowingMode() : WINDOWING_MODE_UNDEFINED; - if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { - if (sourceRootTask.inSplitScreenPrimaryWindowingMode()) { - windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - } else if (sourceRootTask.inSplitScreenSecondaryWindowingMode()) { - windowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - } - } - - if (mOptions == null) { - mOptions = ActivityOptions.makeBasic(); - } - mOptions.setLaunchWindowingMode(windowingMode); - } - mSupervisor.getLaunchParamsController().calculate(targetTask, r.info.windowLayout, r, sourceRecord, mOptions, mRequest, PHASE_BOUNDS, mLaunchParams); mPreferredTaskDisplayArea = mLaunchParams.hasPreferredTaskDisplayArea() diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 3cecce25d195..dd394ca3da67 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -36,7 +36,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY; import static android.content.pm.PackageManager.PERMISSION_DENIED; @@ -364,6 +363,17 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** Check if placing task or activity on specified display is allowed. */ boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid, ActivityInfo activityInfo) { + return canPlaceEntityOnDisplay(displayId, callingPid, callingUid, null /* task */, + activityInfo); + } + + boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid, Task task) { + return canPlaceEntityOnDisplay(displayId, callingPid, callingUid, task, + null /* activityInfo */); + } + + private boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid, + Task task, ActivityInfo activityInfo) { if (displayId == DEFAULT_DISPLAY) { // No restrictions for the default display. return true; @@ -372,12 +382,31 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Can't launch on secondary displays if feature is not supported. return false; } + if (!isCallerAllowedToLaunchOnDisplay(callingPid, callingUid, displayId, activityInfo)) { // Can't place activities to a display that has restricted launch rules. // In this case the request should be made by explicitly adding target display id and // by caller with corresponding permissions. See #isCallerAllowedToLaunchOnDisplay(). return false; } + + final DisplayContent displayContent = + mRootWindowContainer.getDisplayContentOrCreate(displayId); + if (displayContent != null && displayContent.mDwpcHelper.hasController()) { + final ArrayList<ActivityInfo> activities = new ArrayList<>(); + if (activityInfo != null) { + activities.add(activityInfo); + } + if (task != null) { + task.forAllActivities((r) -> { + activities.add(r.info); + }); + } + if (!displayContent.mDwpcHelper.canContainActivities(activities)) { + return false; + } + } + return true; } @@ -2169,10 +2198,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { boolean forceNonResizable) { final boolean isSecondaryDisplayPreferred = preferredTaskDisplayArea != null && preferredTaskDisplayArea.getDisplayId() != DEFAULT_DISPLAY; - final boolean inSplitScreenMode = actualRootTask != null - && actualRootTask.getDisplayArea().isSplitScreenModeActivated(); - if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) - && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) { + if (!task.isActivityTypeStandardOrUndefined()) { return; } @@ -2513,10 +2539,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { == PERMISSION_GRANTED) { mRecentTasks.setFreezeTaskListReordering(); } - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || activityOptions.getLaunchRootTask() != null) { - // Don't move home activity forward if we are launching into primary split or - // there is a launch root set. + if (activityOptions.getLaunchRootTask() != null) { + // Don't move home activity forward if there is a launch root set. moveHomeTaskForward = false; } } diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 47622bc83417..5919806ae7e1 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -24,18 +24,17 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION; import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT; import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; -import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION; import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; import static com.android.server.wm.DisplayAreaPolicyBuilder.HierarchyBuilder; +import android.annotation.Nullable; import android.content.res.Resources; import android.os.Bundle; import android.text.TextUtils; @@ -88,6 +87,9 @@ public abstract class DisplayAreaPolicy { */ public abstract TaskDisplayArea getDefaultTaskDisplayArea(); + /** Returns the {@link TaskDisplayArea} specified by launch options. */ + public abstract TaskDisplayArea getTaskDisplayArea(@Nullable Bundle options); + /** Provider for platform-default display area policy. */ static final class DefaultProvider implements DisplayAreaPolicy.Provider { @Override @@ -134,11 +136,6 @@ public abstract class DisplayAreaPolicy { .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE) .build()) - .addFeature(new Feature.Builder(wmService.mPolicy, - "OneHandedBackgroundPanel", - FEATURE_ONE_HANDED_BACKGROUND_PANEL) - .upTo(TYPE_WALLPAPER) - .build()) .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded", FEATURE_ONE_HANDED) .all() diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index 3d7ac6c1a3f8..8e21d9607072 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -27,12 +27,19 @@ import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST; import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; + +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityOptions; import android.os.Bundle; import android.util.ArrayMap; import android.util.ArraySet; +import android.window.DisplayAreaOrganizer; +import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; import com.android.server.policy.WindowManagerPolicy; import java.util.ArrayList; @@ -43,6 +50,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Function; /** * A builder for instantiating a complex {@link DisplayAreaPolicy} @@ -149,6 +157,8 @@ class DisplayAreaPolicyBuilder { **/ @Nullable private BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc; + @Nullable private Function<Bundle, TaskDisplayArea> mSelectTaskDisplayAreaFunc; + /** Defines the root hierarchy for the whole logical display. */ DisplayAreaPolicyBuilder setRootHierarchy(HierarchyBuilder rootHierarchyBuilder) { mRootHierarchyBuilder = rootHierarchyBuilder; @@ -176,14 +186,25 @@ class DisplayAreaPolicyBuilder { } /** + * The policy will use this function to find the {@link TaskDisplayArea}. + * @see DefaultSelectTaskDisplayAreaFunction as an example. + */ + DisplayAreaPolicyBuilder setSelectTaskDisplayAreaFunc( + Function<Bundle, TaskDisplayArea> selectTaskDisplayAreaFunc) { + mSelectTaskDisplayAreaFunc = selectTaskDisplayAreaFunc; + return this; + } + + /** * Makes sure the setting meets the requirement: * 1. {@link #mRootHierarchyBuilder} must be set. * 2. {@link RootDisplayArea} and {@link TaskDisplayArea} must have unique ids. * 3. {@link Feature} below the same {@link RootDisplayArea} must have unique ids. * 4. There must be exactly one {@link HierarchyBuilder} that contains the IME container. * 5. There must be exactly one {@link HierarchyBuilder} that contains the default - * {@link TaskDisplayArea} with id {@link FEATURE_DEFAULT_TASK_CONTAINER}. - * 6. None of the ids is greater than {@link FEATURE_VENDOR_LAST}. + * {@link TaskDisplayArea} with id + * {@link DisplayAreaOrganizer#FEATURE_DEFAULT_TASK_CONTAINER}. + * 6. None of the ids is greater than {@link DisplayAreaOrganizer#FEATURE_VENDOR_LAST}. */ private void validate() { if (mRootHierarchyBuilder == null) { @@ -250,7 +271,7 @@ class DisplayAreaPolicyBuilder { * {@link Feature} below the same {@link RootDisplayArea} must have unique ids, but * {@link Feature} below different {@link RootDisplayArea} can have the same id so that we can * organize them together. - * None of the ids is greater than {@link FEATURE_VENDOR_LAST} + * None of the ids is greater than {@link DisplayAreaOrganizer#FEATURE_VENDOR_LAST} * * @param uniqueIdSet ids of {@link RootDisplayArea} and {@link TaskDisplayArea} that must be * unique, @@ -323,7 +344,7 @@ class DisplayAreaPolicyBuilder { mRootHierarchyBuilder.mRoot, displayAreaGroupRoots); } return new Result(wmService, mRootHierarchyBuilder.mRoot, displayAreaGroupRoots, - mSelectRootForWindowFunc); + mSelectRootForWindowFunc, mSelectTaskDisplayAreaFunc); } /** @@ -368,6 +389,51 @@ class DisplayAreaPolicyBuilder { } /** + * The default function to find {@link TaskDisplayArea} if there's no other function set + * through {@link #setSelectTaskDisplayAreaFunc(Function)}. + * <p> + * This function returns {@link TaskDisplayArea} specified by + * {@link ActivityOptions#getLaunchTaskDisplayArea()} if it is not {@code null}. Otherwise, + * returns {@link DisplayContent#getDefaultTaskDisplayArea()}. + * </p> + */ + private static class DefaultSelectTaskDisplayAreaFunction implements + Function<Bundle, TaskDisplayArea> { + private final TaskDisplayArea mDefaultTaskDisplayArea; + private final int mDisplayId; + + DefaultSelectTaskDisplayAreaFunction(TaskDisplayArea defaultTaskDisplayArea) { + mDefaultTaskDisplayArea = defaultTaskDisplayArea; + mDisplayId = defaultTaskDisplayArea.getDisplayId(); + } + + @Override + public TaskDisplayArea apply(@Nullable Bundle options) { + if (options == null) { + return mDefaultTaskDisplayArea; + } + final ActivityOptions activityOptions = new ActivityOptions(options); + final WindowContainerToken tdaToken = activityOptions.getLaunchTaskDisplayArea(); + if (tdaToken == null) { + return mDefaultTaskDisplayArea; + } + final TaskDisplayArea tda = WindowContainer.fromBinder(tdaToken.asBinder()) + .asTaskDisplayArea(); + if (tda == null) { + ProtoLog.w(WM_DEBUG_WINDOW_ORGANIZER, "The TaskDisplayArea with %s does not " + + "exist.", tdaToken); + return mDefaultTaskDisplayArea; + } + if (tda.getDisplayId() != mDisplayId) { + throw new IllegalArgumentException("The specified TaskDisplayArea must attach " + + "to Display#" + mDisplayId + ", but it is in Display#" + + tda.getDisplayId()); + } + return tda; + } + } + + /** * Builder to define {@link Feature} and {@link DisplayArea} hierarchy under a * {@link RootDisplayArea} */ @@ -722,11 +788,13 @@ class DisplayAreaPolicyBuilder { static class Result extends DisplayAreaPolicy { final List<RootDisplayArea> mDisplayAreaGroupRoots; final BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc; + private final Function<Bundle, TaskDisplayArea> mSelectTaskDisplayAreaFunc; private final TaskDisplayArea mDefaultTaskDisplayArea; Result(WindowManagerService wmService, RootDisplayArea root, List<RootDisplayArea> displayAreaGroupRoots, - BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc) { + BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc, + Function<Bundle, TaskDisplayArea> selectTaskDisplayAreaFunc) { super(wmService, root); mDisplayAreaGroupRoots = Collections.unmodifiableList(displayAreaGroupRoots); mSelectRootForWindowFunc = selectRootForWindowFunc; @@ -740,6 +808,9 @@ class DisplayAreaPolicyBuilder { throw new IllegalStateException( "No display area with FEATURE_DEFAULT_TASK_CONTAINER"); } + mSelectTaskDisplayAreaFunc = selectTaskDisplayAreaFunc != null + ? selectTaskDisplayAreaFunc + : new DefaultSelectTaskDisplayAreaFunction(mDefaultTaskDisplayArea); } @Override @@ -796,6 +867,12 @@ class DisplayAreaPolicyBuilder { public TaskDisplayArea getDefaultTaskDisplayArea() { return mDefaultTaskDisplayArea; } + + @NonNull + @Override + public TaskDisplayArea getTaskDisplayArea(@Nullable Bundle options) { + return mSelectTaskDisplayAreaFunc.apply(options); + } } static class PendingArea { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 55f463dba2f7..c65ca0847563 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -218,12 +218,10 @@ import android.view.Surface.Rotation; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; -import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManagerPolicyConstants.PointerEventListener; -import android.window.DisplayWindowPolicyController; import android.window.IDisplayAreaOrganizer; import android.window.TransitionRequestInfo; @@ -699,12 +697,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean mDontMoveToTop; /** - * The policy controller of the windows that can be displayed on the virtual display. + * The helper of policy controller. * - * @see DisplayWindowPolicyController + * @see DisplayWindowPolicyControllerHelper */ - @Nullable - DisplayWindowPolicyController mDisplayWindowPolicyController; + DisplayWindowPolicyControllerHelper mDwpcHelper; private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; @@ -1053,8 +1050,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); - mTransitionController.registerLegacyListener( - mWmService.mActivityManagerAppTransitionNotifier); mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); mAppTransitionController = new AppTransitionController(mWmService, this); mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); @@ -1425,7 +1420,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWaitingForConfig = true; if (mTransitionController.isShellTransitionsEnabled()) { requestChangeTransitionIfNeeded(changes, null /* displayChange */); - } else { + } else if (mLastHasContent) { mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this); } sendNewConfiguration(); @@ -1702,8 +1697,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp case SOFT_INPUT_STATE_HIDDEN: return false; } - return r.mLastImeShown && mInputMethodWindow != null && mInputMethodWindow.mHasSurface - && mInputMethodWindow.mViewVisibility == View.VISIBLE; + return r.mLastImeShown; } /** Returns {@code true} if the top activity is transformed with the new rotation of display. */ @@ -2741,8 +2735,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayInfo.copyFrom(newDisplayInfo); } - mDisplayWindowPolicyController = - displayManagerInternal.getDisplayWindowPolicyController(mDisplayId); + mDwpcHelper = new DisplayWindowPolicyControllerHelper(this); } updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, @@ -2783,7 +2776,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (displayMetricsChanged || physicalDisplayChanged) { if (physicalDisplayChanged) { // Reapply the window settings as the underlying physical display has changed. - mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); + // Do not include rotation settings here, postpone them until the display + // metrics are updated as rotation settings might depend on them + mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this, + /* includeRotationSettings */ false); } // If there is an override set for base values - use it, otherwise use new values. @@ -3222,6 +3218,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes, @Nullable TransitionRequestInfo.DisplayChange displayChange) { + if (!mLastHasContent) return; final TransitionController controller = mTransitionController; if (controller.isCollecting()) { if (displayChange != null) { @@ -3451,10 +3448,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInputMonitor.dump(pw, " "); pw.println(); mInsetsStateController.dump(prefix, pw); - if (mDisplayWindowPolicyController != null) { - pw.println(); - mDisplayWindowPolicyController.dump(prefix, pw); - } + mDwpcHelper.dump(prefix, pw); } @Override @@ -3682,6 +3676,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } + /** Update the top activity and the uids of non-finishing activity */ + void onRunningActivityChanged() { + mDwpcHelper.onRunningActivityChanged(); + } + /** Called when the focused {@link TaskDisplayArea} on this display may have changed. */ void onLastFocusedTaskDisplayAreaChanged(@Nullable TaskDisplayArea taskDisplayArea) { // Only record the TaskDisplayArea that handles orientation request. @@ -4102,7 +4101,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp "IME-snapshot-surface"); t.setBuffer(imeSurface, buffer); t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB)); - t.setRelativeLayer(imeSurface, activity.getSurfaceControl(), 1); + t.setLayer(imeSurface, 1); t.setPosition(imeSurface, mInputMethodWindow.getDisplayFrame().left, mInputMethodWindow.getDisplayFrame().top); return imeSurface; @@ -4147,9 +4146,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * which controls the visibility and animation of the input method window. */ void updateImeInputAndControlTarget(WindowState target) { - if (target != null && target.mActivityRecord != null) { - target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; - } if (mImeInputTarget != target) { ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target); setImeInputTarget(target); @@ -4157,6 +4153,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); updateImeControlTarget(); } + // Unfreeze IME insets after the new target updated, in case updateAboveInsetsState may + // deliver unrelated IME insets change to the non-IME requester. + if (target != null && target.mActivityRecord != null) { + target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; + } } void updateImeControlTarget() { @@ -5090,6 +5091,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mLastHasContent; } + @VisibleForTesting + void setLastHasContent() { + mLastHasContent = true; + } + void registerPointerEventListener(@NonNull PointerEventListener listener) { mPointerEventDispatcher.registerInputEventListener(listener); } @@ -6460,9 +6466,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp DisplayArea findAreaForWindowType(int windowType, Bundle options, boolean ownerCanManageAppToken, boolean roundedCornerOverlay) { - // TODO(b/159767464): figure out how to find an appropriate TDA. if (windowType >= FIRST_APPLICATION_WINDOW && windowType <= LAST_APPLICATION_WINDOW) { - return getDefaultTaskDisplayArea(); + return mDisplayAreaPolicy.getTaskDisplayArea(options); } // Return IME container here because it could be in one of sub RootDisplayAreas depending on // the focused edit text. Also, the RootDisplayArea choosing strategy is implemented by diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index f0e8b8f54e50..0745b3b6d971 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -18,7 +18,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -1122,6 +1121,7 @@ public class DisplayPolicy { if (!mNavButtonForcedVisible) { inOutFrame.inset(windowState.getLayoutingAttrs( displayFrames.mRotation).providedInternalInsets); + inOutFrame.inset(win.mGivenContentInsets); } }, @@ -1190,9 +1190,12 @@ public class DisplayPolicy { break; } mDisplayContent.setInsetProvider(insetsType, win, (displayFrames, - windowState, inOutFrame) -> inOutFrame.inset( - windowState.getLayoutingAttrs(displayFrames.mRotation) - .providedInternalInsets), imeFrameProvider); + windowState, inOutFrame) -> { + inOutFrame.inset( + windowState.getLayoutingAttrs(displayFrames.mRotation) + .providedInternalInsets); + inOutFrame.inset(win.mGivenContentInsets); + }, imeFrameProvider); mInsetsSourceWindowsExceptIme.add(win); } } @@ -1715,8 +1718,7 @@ public class DisplayPolicy { // and mTopIsFullscreen is that mTopIsFullscreen is set only if the window // requests to hide the status bar. Not sure if there is another way that to be the // case though. - if (!topIsFullscreen || mDisplayContent.getDefaultTaskDisplayArea() - .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { + if (!topIsFullscreen) { topAppHidesStatusBar = false; } } @@ -2423,8 +2425,7 @@ public class DisplayPolicy { private int updateSystemBarsLw(WindowState win, int disableFlags) { final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); final boolean multiWindowTaskVisible = - defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) - || defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW); + defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW); final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); @@ -2850,6 +2851,7 @@ public class DisplayPolicy { } void release() { + mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener); mHandler.post(mGestureNavigationSettingsObserver::unregister); mImmersiveModeConfirmation.release(); } diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java new file mode 100644 index 000000000000..60d2a5da9286 --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java @@ -0,0 +1,135 @@ +/* + * 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.content.pm.ActivityInfo; +import android.os.UserHandle; +import android.util.ArraySet; +import android.window.DisplayWindowPolicyController; + +import java.io.PrintWriter; +import java.util.List; + +class DisplayWindowPolicyControllerHelper { + + private final DisplayContent mDisplayContent; + + /** + * The policy controller of the windows that can be displayed on the virtual display. + * + * @see DisplayWindowPolicyController + */ + @Nullable + private DisplayWindowPolicyController mDisplayWindowPolicyController; + + /** + * The top non-finishing activity of this display. + */ + private ActivityRecord mTopRunningActivity = null; + + /** + * All the uids of non-finishing activity on this display. + * @see DisplayWindowPolicyController#onRunningAppsChanged(ArraySet) + */ + private ArraySet<Integer> mRunningUid = new ArraySet<>(); + + DisplayWindowPolicyControllerHelper(DisplayContent displayContent) { + mDisplayContent = displayContent; + mDisplayWindowPolicyController = mDisplayContent.mWmService.mDisplayManagerInternal + .getDisplayWindowPolicyController(mDisplayContent.mDisplayId); + } + + /** + * Return {@code true} if there is DisplayWindowPolicyController. + */ + public boolean hasController() { + return mDisplayWindowPolicyController != null; + } + + /** + * @see DisplayWindowPolicyController#canContainActivities(List) + */ + public boolean canContainActivities(@NonNull List<ActivityInfo> activities) { + if (mDisplayWindowPolicyController == null) { + return true; + } + return mDisplayWindowPolicyController.canContainActivities(activities); + } + + /** + * @see DisplayWindowPolicyController#keepActivityOnWindowFlagsChanged(ActivityInfo, int, int) + */ + boolean keepActivityOnWindowFlagsChanged(ActivityInfo aInfo, int flagChanges, + int privateFlagChanges) { + if (mDisplayWindowPolicyController == null) { + return true; + } + + if (!mDisplayWindowPolicyController.isInterestedWindowFlags( + flagChanges, privateFlagChanges)) { + return true; + } + + return mDisplayWindowPolicyController.keepActivityOnWindowFlagsChanged( + aInfo, flagChanges, privateFlagChanges); + } + + /** Update the top activity and the uids of non-finishing activity */ + void onRunningActivityChanged() { + if (mDisplayWindowPolicyController == null) { + return; + } + + // Update top activity. + ActivityRecord topActivity = mDisplayContent.getTopActivity(false /* includeFinishing */, + true /* includeOverlays */); + if (topActivity != mTopRunningActivity) { + mTopRunningActivity = topActivity; + mDisplayWindowPolicyController.onTopActivityChanged( + topActivity == null ? null : topActivity.info.getComponentName(), + topActivity == null + ? UserHandle.USER_NULL : topActivity.info.applicationInfo.uid); + } + + // Update running uid. + final boolean[] notifyChanged = {false}; + ArraySet<Integer> runningUids = new ArraySet<>(); + mDisplayContent.forAllActivities((r) -> { + if (!r.finishing) { + notifyChanged[0] |= runningUids.add(r.getUid()); + } + }); + + // We need to compare the size because if it is the following case, we can't know the + // existence of 3 in the forAllActivities() loop. + // Old set: 1,2,3 + // New set: 1,2 + if (notifyChanged[0] || (mRunningUid.size() != runningUids.size())) { + mRunningUid = runningUids; + mDisplayWindowPolicyController.onRunningAppsChanged(runningUids); + } + } + + void dump(String prefix, PrintWriter pw) { + if (mDisplayWindowPolicyController != null) { + pw.println(); + mDisplayWindowPolicyController.dump(prefix, pw); + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 8260fd6c09f4..483c799c5afa 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -244,6 +244,10 @@ class DisplayWindowSettings { } void applySettingsToDisplayLocked(DisplayContent dc) { + applySettingsToDisplayLocked(dc, /* includeRotationSettings */ true); + } + + void applySettingsToDisplayLocked(DisplayContent dc, boolean includeRotationSettings) { final DisplayInfo displayInfo = dc.getDisplayInfo(); final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo); @@ -282,6 +286,8 @@ class DisplayWindowSettings { boolean dontMoveToTop = settings.mDontMoveToTop != null ? settings.mDontMoveToTop : false; dc.mDontMoveToTop = dontMoveToTop; + + if (includeRotationSettings) applyRotationSettingsToDisplayLocked(dc); } void applyRotationSettingsToDisplayLocked(DisplayContent dc) { diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index fc317a1212d5..0e2d84779602 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -93,6 +93,18 @@ class EmbeddedWindowController { return embeddedWindow != null ? embeddedWindow.mHostWindowState : null; } + boolean isOverlay(IBinder inputToken) { + EmbeddedWindow embeddedWindow = mWindows.get(inputToken); + return embeddedWindow != null ? embeddedWindow.getIsOverlay() : false; + } + + void setIsOverlay(IBinder inputToken) { + EmbeddedWindow embeddedWindow = mWindows.get(inputToken); + if (embeddedWindow != null) { + embeddedWindow.setIsOverlay(); + } + } + void remove(IWindow client) { for (int i = mWindows.size() - 1; i >= 0; i--) { if (mWindows.valueAt(i).mClient.asBinder() == client.asBinder()) { @@ -138,6 +150,12 @@ class EmbeddedWindowController { public Session mSession; InputChannel mInputChannel; final int mWindowType; + // Track whether the EmbeddedWindow is a system hosted overlay via + // {@link OverlayHost}. In the case of client hosted overlays, the client + // view hierarchy will take care of invoking requestEmbeddedWindowFocus + // but for system hosted overlays we have to do this via tapOutsideDetection + // and this variable is mostly used for tracking that. + boolean mIsOverlay = false; /** * @param session calling session to check ownership of the window @@ -216,5 +234,39 @@ class EmbeddedWindowController { public int getPid() { return mOwnerPid; } + + void setIsOverlay() { + mIsOverlay = true; + } + boolean getIsOverlay() { + return mIsOverlay; + } + + /** + * System hosted overlays need the WM to invoke grantEmbeddedWindowFocus and + * so we need to participate inside handlePointerDownOutsideFocus logic + * however client hosted overlays will rely on the hosting view hierarchy + * to grant and revoke focus, and so the server side logic is not needed. + */ + @Override + public boolean receiveFocusFromTapOutside() { + return mIsOverlay; + } + + private void handleTap(boolean grantFocus) { + if (mInputChannel != null) { + mWmService.grantEmbeddedWindowFocus(mSession, mInputChannel.getToken(), grantFocus); + } + } + + @Override + public void handleTapOutsideFocusOutsideSelf() { + handleTap(false); + } + + @Override + public void handleTapOutsideFocusInsideSelf() { + handleTap(true); + } } } diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java index c85e04dbfa15..2cefd9935870 100644 --- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java @@ -256,7 +256,7 @@ public class FadeRotationAnimationController extends FadeAnimationController { false /* applyFixedTransformationHint */); for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { final SurfaceControl leash = mTargetWindowTokens.valueAt(i); - if (leash != null) { + if (leash != null && leash.isValid()) { rotator.applyTransform(t, leash); } } @@ -265,7 +265,7 @@ public class FadeRotationAnimationController extends FadeAnimationController { // Hide the windows immediately because a screenshot layer should cover the screen. for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { final SurfaceControl leash = mTargetWindowTokens.valueAt(i); - if (leash != null) { + if (leash != null && leash.isValid()) { t.setAlpha(leash, 0f); } } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 4225f214eebd..44818a8c9ee4 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -399,10 +399,11 @@ final class InputMonitor { if (recentsAnimationInputConsumer != null && focus != null) { final RecentsAnimationController recentsAnimationController = mService.getRecentsAnimationController(); + // Apply recents input consumer when the focusing window is in recents animation. final boolean shouldApplyRecentsInputConsumer = (recentsAnimationController != null && recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord)) // Shell transitions doesn't use RecentsAnimationController - || getWeak(mActiveRecentsActivity) != null; + || getWeak(mActiveRecentsActivity) != null && focus.inTransition(); if (shouldApplyRecentsInputConsumer) { requestFocus(recentsAnimationInputConsumer.mWindowHandle.token, recentsAnimationInputConsumer.mName); diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java index c7d328a2b18a..5166b8adcecc 100644 --- a/services/core/java/com/android/server/wm/InputTarget.java +++ b/services/core/java/com/android/server/wm/InputTarget.java @@ -36,5 +36,16 @@ interface InputTarget { /* Owning pid of the target. */ int getPid(); + + /** + * Indicates whether a target should receive focus from server side + * tap outside focus detection. For example, this is false in the case of + * EmbeddedWindows in a client view hierarchy, where the client will do internal + * tap detection and invoke grantEmbeddedWindowFocus itself + */ + boolean receiveFocusFromTapOutside(); + + void handleTapOutsideFocusInsideSelf(); + void handleTapOutsideFocusOutsideSelf(); } diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index cbb473c10c6d..1955e30aab30 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -175,7 +175,7 @@ final class LetterboxConfiguration { * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0, * both it and a value of {@link * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and - * and corners of the activity won't be rounded. + * corners of the activity won't be rounded. */ void setLetterboxActivityCornersRadius(int cornersRadius) { mLetterboxActivityCornersRadius = cornersRadius; diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 7d073573835f..8866343afe03 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -150,7 +150,7 @@ final class LetterboxUiController { if (mLetterbox == null) { mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null), mActivityRecord.mWmService.mTransactionFactory, - mLetterboxConfiguration::isLetterboxActivityCornersRounded, + this::shouldLetterboxHaveRoundedCorners, this::getLetterboxBackgroundColor, this::hasWallpaperBackgroudForLetterbox, this::getLetterboxWallpaperBlurRadius, @@ -175,6 +175,13 @@ final class LetterboxUiController { } } + private boolean shouldLetterboxHaveRoundedCorners() { + // TODO(b/214030873): remove once background is drawn for transparent activities + // Letterbox shouldn't have rounded corners if the activity is transparent + return mLetterboxConfiguration.isLetterboxActivityCornersRounded() + && mActivityRecord.fillsParent(); + } + float getHorizontalPositionMultiplier(Configuration parentConfiguration) { // Don't check resolved configuration because it may not be updated yet during // configuration change. @@ -257,8 +264,6 @@ final class LetterboxUiController { @VisibleForTesting boolean shouldShowLetterboxUi(WindowState mainWindow) { return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed() - // Check that an activity isn't transparent. - && mActivityRecord.fillsParent() // Check for FLAG_SHOW_WALLPAPER explicitly instead of using // WindowContainer#showWallpaper because the later will return true when this // activity is using blurred wallpaper for letterbox backgroud. diff --git a/services/core/java/com/android/server/wm/OverlayHost.java b/services/core/java/com/android/server/wm/OverlayHost.java new file mode 100644 index 000000000000..724e1247b100 --- /dev/null +++ b/services/core/java/com/android/server/wm/OverlayHost.java @@ -0,0 +1,131 @@ +/* + * 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 android.content.res.Configuration; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; + +import java.util.ArrayList; + +/** + * Utility class to assist WindowContainer in the hosting of + * SurfacePackage based overlays. Manages overlays inside + * one parent control, and manages the lifetime of that parent control + * in order to obscure details from WindowContainer. + * + * Also handles multiplexing of event dispatch and tracking of overlays + * to make things easier for WindowContainer. + */ +class OverlayHost { + // Lazily initialized when required + SurfaceControl mSurfaceControl; + final ArrayList<SurfaceControlViewHost.SurfacePackage> mOverlays = new ArrayList<>(); + final WindowManagerService mWmService; + + OverlayHost(WindowManagerService wms) { + mWmService = wms; + } + + void requireOverlaySurfaceControl() { + if (mSurfaceControl == null) { + final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null) + .setContainerLayer() + .setHidden(true) + .setName("Overlay Host Leash"); + + mSurfaceControl = b.build(); + } + } + + void setParent(SurfaceControl.Transaction t, SurfaceControl newParent) { + if (mSurfaceControl == null) { + return; + } + t.reparent(mSurfaceControl, newParent); + if (newParent != null) { + t.show(mSurfaceControl); + } else { + t.hide(mSurfaceControl); + } + } + + void setLayer(SurfaceControl.Transaction t, int layer) { + if (mSurfaceControl != null) { + t.setLayer(mSurfaceControl, layer); + } + } + + void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) { + requireOverlaySurfaceControl(); + mOverlays.add(p); + + mWmService.mEmbeddedWindowController.setIsOverlay(p.getInputToken()); + + SurfaceControl.Transaction t = mWmService.mTransactionFactory.get(); + t.reparent(p.getSurfaceControl(), mSurfaceControl) + .show(p.getSurfaceControl()); + setParent(t,currentParent); + t.apply(); + } + + boolean removeOverlay(SurfaceControlViewHost.SurfacePackage p) { + final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get(); + + for (int i = mOverlays.size() - 1; i >= 0; i--) { + SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i); + if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) { + mOverlays.remove(i); + t.reparent(l.getSurfaceControl(), null); + l.release(); + } + } + t.apply(); + return mOverlays.size() > 0; + } + + void dispatchConfigurationChanged(Configuration c) { + for (int i = mOverlays.size() - 1; i >= 0; i--) { + SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i); + try { + l.getRemoteInterface().onConfigurationChanged(c); + } catch (Exception e) { + removeOverlay(l); + } + } + } + + private void dispatchDetachedFromWindow() { + for (int i = mOverlays.size() - 1; i >= 0; i--) { + SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i); + try { + l.getRemoteInterface().onDispatchDetachedFromWindow(); + } catch (Exception e) { + // Oh well we are tearing down anyway. + } + l.release(); + } + } + + void release() { + dispatchDetachedFromWindow(); + mOverlays.clear(); + final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get(); + t.remove(mSurfaceControl).apply(); + mSurfaceControl = null; + } +} diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index dca0bbda78cf..a049d6547396 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -27,7 +27,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; @@ -1370,16 +1369,6 @@ class RecentTasks { switch (task.getWindowingMode()) { case WINDOWING_MODE_PINNED: return false; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - if (DEBUG_RECENTS_TRIM_TASKS) { - Slog.d(TAG, "\ttop=" + task.getRootTask().getTopMostTask()); - } - final Task rootTask = task.getRootTask(); - if (rootTask != null && rootTask.getTopMostTask() == task) { - // Only the non-top task of the primary split screen mode is visible - return false; - } - break; case WINDOWING_MODE_MULTI_WINDOW: // Ignore tasks that are always on top if (task.isAlwaysOnTopWhenVisible()) { diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java index f9d7b53e4e78..6ed59e96c700 100644 --- a/services/core/java/com/android/server/wm/ShellRoot.java +++ b/services/core/java/com/android/server/wm/ShellRoot.java @@ -25,14 +25,15 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMAT import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.Point; +import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; import android.view.DisplayInfo; import android.view.IWindow; import android.view.SurfaceControl; +import android.view.WindowInfo; import android.view.WindowManager; import android.view.animation.Animation; @@ -135,12 +136,47 @@ public class ShellRoot { ANIMATION_TYPE_WINDOW_ANIMATION); } - @Nullable - IBinder getAccessibilityWindowToken() { - if (mAccessibilityWindow != null) { - return mAccessibilityWindow.asBinder(); + WindowInfo getWindowInfo() { + if (mShellRootLayer != SHELL_ROOT_LAYER_DIVIDER + && mShellRootLayer != SHELL_ROOT_LAYER_PIP) { + return null; + } + if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER + && !mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()) { + return null; + } + if (mShellRootLayer == SHELL_ROOT_LAYER_PIP + && mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask() == null) { + return null; } - return null; + if (mAccessibilityWindow == null) { + return null; + } + WindowInfo windowInfo = WindowInfo.obtain(); + windowInfo.displayId = mToken.getDisplayArea().getDisplayContent().mDisplayId; + windowInfo.type = mToken.windowType; + windowInfo.layer = mToken.getWindowLayerFromType(); + windowInfo.token = mAccessibilityWindow.asBinder(); + windowInfo.focused = false; + windowInfo.hasFlagWatchOutsideTouch = false; + final Rect regionRect = new Rect(); + + + // DividerView + if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER) { + windowInfo.inPictureInPicture = false; + mDisplayContent.getDockedDividerController().getTouchRegion(regionRect); + windowInfo.regionInScreen.set(regionRect); + windowInfo.title = "Splitscreen Divider"; + } + // PipMenuView + if (mShellRootLayer == SHELL_ROOT_LAYER_PIP) { + windowInfo.inPictureInPicture = true; + mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask().getBounds(regionRect); + windowInfo.regionInScreen.set(regionRect); + windowInfo.title = "Picture-in-Picture menu"; + } + return windowInfo; } void setAccessibilityWindow(IWindow window) { @@ -161,5 +197,9 @@ public class ShellRoot { mAccessibilityWindow = null; } } + if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) { + mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved( + mDisplayContent.getDisplayId()); + } } } diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 5fe40766fb66..eb73cd807204 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -34,6 +34,7 @@ import android.content.pm.ApplicationInfo; import android.util.Slog; import android.window.TaskSnapshot; +import java.util.ArrayList; import java.util.function.Supplier; /** @@ -45,6 +46,14 @@ public class StartingSurfaceController { private final WindowManagerService mService; private final SplashScreenExceptionList mSplashScreenExceptionsList; + // Cache status while deferring add starting window + boolean mInitProcessRunning; + boolean mInitNewTask; + boolean mInitTaskSwitch; + private final ArrayList<DeferringStartingWindowRecord> mDeferringAddStartActivities = + new ArrayList<>(); + private boolean mDeferringAddStartingWindow; + public StartingSurfaceController(WindowManagerService wm) { mService = wm; mSplashScreenExceptionsList = new SplashScreenExceptionList(wm.mContext.getMainExecutor()); @@ -70,7 +79,7 @@ public class StartingSurfaceController { return mSplashScreenExceptionsList.isException(packageName, targetSdk, infoProvider); } - int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch, + static int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean useEmpty, boolean useLegacy, boolean activityDrawn) { int parameter = 0; @@ -142,6 +151,82 @@ public class StartingSurfaceController { } } + private static final class DeferringStartingWindowRecord { + final ActivityRecord mDeferring; + final ActivityRecord mPrev; + final ActivityRecord mSource; + + DeferringStartingWindowRecord(ActivityRecord deferring, ActivityRecord prev, + ActivityRecord source) { + mDeferring = deferring; + mPrev = prev; + mSource = source; + } + } + + /** + * Shows a starting window while starting a new activity. Do not use this method to create a + * starting window for an existing activity. + */ + void showStartingWindow(ActivityRecord target, ActivityRecord prev, + boolean newTask, boolean isTaskSwitch, ActivityRecord source) { + if (mDeferringAddStartingWindow) { + addDeferringRecord(target, prev, newTask, isTaskSwitch, source); + } else { + target.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */, + source); + } + } + + /** + * Queueing the starting activity status while deferring add starting window. + * @see Task#startActivityLocked + */ + private void addDeferringRecord(ActivityRecord deferring, ActivityRecord prev, + boolean newTask, boolean isTaskSwitch, ActivityRecord source) { + // Set newTask, taskSwitch, processRunning form first activity because those can change + // after first activity started. + if (mDeferringAddStartActivities.isEmpty()) { + mInitProcessRunning = deferring.isProcessRunning(); + mInitNewTask = newTask; + mInitTaskSwitch = isTaskSwitch; + } + mDeferringAddStartActivities.add(new DeferringStartingWindowRecord( + deferring, prev, source)); + } + + private void showStartingWindowFromDeferringActivities() { + // Attempt to add starting window from the top-most activity. + for (int i = mDeferringAddStartActivities.size() - 1; i >= 0; --i) { + final DeferringStartingWindowRecord next = mDeferringAddStartActivities.get(i); + next.mDeferring.showStartingWindow(next.mPrev, mInitNewTask, mInitTaskSwitch, + mInitProcessRunning, true /* startActivity */, next.mSource); + // If one succeeds, it is done. + if (next.mDeferring.mStartingData != null) { + break; + } + } + mDeferringAddStartActivities.clear(); + } + + /** + * Begin deferring add starting window in one pass. + * This is used to deferring add starting window while starting multiples activities because + * system only need to provide a starting window to the top-visible activity. + * Most call {@link #endDeferAddStartingWindow} when starting activities process finished. + * @see #endDeferAddStartingWindow() + */ + void beginDeferAddStartingWindow() { + mDeferringAddStartingWindow = true; + } + + /** + * End deferring add starting window. + */ + void endDeferAddStartingWindow() { + mDeferringAddStartingWindow = false; + showStartingWindowFromDeferringActivities(); + } final class StartingSurface { private final Task mTask; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7617726e1fcd..2331dc4dff52 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -30,7 +30,6 @@ import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; @@ -125,6 +124,8 @@ import static com.android.server.wm.TaskProto.SURFACE_HEIGHT; import static com.android.server.wm.TaskProto.SURFACE_WIDTH; import static com.android.server.wm.TaskProto.TASK_FRAGMENT; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; +import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; +import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; @@ -609,6 +610,8 @@ class Task extends TaskFragment { */ ActivityRecord mChildPipActivity; + boolean mLastSurfaceShowing = true; + private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, @@ -925,13 +928,6 @@ class Task extends TaskFragment { if (!animate) { mTaskSupervisor.mNoAnimActivities.add(topActivity); } - - if (toRootTaskWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT) { - // Move recents to front so it is not behind root home task when going into docked - // mode - mTaskSupervisor.moveRecentsRootTaskToFront(reason); - } } finally { mAtmService.continueWindowLayout(); } @@ -1749,7 +1745,7 @@ class Task extends TaskFragment { */ boolean canBeLaunchedOnDisplay(int displayId) { return mTaskSupervisor.canPlaceEntityOnDisplay(displayId, - -1 /* don't check PID */, -1 /* don't check UID */, null /* activityInfo */); + -1 /* don't check PID */, -1 /* don't check UID */, this); } /** @@ -2326,8 +2322,7 @@ class Task extends TaskFragment { final int windowingMode = getWindowingMode(); if (!isActivityTypeStandardOrUndefined() - || windowingMode == WINDOWING_MODE_FULLSCREEN - || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) { + || windowingMode == WINDOWING_MODE_FULLSCREEN) { return isResizeable() ? rootTask.getRequestedOverrideBounds() : null; } else if (!getWindowConfiguration().persistTaskBounds()) { return rootTask.getRequestedOverrideBounds(); @@ -2712,10 +2707,14 @@ class Task extends TaskFragment { } boolean isResizeable() { + return isResizeable(/* checkPictureInPictureSupport */ true); + } + + boolean isResizeable(boolean checkPictureInPictureSupport) { final boolean forceResizable = mAtmService.mForceResizableActivities && getActivityType() == ACTIVITY_TYPE_STANDARD; return forceResizable || ActivityInfo.isResizeableMode(mResizeMode) - || mSupportsPictureInPicture; + || (mSupportsPictureInPicture && checkPictureInPictureSupport); } /** @@ -3308,6 +3307,17 @@ class Task extends TaskFragment { if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { scheduleAnimation(); } + + // We intend to let organizer manage task visibility but it doesn't + // have enough information until we finish shell transitions. + // In the mean time we do an easy fix here. + final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS); + if (mSurfaceControl != null) { + if (show != mLastSurfaceShowing) { + getSyncTransaction().setVisibility(mSurfaceControl, show); + } + } + mLastSurfaceShowing = show; } @Override @@ -4623,25 +4633,7 @@ class Task extends TaskFragment { } void moveToFront(String reason, Task task) { - if (inSplitScreenSecondaryWindowingMode()) { - // If the root task is in split-screen secondary mode, we need to make sure we move the - // primary split-screen root task forward in the case it is currently behind a - // fullscreen root task so both halves of the split-screen appear on-top and the - // fullscreen root task isn't cutting between them. - // TODO(b/70677280): This is a workaround until we can fix as part of b/70677280. - final TaskDisplayArea taskDisplayArea = getDisplayArea(); - final Task topFullScreenRootTask = - taskDisplayArea.getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN); - if (topFullScreenRootTask != null) { - final Task primarySplitScreenRootTask = - taskDisplayArea.getRootSplitScreenPrimaryTask(); - if (primarySplitScreenRootTask != null - && topFullScreenRootTask.compareTo(primarySplitScreenRootTask) > 0) { - primarySplitScreenRootTask.moveToFrontInner(reason + " splitScreenToTop", - null /* task */); - } - } - } else if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) { + if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) { final Task adjacentTask = getAdjacentTaskFragment().asTask(); if (adjacentTask != null) { adjacentTask.moveToFrontInner(reason + " adjacentTaskToTop", null /* task */); @@ -5149,8 +5141,8 @@ class Task extends TaskFragment { final ActivityRecord prev = baseTask.getActivity( a -> a.mStartingData != null && a.showToCurrentUser()); - r.showStartingWindow(prev, newTask, isTaskSwitch, - true /* startActivity */, sourceRecord); + mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask, + isTaskSwitch, sourceRecord); } } else { // If this is the first activity, don't do any fancy animations, @@ -5971,7 +5963,19 @@ class Task extends TaskFragment { } void reparent(TaskDisplayArea newParent, boolean onTop) { - reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM); + if (newParent == null) { + throw new IllegalArgumentException("Task can't reparent to null " + this); + } + + if (getParent() == newParent) { + throw new IllegalArgumentException("Task=" + this + " already child of " + newParent); + } + + if (canBeLaunchedOnDisplay(newParent.getDisplayId())) { + reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM); + } else { + Slog.w(TAG, "Task=" + this + " can't reparent to " + newParent); + } } void setLastRecentsAnimationTransaction(@NonNull PictureInPictureSurfaceTransaction transaction, diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index d133ca96b45e..b681a96d2c0f 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -398,8 +398,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: " + mResumedActivity + " to:" + r + " reason:" + reason); } + final ActivityRecord prevR = mResumedActivity; mResumedActivity = r; mTaskSupervisor.updateTopResumedActivityIfNeeded(); + if (r == null && prevR.mDisplayContent != null + && prevR.mDisplayContent.getFocusedRootTask() == null) { + // Only need to notify DWPC when no activity will resume. + prevR.mDisplayContent.onRunningActivityChanged(); + } else if (r != null) { + r.mDisplayContent.onRunningActivityChanged(); + } } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index c7fdefc412cc..123ca889c73e 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -22,6 +22,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Configuration; import android.graphics.Rect; @@ -497,6 +498,23 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return null; } + private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task, + @NonNull PendingTaskFragmentEvent event) { + final TaskFragmentOrganizerState state = + mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder()); + final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment); + final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo(); + // Send an info changed callback if this event is for the last activities to finish in a + // Task so that the {@link TaskFragmentOrganizer} can delete this TaskFragment. Otherwise, + // the Task may be removed before it becomes visible again to send this event because it no + // longer has activities. As a result, the organizer will never get this info changed event + // and will not delete the TaskFragment because the organizer thinks the TaskFragment still + // has running activities. + return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED + && task.topRunningActivity() == null && lastInfo != null + && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0; + } + void dispatchPendingEvents() { if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred() || mPendingTaskFragmentEvents.isEmpty()) { @@ -510,7 +528,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i); final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null; if (task != null && (task.lastActiveTime <= event.mDeferTime - || !isTaskVisible(task, visibleTasks, invisibleTasks))) { + || !(isTaskVisible(task, visibleTasks, invisibleTasks) + || shouldSendEventWhenTaskInvisible(task, event)))) { // Defer sending events to the TaskFragment until the host task is active again. event.mDeferTime = task.lastActiveTime; continue; diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 364246e1134e..348cfb62582e 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -40,18 +40,17 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; -import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.Trace; import android.util.DisplayMetrics; import android.util.Slog; import android.view.BatchedInputEventReceiver; -import android.view.Choreographer; import android.view.InputApplicationHandle; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputEventReceiver; import android.view.InputWindowHandle; import android.view.MotionEvent; import android.view.WindowManager; @@ -73,7 +72,7 @@ class TaskPositioner implements IBinder.DeathRecipient { public static final int RESIZING_HINT_DURATION_MS = 0; private final WindowManagerService mService; - private WindowPositionerEventReceiver mInputEventReceiver; + private InputEventReceiver mInputEventReceiver; private DisplayContent mDisplayContent; private Rect mTmpRect = new Rect(); private int mMinVisibleWidth; @@ -100,103 +99,91 @@ class TaskPositioner implements IBinder.DeathRecipient { InputApplicationHandle mDragApplicationHandle; InputWindowHandle mDragWindowHandle; - private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver { - public WindowPositionerEventReceiver( - InputChannel inputChannel, Looper looper, Choreographer choreographer) { - super(inputChannel, looper, choreographer); + /** Use {@link #create(WindowManagerService)} instead. */ + @VisibleForTesting + TaskPositioner(WindowManagerService service) { + mService = service; + } + + private boolean onInputEvent(InputEvent event) { + // All returns need to be in the try block to make sure the finishInputEvent is + // called correctly. + if (!(event instanceof MotionEvent) + || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { + return false; } + final MotionEvent motionEvent = (MotionEvent) event; + if (mDragEnded) { + // The drag has ended but the clean-up message has not been processed by + // window manager. Drop events that occur after this until window manager + // has a chance to clean-up the input handle. + return true; + } + + final float newX = motionEvent.getRawX(); + final float newY = motionEvent.getRawY(); + + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); + } + } + break; - @Override - public void onInputEvent(InputEvent event) { - boolean handled = false; - try { - // All returns need to be in the try block to make sure the finishInputEvent is - // called correctly. - if (!(event instanceof MotionEvent) - || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { - return; + case MotionEvent.ACTION_MOVE: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); + } + synchronized (mService.mGlobalLock) { + mDragEnded = notifyMoveLocked(newX, newY); + mTask.getDimBounds(mTmpRect); } - final MotionEvent motionEvent = (MotionEvent) event; - if (mDragEnded) { - // The drag has ended but the clean-up message has not been processed by - // window manager. Drop events that occur after this until window manager - // has a chance to clean-up the input handle. - handled = true; - return; + if (!mTmpRect.equals(mWindowDragBounds)) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, + "wm.TaskPositioner.resizeTask"); + mService.mAtmService.resizeTask( + mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } + } + break; - final float newX = motionEvent.getRawX(); - final float newY = motionEvent.getRawY(); - - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); - } - } break; - - case MotionEvent.ACTION_MOVE: { - if (DEBUG_TASK_POSITIONING){ - Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); - } - synchronized (mService.mGlobalLock) { - mDragEnded = notifyMoveLocked(newX, newY); - mTask.getDimBounds(mTmpRect); - } - if (!mTmpRect.equals(mWindowDragBounds)) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, - "wm.TaskPositioner.resizeTask"); - mService.mAtmService.resizeTask( - mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } break; - - case MotionEvent.ACTION_UP: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); - } - mDragEnded = true; - } break; - - case MotionEvent.ACTION_CANCEL: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); - } - mDragEnded = true; - } break; + case MotionEvent.ACTION_UP: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); } + mDragEnded = true; + } + break; - if (mDragEnded) { - final boolean wasResizing = mResizing; - synchronized (mService.mGlobalLock) { - endDragLocked(); - mTask.getDimBounds(mTmpRect); - } - if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) { - // We were using fullscreen surface during resizing. Request - // resizeTask() one last time to restore surface to window size. - mService.mAtmService.resizeTask( - mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); - } - - // Post back to WM to handle clean-ups. We still need the input - // event handler for the last finishInputEvent()! - mService.mTaskPositioningController.finishTaskPositioning(); + case MotionEvent.ACTION_CANCEL: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); } - handled = true; - } catch (Exception e) { - Slog.e(TAG, "Exception caught by drag handleMotion", e); - } finally { - finishInputEvent(event, handled); + mDragEnded = true; } + break; } - } - /** Use {@link #create(WindowManagerService)} instead. */ - @VisibleForTesting - TaskPositioner(WindowManagerService service) { - mService = service; + if (mDragEnded) { + final boolean wasResizing = mResizing; + synchronized (mService.mGlobalLock) { + endDragLocked(); + mTask.getDimBounds(mTmpRect); + } + if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) { + // We were using fullscreen surface during resizing. Request + // resizeTask() one last time to restore surface to window size. + mService.mAtmService.resizeTask( + mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); + } + + // Post back to WM to handle clean-ups. We still need the input + // event handler for the last finishInputEvent()! + mService.mTaskPositioningController.finishTaskPositioning(); + } + return true; } @VisibleForTesting @@ -221,9 +208,9 @@ class TaskPositioner implements IBinder.DeathRecipient { mDisplayContent = displayContent; mClientChannel = mService.mInputManager.createInputChannel(TAG); - mInputEventReceiver = new WindowPositionerEventReceiver( + mInputEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver( mClientChannel, mService.mAnimationHandler.getLooper(), - mService.mAnimator.getChoreographer()); + mService.mAnimator.getChoreographer(), this::onInputEvent); mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG, DEFAULT_DISPATCHING_TIMEOUT_MILLIS); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index a4771082b3e9..b13c9a9e3e14 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -79,6 +79,7 @@ import android.window.RemoteTransition; import android.window.TransitionInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; @@ -1271,6 +1272,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe change.setAllowEnterPip(topMostActivity != null && topMostActivity.checkEnterPictureInPictureAppOpsState()); } + final ActivityRecord activityRecord = target.asActivityRecord(); + if (activityRecord != null) { + final Task arTask = activityRecord.getTask(); + final int backgroundColor = ColorUtils.setAlphaComponent( + arTask.getTaskDescription().getBackgroundColor(), 255); + change.setBackgroundColor(backgroundColor); + } + out.addChange(change); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index ffe146219c6c..fe968ec2e57b 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -462,6 +462,10 @@ class TransitionController { mLegacyListeners.add(listener); } + void unregisterLegacyListener(WindowManagerInternal.AppTransitionListener listener) { + mLegacyListeners.remove(listener); + } + void dispatchLegacyAppTransitionPending() { for (int i = 0; i < mLegacyListeners.size(); ++i) { mLegacyListeners.get(i).onAppTransitionPendingLocked(); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 61acb97c9db1..400684840467 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -85,6 +85,7 @@ import android.view.MagnificationSpec; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceControl.Builder; import android.view.SurfaceSession; import android.view.TaskTransitionSpec; @@ -312,6 +313,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< private final List<WindowContainerListener> mListeners = new ArrayList<>(); + private OverlayHost mOverlayHost; + WindowContainer(WindowManagerService wms) { mWmService = wms; mTransitionController = mWmService.mAtmService.getTransitionController(); @@ -341,6 +344,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< super.onConfigurationChanged(newParentConfig); updateSurfacePositionNonOrganized(); scheduleAnimation(); + if (mOverlayHost != null) { + mOverlayHost.dispatchConfigurationChanged(getConfiguration()); + } } void reparent(WindowContainer newParent, int position) { @@ -387,6 +393,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (mParent != null) { mParent.onChildAdded(this); + } else if (mSurfaceAnimator.hasLeash()) { + mSurfaceAnimator.cancelAnimation(); } if (!mReparenting) { onSyncReparent(oldParent, mParent); @@ -487,6 +495,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< t.reparent(sc, mSurfaceControl); } } + + if (mOverlayHost != null) { + mOverlayHost.setParent(t, mSurfaceControl); + } + scheduleAnimation(); } @@ -632,6 +645,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mLastSurfacePosition.set(0, 0); scheduleAnimation(); } + if (mOverlayHost != null) { + mOverlayHost.release(); + mOverlayHost = null; + } // This must happen after updating the surface so that sync transactions can be handled // properly. @@ -2308,6 +2325,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< wc.assignLayer(t, layer++); } } + if (mOverlayHost != null) { + mOverlayHost.setLayer(t, layer++); + } } void assignChildLayers() { @@ -3570,4 +3590,18 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type, @Nullable AnimationAdapter snapshotAnim); } + + void addOverlay(SurfaceControlViewHost.SurfacePackage overlay) { + if (mOverlayHost == null) { + mOverlayHost = new OverlayHost(mWmService); + } + mOverlayHost.addOverlay(overlay, mSurfaceControl); + } + + void removeOverlay(SurfaceControlViewHost.SurfacePackage overlay) { + if (mOverlayHost != null && !mOverlayHost.removeOverlay(overlay)) { + mOverlayHost.release(); + mOverlayHost = null; + } + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 62c674b15f89..1ab191bb6650 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -32,6 +32,7 @@ import android.view.IWindow; import android.view.InputChannel; import android.view.MagnificationSpec; import android.view.RemoteAnimationTarget; +import android.view.SurfaceControlViewHost; import android.view.WindowInfo; import android.view.WindowManager.DisplayImePolicy; @@ -767,4 +768,16 @@ public abstract class WindowManagerInternal { * {@code false} otherwise. */ public abstract boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken); + + /** + * Internal methods for other parts of SystemServer to manage + * SurfacePackage based overlays on tasks. + * + * Callers prepare a view hierarchy with SurfaceControlViewHost + * and send the package to WM here. The remote view hierarchy will receive + * configuration change, lifecycle events, etc, forwarded over the + * ISurfaceControlViewHost interface inside the SurfacePackage. + */ + public abstract void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay); + public abstract void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b5e6f49146f1..2f0ef4a8ee1b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -55,6 +55,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; @@ -266,6 +267,7 @@ import android.view.RemoteAnimationAdapter; import android.view.ScrollCaptureResponse; import android.view.Surface; import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceSession; import android.view.TaskTransitionSpec; import android.view.View; @@ -736,6 +738,8 @@ public class WindowManagerService extends IWindowManager.Stub final WindowContextListenerController mWindowContextListenerController = new WindowContextListenerController(); + private InputTarget mFocusedInputTarget; + @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mDisplayInversionEnabledUri = @@ -1746,7 +1750,10 @@ public class WindowManagerService extends IWindowManager.Stub activity.attachStartingWindow(win); ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s", activity, win); - } else if (type == TYPE_INPUT_METHOD) { + } else if (type == TYPE_INPUT_METHOD + // IME window is always touchable. + // Ignore non-touchable windows e.g. Stylus InkWindow.java. + && (win.getAttrs().flags & FLAG_NOT_TOUCHABLE) == 0) { displayContent.setInputMethodWindowLocked(win); imMayMove = false; } else if (type == TYPE_INPUT_METHOD_DIALOG) { @@ -2241,6 +2248,15 @@ public class WindowManagerService extends IWindowManager.Stub winAnimator.setColorSpaceAgnosticLocked((win.mAttrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0); } + if (win.mActivityRecord != null + && !displayContent.mDwpcHelper.keepActivityOnWindowFlagsChanged( + win.mActivityRecord.info, flagChanges, privateFlagChanges)) { + mH.sendMessage(mH.obtainMessage(H.REPARENT_TASK_TO_DEFAULT_DISPLAY, + win.mActivityRecord.getTask())); + Slog.w(TAG_WM, "Activity " + win.mActivityRecord + " window flag changed," + + " can't remain on display " + displayContent.getDisplayId()); + return 0; + } } if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility @@ -4997,6 +5013,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.v(TAG_WM, "Unknown focus tokens, dropping reportFocusChanged"); return; } + mFocusedInputTarget = newTarget; mAccessibilityController.onFocusChanged(lastTarget, newTarget); ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastTarget, newTarget); @@ -5060,6 +5077,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62; public static final int LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED = 63; public static final int WINDOW_STATE_BLAST_SYNC_TIMEOUT = 64; + public static final int REPARENT_TASK_TO_DEFAULT_DISPLAY = 65; /** * Used to denote that an integer field in a message will not be used. @@ -5377,6 +5395,15 @@ public class WindowManagerService extends IWindowManager.Stub } break; } + case REPARENT_TASK_TO_DEFAULT_DISPLAY: { + synchronized (mGlobalLock) { + Task task = (Task) msg.obj; + task.reparent(mRoot.getDefaultTaskDisplayArea(), true /* onTop */); + // Resume focusable root task after reparenting to another display area. + task.resumeNextFocusAfterReparent(); + } + break; + } } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG_WM, "handleMessage: exit"); @@ -7887,6 +7914,28 @@ public class WindowManagerService extends IWindowManager.Stub public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken); } + + @Override + public void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay) { + synchronized (mGlobalLock) { + final Task task = mRoot.getRootTask(taskId); + if (task == null) { + throw new IllegalArgumentException("no task with taskId" + taskId); + } + task.addOverlay(overlay); + } + } + + @Override + public void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay) { + synchronized (mGlobalLock) { + final Task task = mRoot.getRootTask(taskId); + if (task == null) { + throw new IllegalArgumentException("no task with taskId" + taskId); + } + task.removeOverlay(overlay); + } + } } void registerAppFreezeListener(AppFreezeListener listener) { @@ -8124,21 +8173,14 @@ public class WindowManagerService extends IWindowManager.Stub } private void onPointerDownOutsideFocusLocked(IBinder touchedToken) { - WindowState touchedWindow = mInputToWindowMap.get(touchedToken); - if (touchedWindow == null) { - // if a user taps outside the currently focused window onto an embedded window, treat - // it as if the host window was tapped. - touchedWindow = mEmbeddedWindowController.getHostWindow(touchedToken); - } - - if (touchedWindow == null || !touchedWindow.canReceiveKeys(true /* fromUserTouch */)) { + InputTarget t = getInputTargetFromToken(touchedToken); + if (t == null || !t.receiveFocusFromTapOutside()) { // If the window that received the input event cannot receive keys, don't move the // display it's on to the top since that window won't be able to get focus anyway. return; } - if (mRecentsAnimationController != null - && mRecentsAnimationController.getTargetAppMainWindow() == touchedWindow) { + && mRecentsAnimationController.getTargetAppMainWindow() == t) { // If there is an active recents animation and touched window is the target, then ignore // the touch. The target already handles touches using its own input monitor and we // don't want to trigger any lifecycle changes from focusing another window. @@ -8148,13 +8190,11 @@ public class WindowManagerService extends IWindowManager.Stub } ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "onPointerDownOutsideFocusLocked called on %s", - touchedWindow); - final DisplayContent displayContent = touchedWindow.getDisplayContent(); - if (!displayContent.isOnTop()) { - displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent, - true /* includingParents */); + t); + if (mFocusedInputTarget != t && mFocusedInputTarget != null) { + mFocusedInputTarget.handleTapOutsideFocusOutsideSelf(); } - handleTaskFocusChange(touchedWindow.getTask(), touchedWindow.mActivityRecord); + t.handleTapOutsideFocusInsideSelf(); } @VisibleForTesting @@ -8255,7 +8295,7 @@ public class WindowManagerService extends IWindowManager.Stub flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid); - final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE + final int sanitizedFlags = flags & (FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE); h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags; h.layoutParamsType = type; @@ -8731,4 +8771,5 @@ public class WindowManagerService extends IWindowManager.Stub mTaskTransitionSpec = null; } + } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 79dcbcb23870..455856ce22cd 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -139,6 +139,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub void setWindowManager(WindowManagerService wms) { mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController); + mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); } TransitionController getTransitionController() { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 5bbe2cd853f6..e4d3e05c09ff 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4809,6 +4809,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (isAnimating()) { return; } + if (mWmService.mAccessibilityController.hasCallbacks()) { + mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId()); + } if (!isSelfOrAncestorWindowAnimatingExit()) { return; @@ -5982,4 +5985,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean isTrustedOverlay() { return mInputWindowHandle.isTrustedOverlay(); } + + public boolean receiveFocusFromTapOutside() { + return canReceiveKeys(true); + } + + @Override + public void handleTapOutsideFocusOutsideSelf() { + // Nothing to do here since raising the other window will naturally take care of + // us loosing focus + } + + @Override + public void handleTapOutsideFocusInsideSelf() { + final DisplayContent displayContent = getDisplayContent(); + if (!displayContent.isOnTop()) { + displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent, + true /* includingParents */); + } + mWmService.handleTaskFocusChange(getTask(), mActivityRecord); + } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 7b4fd365905c..b6438832399a 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -71,11 +71,14 @@ cc_library_static { "onload.cpp", ":lib_cachedAppOptimizer_native", ":lib_networkStatsFactory_native", + ":lib_gameManagerService_native", ], include_dirs: [ "frameworks/base/libs", "frameworks/native/services", + "frameworks/native/libs/math/include", + "frameworks/native/libs/ui/include", "system/gatekeeper/include", "system/memory/libmeminfo/include", ], @@ -103,6 +106,7 @@ cc_defaults { "libcrypto", "liblog", "libgraphicsenv", + "libgralloctypes", "libhardware", "libhardware_legacy", "libhidlbase", @@ -157,6 +161,10 @@ cc_defaults { "android.hardware.gnss.measurement_corrections@1.0", "android.hardware.gnss.visibility_control@1.0", "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + "android.hardware.graphics.common@1.2", + "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.mapper@4.0", "android.hardware.input.classifier@1.0", "android.hardware.ir@1.0", "android.hardware.light@2.0", @@ -216,3 +224,10 @@ filegroup { "com_android_server_am_CachedAppOptimizer.cpp", ], } + +filegroup { + name: "lib_gameManagerService_native", + srcs: [ + "com_android_server_app_GameManagerService.cpp", + ], +} diff --git a/services/core/jni/com_android_server_app_GameManagerService.cpp b/services/core/jni/com_android_server_app_GameManagerService.cpp new file mode 100644 index 000000000000..3028813d0d5a --- /dev/null +++ b/services/core/jni/com_android_server_app_GameManagerService.cpp @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#define LOG_TAG "GameManagerService" + +#include <android/log.h> +#include <gui/SurfaceComposerClient.h> +#include <log/log.h> +#include <nativehelper/JNIHelp.h> + +#include "jni.h" + +namespace android { + +static void android_server_app_GameManagerService_nativeSetOverrideFrameRate(JNIEnv* env, + jclass clazz, jint uid, + jfloat frameRate) { + SurfaceComposerClient::setOverrideFrameRate(uid, frameRate); +} + +static const JNINativeMethod gMethods[] = { + {"nativeSetOverrideFrameRate", "(IF)V", + (void*)android_server_app_GameManagerService_nativeSetOverrideFrameRate}, +}; + +int register_android_server_app_GameManagerService(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/app/GameManagerService", gMethods, + NELEM(gMethods)); +} + +}; // namespace android
\ No newline at end of file diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index be656e3f3a27..0da8f7ef0dea 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -51,6 +51,7 @@ #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" #include "gnss/AGnss.h" +#include "gnss/AGnssRil.h" #include "gnss/GnssAntennaInfoCallback.h" #include "gnss/GnssBatching.h" #include "gnss/GnssConfiguration.h" @@ -76,8 +77,6 @@ static jmethodID method_setGnssHardwareModelName; static jmethodID method_psdsDownloadRequest; static jmethodID method_reportNiNotification; static jmethodID method_requestLocation; -static jmethodID method_requestRefLocation; -static jmethodID method_requestSetID; static jmethodID method_requestUtcTime; static jmethodID method_reportGnssServiceDied; static jmethodID method_reportGnssPowerStats; @@ -126,7 +125,6 @@ using android::hardware::hidl_string; using android::hardware::hidl_death_recipient; using android::hardware::gnss::V1_0::GnssLocationFlags; -using android::hardware::gnss::V1_0::IAGnssRilCallback; using android::hardware::gnss::V1_0::IGnssNavigationMessage; using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback; using android::hardware::gnss::V1_0::IGnssNi; @@ -158,8 +156,6 @@ using IGnssCallback_V1_0 = android::hardware::gnss::V1_0::IGnssCallback; using IGnssCallback_V2_0 = android::hardware::gnss::V2_0::IGnssCallback; using IGnssCallback_V2_1 = android::hardware::gnss::V2_1::IGnssCallback; using IGnssAntennaInfo = android::hardware::gnss::V2_1::IGnssAntennaInfo; -using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil; -using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil; using IMeasurementCorrections_V1_0 = android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections; using IMeasurementCorrections_V1_1 = android::hardware::gnss::measurement_corrections::V1_1::IMeasurementCorrections; @@ -175,7 +171,9 @@ using android::hardware::gnss::GnssPowerStats; using android::hardware::gnss::IGnssPowerIndication; using android::hardware::gnss::IGnssPowerIndicationCallback; using android::hardware::gnss::PsdsType; + using IAGnssAidl = android::hardware::gnss::IAGnss; +using IAGnssRilAidl = android::hardware::gnss::IAGnssRil; using IGnssAidl = android::hardware::gnss::IGnss; using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback; using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching; @@ -208,8 +206,6 @@ sp<IGnssAidl> gnssHalAidl = nullptr; sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr; sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr; sp<IGnssXtra> gnssXtraIface = nullptr; -sp<IAGnssRil_V1_0> agnssRilIface = nullptr; -sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr; sp<IGnssNi> gnssNiIface = nullptr; sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr; sp<IMeasurementCorrections_V1_0> gnssCorrectionsIface_V1_0 = nullptr; @@ -224,6 +220,7 @@ std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullpt std::unique_ptr<android::gnss::GnssGeofenceInterface> gnssGeofencingIface = nullptr; std::unique_ptr<android::gnss::AGnssInterface> agnssIface = nullptr; std::unique_ptr<android::gnss::GnssDebugInterface> gnssDebugIface = nullptr; +std::unique_ptr<android::gnss::AGnssRilInterface> agnssRilIface = nullptr; #define WAKE_LOCK_NAME "GPS" @@ -909,29 +906,6 @@ Return<bool> GnssVisibilityControlCallback::isInEmergencySession() { return result; } -/* - * AGnssRilCallback implements the callback methods required by the AGnssRil - * interface. - */ -struct AGnssRilCallback : IAGnssRilCallback { - Return<void> requestSetIdCb(uint32_t setIdFlag) override; - Return<void> requestRefLocCb() override; -}; - -Return<void> AGnssRilCallback::requestSetIdCb(uint32_t setIdFlag) { - JNIEnv* env = getJniEnv(); - env->CallVoidMethod(mCallbacksObj, method_requestSetID, setIdFlag); - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); -} - -Return<void> AGnssRilCallback::requestRefLocCb() { - JNIEnv* env = getJniEnv(); - env->CallVoidMethod(mCallbacksObj, method_requestRefLocation); - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); -} - /* Initializes the GNSS service handle. */ static void android_location_gnss_hal_GnssNative_set_gps_service_handle() { gnssHalAidl = waitForVintfService<IGnssAidl>(); @@ -990,8 +964,6 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;II)V"); method_requestLocation = env->GetMethodID(clazz, "requestLocation", "(ZZ)V"); - method_requestRefLocation = env->GetMethodID(clazz, "requestRefLocation", "()V"); - method_requestSetID = env->GetMethodID(clazz, "requestSetID", "(I)V"); method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V"); method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V"); method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification", @@ -1069,6 +1041,7 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc gnss::GnssMeasurement_class_init_once(env, clazz); gnss::GnssNavigationMessage_class_init_once(env, clazz); gnss::AGnss_class_init_once(env, clazz); + gnss::AGnssRil_class_init_once(env, clazz); gnss::Utils_class_init_once(env); } @@ -1124,20 +1097,21 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - if (gnssHal_V2_0 != nullptr) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + sp<IAGnssRilAidl> agnssRilAidl; + auto status = gnssHalAidl->getExtensionAGnssRil(&agnssRilAidl); + if (checkAidlStatus(status, "Unable to get a handle to AGnssRil interface.")) { + agnssRilIface = std::make_unique<gnss::AGnssRil>(agnssRilAidl); + } + } else if (gnssHal_V2_0 != nullptr) { auto agnssRil_V2_0 = gnssHal_V2_0->getExtensionAGnssRil_2_0(); - if (!agnssRil_V2_0.isOk()) { - ALOGD("Unable to get a handle to AGnssRil_V2_0"); - } else { - agnssRilIface_V2_0 = agnssRil_V2_0; - agnssRilIface = agnssRilIface_V2_0; + if (checkHidlReturn(agnssRil_V2_0, "Unable to get a handle to AGnssRil_V2_0")) { + agnssRilIface = std::make_unique<gnss::AGnssRil_V2_0>(agnssRil_V2_0); } } else if (gnssHal != nullptr) { auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil(); - if (!agnssRil_V1_0.isOk()) { - ALOGD("Unable to get a handle to AGnssRil"); - } else { - agnssRilIface = agnssRil_V1_0; + if (checkHidlReturn(agnssRil_V1_0, "Unable to get a handle to AGnssRil_V1_0")) { + agnssRilIface = std::make_unique<gnss::AGnssRil_V1_0>(agnssRil_V1_0); } } @@ -1472,12 +1446,9 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl ALOGI("Unable to initialize IGnssNi interface."); } - // Set IAGnssRil.hal callback. - sp<IAGnssRilCallback> aGnssRilCbIface = new AGnssRilCallback(); - if (agnssRilIface != nullptr) { - auto status = agnssRilIface->setCallback(aGnssRilCbIface); - checkHidlReturn(status, "IAGnssRil setCallback() failed."); - } else { + // Set IAGnssRil callback. + if (agnssRilIface == nullptr || + !agnssRilIface->setCallback(std::make_unique<gnss::AGnssRilCallback>())) { ALOGI("Unable to initialize IAGnssRil interface."); } @@ -1605,31 +1576,13 @@ static void android_location_gnss_hal_GnssNative_delete_aiding_data(JNIEnv* /* e } static void android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid( - JNIEnv* /* env */, jclass, jint type, jint mcc, jint mnc, jint lac, jint cid) { - IAGnssRil_V1_0::AGnssRefLocation location; - + JNIEnv* /* env */, jclass, jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, + jint pcid, jint arfcn) { if (agnssRilIface == nullptr) { ALOGE("%s: IAGnssRil interface not available.", __func__); return; } - - switch (static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type)) { - case IAGnssRil_V1_0::AGnssRefLocationType::GSM_CELLID: - case IAGnssRil_V1_0::AGnssRefLocationType::UMTS_CELLID: - location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type); - location.cellID.mcc = mcc; - location.cellID.mnc = mnc; - location.cellID.lac = lac; - location.cellID.cid = cid; - break; - default: - ALOGE("Neither a GSM nor a UMTS cellid (%s:%d).", __FUNCTION__, __LINE__); - return; - break; - } - - auto result = agnssRilIface->setRefLocation(location); - checkHidlReturn(result, "IAGnssRil setRefLocation() failed."); + agnssRilIface->setRefLocation(type, mcc, mnc, lac, cid, tac, pcid, arfcn); } static void android_location_gnss_hal_GnssNative_agps_set_id(JNIEnv* env, jclass, jint type, @@ -1638,10 +1591,7 @@ static void android_location_gnss_hal_GnssNative_agps_set_id(JNIEnv* env, jclass ALOGE("%s: IAGnssRil interface not available.", __func__); return; } - - ScopedJniString jniSetId{env, setid_string}; - auto result = agnssRilIface->setSetId((IAGnssRil_V1_0::SetIDType)type, jniSetId); - checkHidlReturn(result, "IAGnssRil setSetId() failed."); + agnssRilIface->setSetId(type, setid_string); } static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass, @@ -1878,31 +1828,12 @@ static void android_location_GnssNetworkConnectivityHandler_update_network_state jstring apn, jlong networkHandle, jshort capabilities) { - if (agnssRilIface_V2_0 != nullptr) { - ScopedJniString jniApn{env, apn}; - IAGnssRil_V2_0::NetworkAttributes networkAttributes = { - .networkHandle = static_cast<uint64_t>(networkHandle), - .isConnected = static_cast<bool>(connected), - .capabilities = static_cast<uint16_t>(capabilities), - .apn = jniApn - }; - - auto result = agnssRilIface_V2_0->updateNetworkState_2_0(networkAttributes); - checkHidlReturn(result, "IAGnssRil updateNetworkState_2_0() failed."); - } else if (agnssRilIface != nullptr) { - ScopedJniString jniApn{env, apn}; - hidl_string hidlApn{jniApn}; - auto result = agnssRilIface->updateNetworkState(connected, - static_cast<IAGnssRil_V1_0::NetworkType>(type), roaming); - checkHidlReturn(result, "IAGnssRil updateNetworkState() failed."); - - if (!hidlApn.empty()) { - result = agnssRilIface->updateNetworkAvailability(available, hidlApn); - checkHidlReturn(result, "IAGnssRil updateNetworkAvailability() failed."); - } - } else { + if (agnssRilIface == nullptr) { ALOGE("%s: IAGnssRil interface not available.", __func__); + return; } + agnssRilIface->updateNetworkState(connected, type, roaming, available, apn, networkHandle, + capabilities); } static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */, @@ -2342,11 +2273,12 @@ static void android_location_gnss_hal_GnssNative_cleanup_batching(JNIEnv*, jclas } static jboolean android_location_gnss_hal_GnssNative_start_batch(JNIEnv*, jclass, jlong periodNanos, + jfloat minUpdateDistanceMeters, jboolean wakeOnFifoFull) { if (gnssBatchingIface == nullptr) { return JNI_FALSE; // batching not supported } - return gnssBatchingIface->start(periodNanos, wakeOnFifoFull); + return gnssBatchingIface->start(periodNanos, minUpdateDistanceMeters, wakeOnFifoFull); } static void android_location_gnss_hal_GnssNative_flush_batch(JNIEnv*, jclass) { @@ -2418,7 +2350,7 @@ static const JNINativeMethod sLocationProviderMethods[] = { reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_psds_data)}, {"native_agps_set_id", "(ILjava/lang/String;)V", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_agps_set_id)}, - {"native_agps_set_ref_location_cellid", "(IIIII)V", + {"native_agps_set_ref_location_cellid", "(IIIIJIII)V", reinterpret_cast<void*>( android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid)}, {"native_set_agps_server", "(ILjava/lang/String;I)V", @@ -2436,7 +2368,7 @@ static const JNINativeMethod sBatchingMethods[] = { /* name, signature, funcPtr */ {"native_get_batch_size", "()I", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_get_batch_size)}, - {"native_start_batch", "(JZ)Z", + {"native_start_batch", "(JFZ)Z", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_start_batch)}, {"native_flush_batch", "()V", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_flush_batch)}, diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp new file mode 100644 index 000000000000..d760b4d2195e --- /dev/null +++ b/services/core/jni/gnss/AGnssRil.cpp @@ -0,0 +1,172 @@ +/* + * 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. + */ + +// Define LOG_TAG before <log/log.h> to overwrite the default value. +#define LOG_TAG "AGnssRilJni" + +#include "AGnssRil.h" + +#include "Utils.h" + +using android::hardware::gnss::IAGnssRil; +using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil; +using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil; + +namespace android::gnss { + +// Implementation of AGnssRil (AIDL HAL) + +AGnssRil::AGnssRil(const sp<IAGnssRil>& iAGnssRil) : mIAGnssRil(iAGnssRil) { + assert(mIAGnssRil != nullptr); +} + +jboolean AGnssRil::setCallback(const std::unique_ptr<AGnssRilCallback>& callback) { + auto status = mIAGnssRil->setCallback(callback->getAidl()); + return checkAidlStatus(status, "IAGnssRilAidl setCallback() failed."); +} + +jboolean AGnssRil::setSetId(jint type, const jstring& setid_string) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniSetId{env, setid_string}; + auto status = mIAGnssRil->setSetId((IAGnssRil::SetIDType)type, jniSetId.c_str()); + return checkAidlStatus(status, "IAGnssRilAidl setSetId() failed."); +} + +jboolean AGnssRil::setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, + jint pcid, jint arfcn) { + IAGnssRil::AGnssRefLocation location; + location.type = static_cast<IAGnssRil::AGnssRefLocationType>(type); + + switch (location.type) { + case IAGnssRil::AGnssRefLocationType::GSM_CELLID: + case IAGnssRil::AGnssRefLocationType::UMTS_CELLID: + case IAGnssRil::AGnssRefLocationType::LTE_CELLID: + case IAGnssRil::AGnssRefLocationType::NR_CELLID: + location.cellID.mcc = mcc; + location.cellID.mnc = mnc; + location.cellID.lac = lac; + location.cellID.cid = cid; + location.cellID.tac = tac; + location.cellID.pcid = pcid; + location.cellID.arfcn = arfcn; + break; + default: + ALOGE("Unknown cellid (%s:%d).", __FUNCTION__, __LINE__); + return JNI_FALSE; + break; + } + + auto status = mIAGnssRil->setRefLocation(location); + return checkAidlStatus(status, "IAGnssRilAidl dataConnClosed() failed."); +} + +jboolean AGnssRil::updateNetworkState(jboolean connected, jint type, jboolean roaming, + jboolean available, const jstring& apn, jlong networkHandle, + jshort capabilities) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniApn{env, apn}; + IAGnssRil::NetworkAttributes networkAttributes; + networkAttributes.networkHandle = static_cast<int64_t>(networkHandle), + networkAttributes.isConnected = static_cast<bool>(connected), + networkAttributes.capabilities = static_cast<int32_t>(capabilities), + networkAttributes.apn = jniApn.c_str(); + + auto result = mIAGnssRil->updateNetworkState(networkAttributes); + return checkAidlStatus(result, "IAGnssRilAidl updateNetworkState() failed."); +} + +// Implementation of AGnssRil_V1_0 + +AGnssRil_V1_0::AGnssRil_V1_0(const sp<IAGnssRil_V1_0>& iAGnssRil) : mAGnssRil_V1_0(iAGnssRil) { + assert(mIAGnssRil_V1_0 != nullptr); +} + +jboolean AGnssRil_V1_0::setCallback(const std::unique_ptr<AGnssRilCallback>& callback) { + auto result = mAGnssRil_V1_0->setCallback(callback->getV1_0()); + return checkHidlReturn(result, "IAGnssRil_V1_0 setCallback() failed."); +} + +jboolean AGnssRil_V1_0::setSetId(jint type, const jstring& setid_string) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniSetId{env, setid_string}; + auto result = mAGnssRil_V1_0->setSetId((IAGnssRil_V1_0::SetIDType)type, jniSetId); + return checkHidlReturn(result, "IAGnssRil_V1_0 setSetId() failed."); +} + +jboolean AGnssRil_V1_0::setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint, + jint, jint) { + IAGnssRil_V1_0::AGnssRefLocation location; + switch (static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type)) { + case IAGnssRil_V1_0::AGnssRefLocationType::GSM_CELLID: + case IAGnssRil_V1_0::AGnssRefLocationType::UMTS_CELLID: + location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type); + location.cellID.mcc = mcc; + location.cellID.mnc = mnc; + location.cellID.lac = lac; + location.cellID.cid = cid; + break; + default: + ALOGE("Neither a GSM nor a UMTS cellid (%s:%d).", __FUNCTION__, __LINE__); + return JNI_FALSE; + break; + } + + auto result = mAGnssRil_V1_0->setRefLocation(location); + return checkHidlReturn(result, "IAGnssRil_V1_0 setRefLocation() failed."); +} + +jboolean AGnssRil_V1_0::updateNetworkState(jboolean connected, jint type, jboolean roaming, + jboolean available, const jstring& apn, + jlong networkHandle, jshort capabilities) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniApn{env, apn}; + hardware::hidl_string hidlApn{jniApn}; + hardware::Return<bool> result(false); + + if (!hidlApn.empty()) { + result = mAGnssRil_V1_0->updateNetworkAvailability(available, hidlApn); + checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkAvailability() failed."); + } + + result = mAGnssRil_V1_0->updateNetworkState(connected, + static_cast<IAGnssRil_V1_0::NetworkType>(type), + roaming); + return checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkState() failed."); +} + +// Implementation of AGnssRil_V2_0 + +AGnssRil_V2_0::AGnssRil_V2_0(const sp<IAGnssRil_V2_0>& iAGnssRil) + : AGnssRil_V1_0{iAGnssRil}, mAGnssRil_V2_0(iAGnssRil) { + assert(mIAGnssRil_V2_0 != nullptr); +} + +jboolean AGnssRil_V2_0::updateNetworkState(jboolean connected, jint type, jboolean roaming, + jboolean available, const jstring& apn, + jlong networkHandle, jshort capabilities) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniApn{env, apn}; + IAGnssRil_V2_0::NetworkAttributes networkAttributes = + {.networkHandle = static_cast<uint64_t>(networkHandle), + .isConnected = static_cast<bool>(connected), + .capabilities = static_cast<uint16_t>(capabilities), + .apn = jniApn.c_str()}; + + auto result = mAGnssRil_V2_0->updateNetworkState_2_0(networkAttributes); + return checkHidlReturn(result, "AGnssRil_V2_0 updateNetworkState_2_0() failed."); +} + +} // namespace android::gnss diff --git a/services/core/jni/gnss/AGnssRil.h b/services/core/jni/gnss/AGnssRil.h new file mode 100644 index 000000000000..ce14a77d56c4 --- /dev/null +++ b/services/core/jni/gnss/AGnssRil.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +#ifndef _ANDROID_SERVER_GNSS_AGNSSRIL_H +#define _ANDROID_SERVER_GNSS_AGNSSRIL_H + +#pragma once + +#ifndef LOG_TAG +#error LOG_TAG must be defined before including this file. +#endif + +#include <android/hardware/gnss/1.0/IAGnssRil.h> +#include <android/hardware/gnss/2.0/IAGnssRil.h> +#include <android/hardware/gnss/BnAGnssRil.h> +#include <log/log.h> + +#include "AGnssRilCallback.h" +#include "jni.h" + +namespace android::gnss { + +class AGnssRilInterface { +public: + virtual ~AGnssRilInterface() {} + virtual jboolean setCallback(const std::unique_ptr<AGnssRilCallback>& callback) = 0; + virtual jboolean setSetId(jint type, const jstring& setid_string) = 0; + virtual jboolean setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, + jint pcid, jint arfcn) = 0; + virtual jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, + jboolean available, const jstring& apn, jlong networkHandle, + jshort capabilities) = 0; +}; + +class AGnssRil : public AGnssRilInterface { +public: + AGnssRil(const sp<android::hardware::gnss::IAGnssRil>& iAGnssRil); + jboolean setCallback(const std::unique_ptr<AGnssRilCallback>& callback) override; + jboolean setSetId(jint type, const jstring& setid_string) override; + jboolean setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, jint pcid, + jint arfcn) override; + jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available, + const jstring& apn, jlong networkHandle, + jshort capabilities) override; + +private: + const sp<android::hardware::gnss::IAGnssRil> mIAGnssRil; +}; + +class AGnssRil_V1_0 : public AGnssRilInterface { +public: + AGnssRil_V1_0(const sp<android::hardware::gnss::V1_0::IAGnssRil>& iAGnssRil); + jboolean setCallback(const std::unique_ptr<AGnssRilCallback>& callback) override; + jboolean setSetId(jint type, const jstring& setid_string) override; + jboolean setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint, jint, + jint) override; + jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available, + const jstring& apn, jlong networkHandle, + jshort capabilities) override; + +private: + const sp<android::hardware::gnss::V1_0::IAGnssRil> mAGnssRil_V1_0; +}; + +class AGnssRil_V2_0 : public AGnssRil_V1_0 { +public: + AGnssRil_V2_0(const sp<android::hardware::gnss::V2_0::IAGnssRil>& iAGnssRil); + jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available, + const jstring& apn, jlong networkHandle, + jshort capabilities) override; + +private: + const sp<android::hardware::gnss::V2_0::IAGnssRil> mAGnssRil_V2_0; +}; + +} // namespace android::gnss + +#endif // _ANDROID_SERVER_GNSS_AGNSSRIL_H diff --git a/services/core/jni/gnss/AGnssRilCallback.cpp b/services/core/jni/gnss/AGnssRilCallback.cpp new file mode 100644 index 000000000000..b63ccc281aa9 --- /dev/null +++ b/services/core/jni/gnss/AGnssRilCallback.cpp @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#define LOG_TAG "AGnssRilCbJni" + +#include "AGnssRilCallback.h" + +namespace android::gnss { + +jmethodID method_requestSetID; +jmethodID method_requestRefLocation; + +using binder::Status; +using hardware::Return; +using hardware::Void; + +void AGnssRil_class_init_once(JNIEnv* env, jclass clazz) { + method_requestSetID = env->GetMethodID(clazz, "requestSetID", "(I)V"); + method_requestRefLocation = env->GetMethodID(clazz, "requestRefLocation", "()V"); +} + +Status AGnssRilCallbackAidl::requestSetIdCb(int setIdflag) { + AGnssRilCallbackUtil::requestSetIdCb(setIdflag); + return Status::ok(); +} + +Status AGnssRilCallbackAidl::requestRefLocCb() { + AGnssRilCallbackUtil::requestRefLocCb(); + return Status::ok(); +} + +Return<void> AGnssRilCallback_V1_0::requestSetIdCb(uint32_t setIdflag) { + AGnssRilCallbackUtil::requestSetIdCb(setIdflag); + return Void(); +} + +Return<void> AGnssRilCallback_V1_0::requestRefLocCb() { + AGnssRilCallbackUtil::requestRefLocCb(); + return Void(); +} + +void AGnssRilCallbackUtil::requestSetIdCb(int setIdflag) { + ALOGD("%s. setIdflag: %d, ", __func__, setIdflag); + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_requestSetID, setIdflag); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +void AGnssRilCallbackUtil::requestRefLocCb() { + ALOGD("%s.", __func__); + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_requestRefLocation); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +} // namespace android::gnss diff --git a/services/core/jni/gnss/AGnssRilCallback.h b/services/core/jni/gnss/AGnssRilCallback.h new file mode 100644 index 000000000000..2d12089fd6e3 --- /dev/null +++ b/services/core/jni/gnss/AGnssRilCallback.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifndef _ANDROID_SERVER_GNSS_AGNSSRILCALLBACK_H +#define _ANDROID_SERVER_GNSS_AGNSSRILCALLBACK_H + +#pragma once + +#ifndef LOG_TAG +#error LOG_TAG must be defined before including this file. +#endif + +#include <android/hardware/gnss/1.0/IAGnssRil.h> +#include <android/hardware/gnss/BnAGnssRilCallback.h> +#include <log/log.h> + +#include "Utils.h" +#include "jni.h" + +namespace android::gnss { + +void AGnssRil_class_init_once(JNIEnv* env, jclass clazz); + +/* + * AGnssRilCallbackAidl class implements the callback methods required by the + * android::hardware::gnss::IAGnssRil interface. + */ +class AGnssRilCallbackAidl : public android::hardware::gnss::BnAGnssRilCallback { +public: + binder::Status requestSetIdCb(int setIdflag) override; + binder::Status requestRefLocCb() override; +}; + +/* + * AGnssRilCallback_V1_0 implements callback methods required by the IAGnssRilCallback 1.0 + * interface. + */ +class AGnssRilCallback_V1_0 : public android::hardware::gnss::V1_0::IAGnssRilCallback { +public: + // Methods from ::android::hardware::gps::V1_0::IAGnssRilCallback follow. + hardware::Return<void> requestSetIdCb(uint32_t setIdflag) override; + hardware::Return<void> requestRefLocCb() override; +}; + +class AGnssRilCallback { +public: + AGnssRilCallback() {} + sp<AGnssRilCallbackAidl> getAidl() { + if (callbackAidl == nullptr) { + callbackAidl = sp<AGnssRilCallbackAidl>::make(); + } + return callbackAidl; + } + + sp<AGnssRilCallback_V1_0> getV1_0() { + if (callbackV1_0 == nullptr) { + callbackV1_0 = sp<AGnssRilCallback_V1_0>::make(); + } + return callbackV1_0; + } + +private: + sp<AGnssRilCallbackAidl> callbackAidl; + sp<AGnssRilCallback_V1_0> callbackV1_0; +}; + +struct AGnssRilCallbackUtil { + static void requestSetIdCb(int setIdflag); + static void requestRefLocCb(); + +private: + AGnssRilCallbackUtil() = delete; +}; + +} // namespace android::gnss + +#endif // _ANDROID_SERVER_GNSS_AGNSSRILCALLBACK_H
\ No newline at end of file diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp index 63f5f526db17..d8de5a604b3d 100644 --- a/services/core/jni/gnss/Android.bp +++ b/services/core/jni/gnss/Android.bp @@ -25,6 +25,8 @@ cc_library_shared { srcs: [ "AGnss.cpp", "AGnssCallback.cpp", + "AGnssRil.cpp", + "AGnssRilCallback.cpp", "GnssAntennaInfoCallback.cpp", "GnssBatching.cpp", "GnssBatchingCallback.cpp", diff --git a/services/core/jni/gnss/GnssBatching.cpp b/services/core/jni/gnss/GnssBatching.cpp index b66bf21381c7..7f936b9a510d 100644 --- a/services/core/jni/gnss/GnssBatching.cpp +++ b/services/core/jni/gnss/GnssBatching.cpp @@ -47,9 +47,12 @@ jint GnssBatching::getBatchSize() { return size; } -jboolean GnssBatching::start(long periodNanos, bool wakeOnFifoFull) { - int flags = (wakeOnFifoFull) ? IGnssBatching::WAKEUP_ON_FIFO_FULL : 0; - auto status = mIGnssBatching->start(periodNanos, flags); +jboolean GnssBatching::start(long periodNanos, float minUpdateDistanceMeters, bool wakeOnFifoFull) { + IGnssBatching::Options options; + options.flags = (wakeOnFifoFull) ? IGnssBatching::WAKEUP_ON_FIFO_FULL : 0; + options.periodNanos = periodNanos; + options.minDistanceMeters = minUpdateDistanceMeters; + auto status = mIGnssBatching->start(options); return checkAidlStatus(status, "IGnssBatchingAidl start() failed."); } @@ -88,9 +91,13 @@ jint GnssBatching_V1_0::getBatchSize() { return static_cast<jint>(result); } -jboolean GnssBatching_V1_0::start(long periodNanos, bool wakeOnFifoFull) { +jboolean GnssBatching_V1_0::start(long periodNanos, float minUpdateDistanceMeters, + bool wakeOnFifoFull) { IGnssBatching_V1_0::Options options; options.periodNanos = periodNanos; + if (minUpdateDistanceMeters > 0) { + ALOGW("minUpdateDistanceMeters is not supported in 1.0 GNSS HAL."); + } if (wakeOnFifoFull) { options.flags = static_cast<uint8_t>(IGnssBatching_V1_0::Flag::WAKEUP_ON_FIFO_FULL); } else { diff --git a/services/core/jni/gnss/GnssBatching.h b/services/core/jni/gnss/GnssBatching.h index a98ca9b0e492..eda02ce39551 100644 --- a/services/core/jni/gnss/GnssBatching.h +++ b/services/core/jni/gnss/GnssBatching.h @@ -38,7 +38,8 @@ public: virtual ~GnssBatchingInterface() {} virtual jboolean init(const std::unique_ptr<GnssBatchingCallback>& callback) = 0; virtual jint getBatchSize() = 0; - virtual jboolean start(long periodNanos, bool wakeupOnFifoFull) = 0; + virtual jboolean start(long periodNanos, float minUpdateDistanceMeters, + bool wakeupOnFifoFull) = 0; virtual jboolean stop() = 0; virtual jboolean flush() = 0; virtual jboolean cleanup() = 0; @@ -49,7 +50,7 @@ public: GnssBatching(const sp<android::hardware::gnss::IGnssBatching>& iGnssBatching); jboolean init(const std::unique_ptr<GnssBatchingCallback>& callback) override; jint getBatchSize() override; - jboolean start(long periodNanos, bool wakeupOnFifoFull) override; + jboolean start(long periodNanos, float minUpdateDistanceMeters, bool wakeupOnFifoFull) override; jboolean stop() override; jboolean flush() override; jboolean cleanup() override; @@ -63,7 +64,7 @@ public: GnssBatching_V1_0(const sp<android::hardware::gnss::V1_0::IGnssBatching>& iGnssBatching); jboolean init(const std::unique_ptr<GnssBatchingCallback>& callback) override; jint getBatchSize() override; - jboolean start(long periodNanos, bool wakeupOnFifoFull) override; + jboolean start(long periodNanos, float minUpdateDistanceMeters, bool wakeupOnFifoFull) override; jboolean stop() override; jboolean flush() override; jboolean cleanup() override; diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp index 34ae4690e0ea..fbdeec6b897e 100644 --- a/services/core/jni/gnss/GnssMeasurementCallback.cpp +++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp @@ -27,12 +27,16 @@ using hardware::gnss::GnssClock; using hardware::gnss::GnssData; using hardware::gnss::GnssMeasurement; using hardware::gnss::SatellitePvt; +using GnssAgc = hardware::gnss::GnssData::GnssAgc; namespace { jclass class_arrayList; jclass class_clockInfo; jclass class_correlationVectorBuilder; +jclass class_gnssAgc; +jclass class_gnssAgcBuilder; jclass class_gnssMeasurementsEvent; +jclass class_gnssMeasurementsEventBuilder; jclass class_gnssMeasurement; jclass class_gnssClock; jclass class_positionEcef; @@ -47,7 +51,16 @@ jmethodID method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond; jmethodID method_correlationVectorBuilderSetMagnitude; jmethodID method_correlationVectorBuilderSetSamplingStartMeters; jmethodID method_correlationVectorBuilderSetSamplingWidthMeters; -jmethodID method_gnssMeasurementsEventCtor; +jmethodID method_gnssAgcBuilderCtor; +jmethodID method_gnssAgcBuilderSetLevelDb; +jmethodID method_gnssAgcBuilderSetConstellationType; +jmethodID method_gnssAgcBuilderSetCarrierFrequencyHz; +jmethodID method_gnssAgcBuilderBuild; +jmethodID method_gnssMeasurementsEventBuilderCtor; +jmethodID method_gnssMeasurementsEventBuilderSetClock; +jmethodID method_gnssMeasurementsEventBuilderSetMeasurements; +jmethodID method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls; +jmethodID method_gnssMeasurementsEventBuilderBuild; jmethodID method_gnssMeasurementsSetCorrelationVectors; jmethodID method_gnssMeasurementsSetSatellitePvt; jmethodID method_gnssClockCtor; @@ -69,12 +82,55 @@ jmethodID method_clockInfo; void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz) { method_reportMeasurementData = env->GetMethodID(clazz, "reportMeasurementData", "(Landroid/location/GnssMeasurementsEvent;)V"); + + // Initialize GnssMeasurement related classes and methods jclass gnssMeasurementsEventClass = env->FindClass("android/location/GnssMeasurementsEvent"); class_gnssMeasurementsEvent = (jclass)env->NewGlobalRef(gnssMeasurementsEventClass); - method_gnssMeasurementsEventCtor = - env->GetMethodID(class_gnssMeasurementsEvent, "<init>", - "(Landroid/location/GnssClock;[Landroid/location/GnssMeasurement;)V"); - + jclass gnssMeasurementsEventBuilderClass = + env->FindClass("android/location/GnssMeasurementsEvent$Builder"); + class_gnssMeasurementsEventBuilder = + (jclass)env->NewGlobalRef(gnssMeasurementsEventBuilderClass); + method_gnssMeasurementsEventBuilderCtor = + env->GetMethodID(class_gnssMeasurementsEventBuilder, "<init>", "()V"); + method_gnssMeasurementsEventBuilderSetClock = + env->GetMethodID(class_gnssMeasurementsEventBuilder, "setClock", + "(Landroid/location/GnssClock;)" + "Landroid/location/GnssMeasurementsEvent$Builder;"); + method_gnssMeasurementsEventBuilderSetMeasurements = + env->GetMethodID(class_gnssMeasurementsEventBuilder, "setMeasurements", + "([Landroid/location/GnssMeasurement;)" + "Landroid/location/GnssMeasurementsEvent$Builder;"); + method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls = + env->GetMethodID(class_gnssMeasurementsEventBuilder, "setGnssAutomaticGainControls", + "([Landroid/location/GnssAutomaticGainControl;)" + "Landroid/location/GnssMeasurementsEvent$Builder;"); + method_gnssMeasurementsEventBuilderBuild = + env->GetMethodID(class_gnssMeasurementsEventBuilder, "build", + "()Landroid/location/GnssMeasurementsEvent;"); + + // Initialize GnssAgc related classes and methods + jclass gnssAgcClass = env->FindClass("android/location/GnssAutomaticGainControl"); + class_gnssAgc = (jclass)env->NewGlobalRef(gnssAgcClass); + jclass gnssAgcBuilderClass = + env->FindClass("android/location/GnssAutomaticGainControl$Builder"); + class_gnssAgcBuilder = (jclass)env->NewGlobalRef(gnssAgcBuilderClass); + method_gnssAgcBuilderCtor = env->GetMethodID(class_gnssAgcBuilder, "<init>", "()V"); + method_gnssAgcBuilderSetLevelDb = + env->GetMethodID(class_gnssAgcBuilder, "setLevelDb", + "(D)" + "Landroid/location/GnssAutomaticGainControl$Builder;"); + method_gnssAgcBuilderSetConstellationType = + env->GetMethodID(class_gnssAgcBuilder, "setConstellationType", + "(I)" + "Landroid/location/GnssAutomaticGainControl$Builder;"); + method_gnssAgcBuilderSetCarrierFrequencyHz = + env->GetMethodID(class_gnssAgcBuilder, "setCarrierFrequencyHz", + "(J)" + "Landroid/location/GnssAutomaticGainControl$Builder;"); + method_gnssAgcBuilderBuild = env->GetMethodID(class_gnssAgcBuilder, "build", + "()Landroid/location/GnssAutomaticGainControl;"); + + // Initialize GnssMeasurement related classes and methods jclass gnssMeasurementClass = env->FindClass("android/location/GnssMeasurement"); class_gnssMeasurement = (jclass)env->NewGlobalRef(gnssMeasurementClass); method_gnssMeasurementCtor = env->GetMethodID(class_gnssMeasurement, "<init>", "()V"); @@ -152,14 +208,25 @@ void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz) { } void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock, - jobjectArray measurementArray) { - jobject gnssMeasurementsEvent = - env->NewObject(class_gnssMeasurementsEvent, method_gnssMeasurementsEventCtor, clock, - measurementArray); - - env->CallVoidMethod(callbacksObj, method_reportMeasurementData, gnssMeasurementsEvent); + jobjectArray measurementArray, jobjectArray gnssAgcArray) { + jobject gnssMeasurementsEventBuilderObject = + env->NewObject(class_gnssMeasurementsEventBuilder, + method_gnssMeasurementsEventBuilderCtor); + env->CallObjectMethod(gnssMeasurementsEventBuilderObject, + method_gnssMeasurementsEventBuilderSetClock, clock); + env->CallObjectMethod(gnssMeasurementsEventBuilderObject, + method_gnssMeasurementsEventBuilderSetMeasurements, measurementArray); + env->CallObjectMethod(gnssMeasurementsEventBuilderObject, + method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls, + gnssAgcArray); + jobject gnssMeasurementsEventObject = + env->CallObjectMethod(gnssMeasurementsEventBuilderObject, + method_gnssMeasurementsEventBuilderBuild); + + env->CallVoidMethod(callbacksObj, method_reportMeasurementData, gnssMeasurementsEventObject); checkAndClearExceptionFromCallback(env, __FUNCTION__); - env->DeleteLocalRef(gnssMeasurementsEvent); + env->DeleteLocalRef(gnssMeasurementsEventBuilderObject); + env->DeleteLocalRef(gnssMeasurementsEventObject); } template <class T_Measurement, class T_Flags> @@ -289,12 +356,17 @@ void GnssMeasurementCallbackAidl::translateAndSetGnssData(const GnssData& data) JavaObject gnssClockJavaObject(env, class_gnssClock, method_gnssClockCtor); translateGnssClock(env, data, gnssClockJavaObject); jobject clock = gnssClockJavaObject.get(); - jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements); - setMeasurementData(env, mCallbacksObj, clock, measurementArray); + + jobjectArray gnssAgcArray = nullptr; + if (data.gnssAgcs.has_value()) { + gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value()); + } + setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray); env->DeleteLocalRef(clock); env->DeleteLocalRef(measurementArray); + env->DeleteLocalRef(gnssAgcArray); } void GnssMeasurementCallbackAidl::translateSingleGnssMeasurement(JNIEnv* env, @@ -436,6 +508,38 @@ jobjectArray GnssMeasurementCallbackAidl::translateAllGnssMeasurements( return gnssMeasurementArray; } +jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs( + JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) { + if (agcs.size() == 0) { + return nullptr; + } + + jobjectArray gnssAgcArray = + env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */); + + for (uint16_t i = 0; i < agcs.size(); ++i) { + if (!agcs[i].has_value()) { + continue; + } + const GnssAgc& gnssAgc = agcs[i].value(); + + jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor); + env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb, + gnssAgc.agcLevelDb); + env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetConstellationType, + (int)gnssAgc.constellation); + env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetCarrierFrequencyHz, + gnssAgc.carrierFrequencyHz); + jobject agcObject = env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderBuild); + + env->SetObjectArrayElement(gnssAgcArray, i, agcObject); + env->DeleteLocalRef(agcBuilderObject); + env->DeleteLocalRef(agcObject); + } + + return gnssAgcArray; +} + void GnssMeasurementCallbackAidl::translateGnssClock(JNIEnv* env, const GnssData& data, JavaObject& object) { setElapsedRealtimeFields<ElapsedRealtime, ElapsedRealtime>(data.elapsedRealtime, object); diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h index 32200fdf904e..9b346312db38 100644 --- a/services/core/jni/gnss/GnssMeasurementCallback.h +++ b/services/core/jni/gnss/GnssMeasurementCallback.h @@ -48,7 +48,7 @@ extern jmethodID method_reportMeasurementData; void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz); void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock, - jobjectArray measurementArray); + jobjectArray measurementArray, jobjectArray gnssAgcArray); class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback { public: @@ -62,6 +62,8 @@ private: jobjectArray translateAllGnssMeasurements( JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements); + jobjectArray translateAllGnssAgcs( + JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs); void translateAndSetGnssData(const hardware::gnss::GnssData& data); @@ -139,7 +141,7 @@ void GnssMeasurementCallbackHidl::translateAndSetGnssData(const T& data) { size_t count = getMeasurementCount(data); jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements.data(), count); - setMeasurementData(env, mCallbacksObj, clock, measurementArray); + setMeasurementData(env, mCallbacksObj, clock, measurementArray, nullptr); env->DeleteLocalRef(clock); env->DeleteLocalRef(measurementArray); diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index d339ef1154c5..80d7055735c3 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -64,6 +64,7 @@ int register_android_server_GpuService(JNIEnv* env); int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env); int register_android_server_companion_virtual_InputController(JNIEnv* env); +int register_android_server_app_GameManagerService(JNIEnv* env); }; using namespace android; @@ -121,5 +122,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_stats_pull_StatsPullAtomService(env); register_android_server_sensor_SensorService(vm, env); register_android_server_companion_virtual_InputController(env); + register_android_server_app_GameManagerService(env); return JNI_VERSION_1_4; } diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 2f4dd57ab15b..baf2ede07fa3 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -58,6 +58,15 @@ <xs:element type="sensorDetails" name="proxSensor"> <xs:annotation name="final"/> </xs:element> + + <!-- Length of the ambient light horizon used to calculate the long & short term + estimates of ambient light in milliseconds.--> + <xs:element type="xs:nonNegativeInteger" name="ambientLightHorizonLong"> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="xs:nonNegativeInteger" name="ambientLightHorizonShort"> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> </xs:element> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 5b2b87c3f14e..6f97431b4873 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -18,6 +18,8 @@ package com.android.server.display.config { public class DisplayConfiguration { ctor public DisplayConfiguration(); + method public final java.math.BigInteger getAmbientLightHorizonLong(); + method public final java.math.BigInteger getAmbientLightHorizonShort(); method @Nullable public final com.android.server.display.config.DensityMap getDensityMap(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.SensorDetails getLightSensor(); @@ -29,6 +31,8 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); + method public final void setAmbientLightHorizonLong(java.math.BigInteger); + method public final void setAmbientLightHorizonShort(java.math.BigInteger); method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setLightSensor(com.android.server.display.config.SensorDetails); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 55ab8c3b1af6..ebe9f9327032 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -17,16 +17,20 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.IDevicePolicyManager; import android.app.admin.ManagedProfileProvisioningParams; +import android.app.admin.ParcelableResource; import android.content.ComponentName; import android.os.UserHandle; import android.util.Slog; import com.android.server.SystemService; +import java.util.List; + /** * Defines the required interface for IDevicePolicyManager implemenation. * @@ -161,4 +165,16 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public boolean isKeyPairGrantedToWifiAuth(String callerPackage, String alias) { return false; } + + @Override + public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables){} + + @Override + public void resetDrawables(@NonNull int[] drawableIds){} + + @Override + public ParcelableResource getDrawable( + int drawableId, int drawableStyle, int drawableSource) { + return null; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java new file mode 100644 index 000000000000..534229402888 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java @@ -0,0 +1,417 @@ +/* + * 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.devicepolicy; + +import static android.app.admin.DevicePolicyResources.Drawable.Source.UPDATABLE_DRAWABLE_SOURCES; +import static android.app.admin.DevicePolicyResources.Drawable.Style; +import static android.app.admin.DevicePolicyResources.Drawable.Style.UPDATABLE_DRAWABLE_STYLES; +import static android.app.admin.DevicePolicyResources.Drawable.UPDATABLE_DRAWABLE_IDS; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.admin.DevicePolicyDrawableResource; +import android.app.admin.DevicePolicyResources; +import android.app.admin.ParcelableResource; +import android.os.Environment; +import android.util.AtomicFile; +import android.util.Log; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A helper class for {@link DevicePolicyManagerService} to store/retrieve updated device + * management resources. + */ +class DeviceManagementResourcesProvider { + private static final String TAG = "DevicePolicyManagerService"; + + private static final String UPDATED_RESOURCES_XML = "updated_resources.xml"; + private static final String TAG_ROOT = "root"; + private static final String TAG_DRAWABLE_STYLE_ENTRY = "drawable-style-entry"; + private static final String TAG_DRAWABLE_SOURCE_ENTRY = "drawable-source-entry"; + private static final String ATTR_DRAWABLE_STYLE_SIZE = "drawable-style-size"; + private static final String ATTR_DRAWABLE_SOURCE_SIZE = "drawable-source-size"; + private static final String ATTR_DRAWABLE_STYLE = "drawable-style"; + private static final String ATTR_DRAWABLE_SOURCE = "drawable-source"; + private static final String ATTR_DRAWABLE_ID = "drawable-id"; + + + private final Map<Integer, Map<Integer, ParcelableResource>> + mUpdatedDrawablesForStyle = new HashMap<>(); + + private final Map<Integer, Map<Integer, ParcelableResource>> + mUpdatedDrawablesForSource = new HashMap<>(); + + private final Object mLock = new Object(); + private final Injector mInjector; + + DeviceManagementResourcesProvider() { + this(new Injector()); + } + + DeviceManagementResourcesProvider(Injector injector) { + mInjector = requireNonNull(injector); + } + + /** + * Returns {@code false} if no resources were updated. + */ + boolean updateDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) { + boolean updated = false; + for (int i = 0; i < drawables.size(); i++) { + int drawableId = drawables.get(i).getDrawableId(); + int drawableStyle = drawables.get(i).getDrawableStyle(); + int drawableSource = drawables.get(i).getDrawableSource(); + ParcelableResource resource = drawables.get(i).getResource(); + + Objects.requireNonNull(resource, "ParcelableResource must be provided."); + + if (drawableSource == DevicePolicyResources.Drawable.Source.UNDEFINED) { + updated |= updateDrawable(drawableId, drawableStyle, resource); + } else { + updated |= updateDrawableForSource(drawableId, drawableSource, resource); + } + } + if (!updated) { + return false; + } + synchronized (mLock) { + write(); + return true; + } + } + + private boolean updateDrawable( + int drawableId, int drawableStyle, ParcelableResource updatableResource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + throw new IllegalArgumentException( + "Can't update drawable resource, invalid drawable " + "id " + drawableId); + } + if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { + throw new IllegalArgumentException( + "Can't update drawable resource, invalid style id " + drawableStyle); + } + synchronized (mLock) { + if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { + mUpdatedDrawablesForStyle.put(drawableId, new HashMap<>()); + } + ParcelableResource current = mUpdatedDrawablesForStyle.get(drawableId).get( + drawableStyle); + if (updatableResource.equals(current)) { + return false; + } + mUpdatedDrawablesForStyle.get(drawableId).put(drawableStyle, updatableResource); + return true; + } + } + + // TODO(b/214576716): change this to respect style + private boolean updateDrawableForSource( + int drawableId, int drawableSource, ParcelableResource updatableResource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + throw new IllegalArgumentException("Can't update drawable resource, invalid drawable " + + "id " + drawableId); + } + if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { + throw new IllegalArgumentException("Can't update drawable resource, invalid source id " + + drawableSource); + } + synchronized (mLock) { + if (!mUpdatedDrawablesForSource.containsKey(drawableId)) { + mUpdatedDrawablesForSource.put(drawableId, new HashMap<>()); + } + ParcelableResource current = mUpdatedDrawablesForSource.get(drawableId).get( + drawableSource); + if (updatableResource.equals(current)) { + return false; + } + mUpdatedDrawablesForSource.get(drawableId).put(drawableSource, updatableResource); + return true; + } + } + + /** + * Returns {@code false} if no resources were removed. + */ + boolean removeDrawables(@NonNull int[] drawableIds) { + synchronized (mLock) { + boolean removed = false; + for (int i = 0; i < drawableIds.length; i++) { + int drawableId = drawableIds[i]; + removed |= mUpdatedDrawablesForStyle.remove(drawableId) != null + || mUpdatedDrawablesForSource.remove(drawableId) != null; + } + if (!removed) { + return false; + } + write(); + return true; + } + } + + @Nullable + ParcelableResource getDrawable( + int drawableId, int drawableStyle, int drawableSource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + Log.e(TAG, "Can't get updated drawable resource, invalid drawable id " + + drawableId); + return null; + } + if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { + Log.e(TAG, "Can't get updated drawable resource, invalid style id " + + drawableStyle); + return null; + } + if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { + Log.e(TAG, "Can't get updated drawable resource, invalid source id " + + drawableSource); + return null; + } + if (mUpdatedDrawablesForSource.containsKey(drawableId) + && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) { + return mUpdatedDrawablesForSource.get(drawableId).get(drawableSource); + } + if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { + Log.d(TAG, "No updated drawable found for drawable id " + drawableId); + return null; + } + if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(drawableStyle)) { + return mUpdatedDrawablesForStyle.get(drawableId).get(drawableStyle); + } + + if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(Style.DEFAULT)) { + return mUpdatedDrawablesForStyle.get(drawableId).get(Style.DEFAULT); + } + Log.d(TAG, "No updated drawable found for drawable id " + drawableId); + return null; + } + + private void write() { + Log.d(TAG, "Writing updated resources to file."); + new ResourcesReaderWriter().writeToFileLocked(); + } + + void load() { + synchronized (mLock) { + new ResourcesReaderWriter().readFromFileLocked(); + } + } + + private File getResourcesFile() { + return new File(mInjector.environmentGetDataSystemDirectory(), UPDATED_RESOURCES_XML); + } + + private class ResourcesReaderWriter { + private final File mFile; + private ResourcesReaderWriter() { + mFile = getResourcesFile(); + } + + void writeToFileLocked() { + Log.d(TAG, "Writing to " + mFile); + + AtomicFile f = new AtomicFile(mFile); + FileOutputStream outputStream = null; + try { + outputStream = f.startWrite(); + TypedXmlSerializer out = Xml.resolveSerializer(outputStream); + + // Root tag + out.startDocument(null, true); + out.startTag(null, TAG_ROOT); + + // Actual content + writeInner(out); + + // Close root + out.endTag(null, TAG_ROOT); + out.endDocument(); + out.flush(); + + // Commit the content. + f.finishWrite(outputStream); + outputStream = null; + + } catch (IOException e) { + Log.e(TAG, "Exception when writing", e); + if (outputStream != null) { + f.failWrite(outputStream); + } + } + } + + void readFromFileLocked() { + if (!mFile.exists()) { + Log.d(TAG, "" + mFile + " doesn't exist"); + return; + } + + Log.d(TAG, "Reading from " + mFile); + AtomicFile f = new AtomicFile(mFile); + InputStream input = null; + try { + input = f.openRead(); + TypedXmlPullParser parser = Xml.resolvePullParser(input); + + int type; + int depth = 0; + while ((type = parser.next()) != TypedXmlPullParser.END_DOCUMENT) { + switch (type) { + case TypedXmlPullParser.START_TAG: + depth++; + break; + case TypedXmlPullParser.END_TAG: + depth--; + // fallthrough + default: + continue; + } + // Check the root tag + String tag = parser.getName(); + if (depth == 1) { + if (!TAG_ROOT.equals(tag)) { + Log.e(TAG, "Invalid root tag: " + tag); + return; + } + continue; + } + // readInner() will only see START_TAG at depth >= 2. + if (!readInner(parser, depth, tag)) { + return; // Error + } + } + } catch (XmlPullParserException | IOException e) { + Log.e(TAG, "Error parsing resources file", e); + } finally { + IoUtils.closeQuietly(input); + } + } + + void writeInner(TypedXmlSerializer out) throws IOException { + if (mUpdatedDrawablesForStyle != null && !mUpdatedDrawablesForStyle.isEmpty()) { + for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry + : mUpdatedDrawablesForStyle.entrySet()) { + out.startTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY); + out.attributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey()); + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_STYLE_SIZE, + drawableEntry.getValue().size()); + int counter = 0; + for (Map.Entry<Integer, ParcelableResource> styleEntry + : drawableEntry.getValue().entrySet()) { + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_STYLE + (counter++), + styleEntry.getKey()); + styleEntry.getValue().writeToXmlFile(out); + } + out.endTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY); + } + } + if (mUpdatedDrawablesForSource != null && !mUpdatedDrawablesForSource.isEmpty()) { + for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry + : mUpdatedDrawablesForSource.entrySet()) { + out.startTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY); + out.attributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey()); + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_SOURCE_SIZE, + drawableEntry.getValue().size()); + int counter = 0; + for (Map.Entry<Integer, ParcelableResource> sourceEntry + : drawableEntry.getValue().entrySet()) { + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_SOURCE + (counter++), + sourceEntry.getKey()); + sourceEntry.getValue().writeToXmlFile(out); + } + out.endTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY); + } + } + } + + private boolean readInner( + TypedXmlPullParser parser, int depth, String tag) + throws XmlPullParserException, IOException { + if (depth > 2) { + return true; // Ignore + } + switch (tag) { + case TAG_DRAWABLE_STYLE_ENTRY: + int drawableId = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID); + mUpdatedDrawablesForStyle.put( + drawableId, + new HashMap<>()); + int size = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_STYLE_SIZE); + for (int i = 0; i < size; i++) { + int style = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_STYLE + i); + mUpdatedDrawablesForStyle.get(drawableId).put( + style, + ParcelableResource.createFromXml(parser)); + } + break; + case TAG_DRAWABLE_SOURCE_ENTRY: + drawableId = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID); + mUpdatedDrawablesForSource.put(drawableId, new HashMap<>()); + size = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_SOURCE_SIZE); + for (int i = 0; i < size; i++) { + int source = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_SOURCE + i); + mUpdatedDrawablesForSource.get(drawableId).put( + source, + ParcelableResource.createFromXml(parser)); + } + break; + default: + Log.e(TAG, "Unexpected tag: " + tag); + return false; + } + return true; + } + } + + public static class Injector { + File environmentGetDataSystemDirectory() { + return Environment.getDataSystemDirectory(); + } + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index db8da1146f1d..0f15db182981 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -26,6 +26,7 @@ import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; @@ -56,6 +57,8 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING; import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER; +import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID; +import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE; import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION; @@ -161,6 +164,7 @@ import android.app.StatusBarManager; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; +import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.DeviceOwnerType; @@ -177,6 +181,7 @@ import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.NetworkEvent; import android.app.admin.ParcelableGranteeMap; +import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; import android.app.admin.SecurityLog; @@ -231,6 +236,7 @@ import android.media.IAudioService; import android.net.ConnectivityManager; import android.net.ConnectivitySettingsManager; import android.net.IIpConnectivityMetrics; +import android.net.ProfileNetworkPreference; import android.net.ProxyInfo; import android.net.Uri; import android.net.VpnManager; @@ -715,6 +721,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Guarded by mHandler private @UserIdInt int mNetworkLoggingNotificationUserId = UserHandle.USER_NULL; + private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider; + private static final boolean ENABLE_LOCK_GUARD = true; /** @@ -1739,6 +1747,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { mSafetyChecker = safetyChecker; } + + DeviceManagementResourcesProvider getDeviceManagementResourcesProvider() { + return new DeviceManagementResourcesProvider(); + } } /** @@ -1791,6 +1803,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager(); mBugreportCollectionManager = new RemoteBugreportManager(this, mInjector); + mDeviceManagementResourcesProvider = mInjector.getDeviceManagementResourcesProvider(); + // "Lite" interface is available even when the device doesn't have the feature LocalServices.addService(DevicePolicyManagerLiteInternal.class, mLocalService); if (!mHasFeature) { @@ -1837,6 +1851,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { loadOwners(); performPolicyVersionUpgrade(); + + mDeviceManagementResourcesProvider.load(); } /** @@ -13213,12 +13229,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * @param restriction The restriction enforced by admin. It could be any user restriction or - * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and - * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}. + * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA}, + * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE} and {@link + * DevicePolicyManager#POLICY_SUSPEND_PACKAGES}. */ private Bundle getEnforcingAdminAndUserDetailsInternal(int userId, String restriction) { Bundle result = null; - if (restriction == null) { + + // For POLICY_SUSPEND_PACKAGES return PO or DO to keep the behavior same as + // before the bug fix for b/192245204. + if (restriction == null || DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals( + restriction)) { ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId); if (profileOwner != null) { result = new Bundle(); @@ -17827,10 +17848,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } int networkPreference = preferentialNetworkServiceEnabled ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT; + ProfileNetworkPreference.Builder preferenceBuilder = + new ProfileNetworkPreference.Builder(); + preferenceBuilder.setPreference(networkPreference); + List<ProfileNetworkPreference> preferences = new ArrayList<>(); + preferences.add(preferenceBuilder.build()); mInjector.binderWithCleanCallingIdentity(() -> - mInjector.getConnectivityManager().setProfileNetworkPreference( - UserHandle.of(userId), - networkPreference, + mInjector.getConnectivityManager().setProfileNetworkPreferences( + UserHandle.of(userId), preferences, null /* executor */, null /* listener */)); } @@ -17951,4 +17976,54 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3 ); } + + @Override + public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + Objects.requireNonNull(drawables, "drawables must be provided."); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.updateDrawables(drawables)) { + sendDrawableUpdatedBroadcast( + drawables.stream().mapToInt(d -> d.getDrawableId()).toArray()); + } + }); + } + + @Override + public void resetDrawables(@NonNull int[] drawableIds) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + Objects.requireNonNull(drawableIds, "drawableIds must be provided."); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.removeDrawables(drawableIds)) { + sendDrawableUpdatedBroadcast(drawableIds); + } + }); + } + + @Override + public ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource) { + return mInjector.binderWithCleanCallingIdentity(() -> + mDeviceManagementResourcesProvider.getDrawable( + drawableId, drawableStyle, drawableSource)); + } + + private void sendDrawableUpdatedBroadcast(int[] drawableIds) { + final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED); + intent.putExtra(EXTRA_RESOURCE_ID, drawableIds); + intent.putExtra(EXTRA_RESOURCE_TYPE_DRAWABLE, /* value= */ true); + intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + List<UserInfo> users = mUserManager.getAliveUsers(); + for (int i = 0; i < users.size(); i++) { + UserHandle user = users.get(i).getUserHandle(); + mContext.sendBroadcastAsUser(intent, user); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index fe5c358bc5c1..dd2f8e3129c0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -115,7 +115,6 @@ import com.android.server.biometrics.sensors.iris.IrisService; import com.android.server.broadcastradio.BroadcastRadioService; import com.android.server.camera.CameraServiceProxy; import com.android.server.clipboard.ClipboardService; -import com.android.server.communal.CommunalManagerService; import com.android.server.compat.PlatformCompat; import com.android.server.compat.PlatformCompatNative; import com.android.server.connectivity.PacProxyService; @@ -260,10 +259,6 @@ public final class SystemServer implements Dumpable { "com.android.server.companion.virtual.VirtualDeviceManagerService"; private static final String STATS_COMPANION_APEX_PATH = "/apex/com.android.os.statsd/javalib/service-statsd.jar"; - private static final String SCHEDULING_APEX_PATH = - "/apex/com.android.scheduling/javalib/service-scheduling.jar"; - private static final String REBOOT_READINESS_LIFECYCLE_CLASS = - "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle"; private static final String CONNECTIVITY_SERVICE_APEX_PATH = "/apex/com.android.tethering/javalib/service-connectivity.jar"; private static final String STATS_COMPANION_LIFECYCLE_CLASS = @@ -310,6 +305,8 @@ public final class SystemServer implements Dumpable { "com.android.clockwork.connectivity.WearConnectivityService"; private static final String WEAR_POWER_SERVICE_CLASS = "com.android.clockwork.power.WearPowerService"; + private static final String HEALTH_SERVICE_CLASS = + "com.google.android.clockwork.healthservices.HealthService"; private static final String WEAR_SIDEKICK_SERVICE_CLASS = "com.google.android.clockwork.sidekick.SidekickService"; private static final String WEAR_DISPLAYOFFLOAD_SERVICE_CLASS = @@ -406,8 +403,6 @@ public final class SystemServer implements Dumpable { private static final String SAFETY_CENTER_SERVICE_CLASS = "com.android.safetycenter.SafetyCenterService"; - private static final String SUPPLEMENTALPROCESS_APEX_PATH = - "/apex/com.android.supplementalprocess/javalib/service-supplementalprocess.jar"; private static final String SUPPLEMENTALPROCESS_SERVICE_CLASS = "com.android.server.supplementalprocess.SupplementalProcessManagerService$Lifecycle"; @@ -1902,7 +1897,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartNetworkStatsService"); try { - networkStats = NetworkStatsService.create(context, networkManagement); + networkStats = NetworkStatsService.create(context); ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats); } catch (Throwable e) { reportWtf("starting NetworkStats Service", e); @@ -2499,6 +2494,10 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(WEAR_POWER_SERVICE_CLASS); t.traceEnd(); + t.traceBegin("StartHealthService"); + mSystemServiceManager.startService(HEALTH_SERVICE_CLASS); + t.traceEnd(); + t.traceBegin("StartWearConnectivityService"); mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS); t.traceEnd(); @@ -2540,12 +2539,6 @@ public final class SystemServer implements Dumpable { STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH); t.traceEnd(); - // Reboot Readiness - t.traceBegin("StartRebootReadinessManagerService"); - mSystemServiceManager.startServiceFromJar( - REBOOT_READINESS_LIFECYCLE_CLASS, SCHEDULING_APEX_PATH); - t.traceEnd(); - // Statsd pulled atoms t.traceBegin("StartStatsPullAtomService"); mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS); @@ -2563,8 +2556,7 @@ public final class SystemServer implements Dumpable { // Supplemental Process t.traceBegin("StartSupplementalProcessManagerService"); - mSystemServiceManager.startServiceFromJar(SUPPLEMENTALPROCESS_SERVICE_CLASS, - SUPPLEMENTALPROCESS_APEX_PATH); + mSystemServiceManager.startService(SUPPLEMENTALPROCESS_SERVICE_CLASS); t.traceEnd(); if (safeMode) { @@ -2670,7 +2662,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("MakePowerManagerServiceReady"); try { // TODO: use boot phase - mPowerManagerService.systemReady(mActivityManagerService.getAppOpsService()); + mPowerManagerService.systemReady(); } catch (Throwable e) { reportWtf("making Power Manager Service ready", e); } @@ -2751,12 +2743,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS); t.traceEnd(); - if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_COMMUNAL_MODE)) { - t.traceBegin("CommunalManagerService"); - mSystemServiceManager.startService(CommunalManagerService.class); - t.traceEnd(); - } - // These are needed to propagate to the runnable below. final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; diff --git a/services/midi/OWNERS b/services/midi/OWNERS new file mode 100644 index 000000000000..f4d51f91b51b --- /dev/null +++ b/services/midi/OWNERS @@ -0,0 +1 @@ +philburk@google.com diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index ca31efcdf3d2..715fe6e5f85e 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -643,13 +643,31 @@ public class MidiService extends IMidiManager.Stub { private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0]; public MidiDeviceInfo[] getDevices() { + return getDevicesForTransport(MidiManager.TRANSPORT_MIDI_BYTE_STREAM); + } + + /** + * @hide + */ + public MidiDeviceInfo[] getDevicesForTransport(int transport) { ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>(); int uid = Binder.getCallingUid(); synchronized (mDevicesByInfo) { for (Device device : mDevicesByInfo.values()) { if (device.isUidAllowed(uid)) { - deviceInfos.add(device.getDeviceInfo()); + // UMP devices have protocols that are not PROTOCOL_UNKNOWN + if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) { + if (device.getDeviceInfo().getDefaultProtocol() + != MidiDeviceInfo.PROTOCOL_UNKNOWN) { + deviceInfos.add(device.getDeviceInfo()); + } + } else if (transport == MidiManager.TRANSPORT_MIDI_BYTE_STREAM) { + if (device.getDeviceInfo().getDefaultProtocol() + == MidiDeviceInfo.PROTOCOL_UNKNOWN) { + deviceInfos.add(device.getDeviceInfo()); + } + } } } } @@ -718,7 +736,7 @@ public class MidiService extends IMidiManager.Stub { @Override public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, - Bundle properties, int type) { + Bundle properties, int type, int defaultProtocol) { int uid = Binder.getCallingUid(); if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) { throw new SecurityException("only system can create USB devices"); @@ -728,7 +746,8 @@ public class MidiService extends IMidiManager.Stub { synchronized (mDevicesByInfo) { return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames, - outputPortNames, properties, server, null, false, uid); + outputPortNames, properties, server, null, false, uid, + defaultProtocol); } } @@ -797,11 +816,12 @@ public class MidiService extends IMidiManager.Stub { private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo, - boolean isPrivate, int uid) { + boolean isPrivate, int uid, int defaultProtocol) { int id = mNextDeviceId++; MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, - inputPortNames, outputPortNames, properties, isPrivate); + inputPortNames, outputPortNames, properties, isPrivate, + defaultProtocol); if (server != null) { try { @@ -988,10 +1008,11 @@ public class MidiService extends IMidiManager.Stub { synchronized (mDevicesByInfo) { addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL, - numInputPorts, numOutputPorts, - inputPortNames.toArray(EMPTY_STRING_ARRAY), - outputPortNames.toArray(EMPTY_STRING_ARRAY), - properties, null, serviceInfo, isPrivate, uid); + numInputPorts, numOutputPorts, + inputPortNames.toArray(EMPTY_STRING_ARRAY), + outputPortNames.toArray(EMPTY_STRING_ARRAY), + properties, null, serviceInfo, isPrivate, uid, + MidiDeviceInfo.PROTOCOL_UNKNOWN); } // setting properties to null signals that we are no longer // processing a <device> diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 8203c1b731c4..5220c8fa7a72 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -20,7 +20,7 @@ import android.app.PropertyInvalidatedCache import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedActivity +import com.android.server.pm.pkg.component.ParsedActivity import android.os.Binder import android.os.UserHandle import android.util.ArrayMap diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp index ebad5afac625..93d70bb6cdba 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp @@ -24,7 +24,7 @@ package { android_test_helper_app { name: "PackageManagerTestAppDeclaresStaticLibrary", manifest: "AndroidManifestDeclaresStaticLibrary.xml", - certificate: ":FrameworksCoreTests_keyset_A_cert", + certificate: ":FrameworksServicesTests_keyset_A_cert", } android_test_helper_app { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index d7e3195765ef..a0f3bbf928ab 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -23,8 +23,7 @@ import android.content.pm.FeatureGroupInfo import android.content.pm.FeatureInfo import android.content.pm.PackageManager import android.content.pm.SigningDetails -import android.content.pm.parsing.ParsingPackage -import android.content.pm.parsing.component.* +import com.android.server.pm.pkg.parsing.ParsingPackage import android.net.Uri import android.os.Bundle import android.os.Parcelable @@ -34,6 +33,18 @@ import android.util.SparseIntArray import com.android.internal.R import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.PackageImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl +import com.android.server.pm.pkg.component.ParsedAttributionImpl +import com.android.server.pm.pkg.component.ParsedComponentImpl +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl +import com.android.server.pm.pkg.component.ParsedPermissionImpl +import com.android.server.pm.pkg.component.ParsedProcessImpl +import com.android.server.pm.pkg.component.ParsedProviderImpl +import com.android.server.pm.pkg.component.ParsedServiceImpl +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import java.security.KeyPairGenerator @@ -130,6 +141,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag AndroidPackage::getLabelRes, AndroidPackage::getLargestWidthLimitDp, AndroidPackage::getLogo, + AndroidPackage::getLocaleConfigRes, AndroidPackage::getManageSpaceActivityName, AndroidPackage::getMemtagMode, AndroidPackage::getMinSdkVersion, @@ -280,7 +292,13 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageImpl::addAttribution, Triple("testTag", 13, listOf("testInherit")), transformGet = { it.singleOrNull()?.let { Triple(it.tag, it.label, it.inheritFrom) } }, - transformSet = { it?.let { ParsedAttributionImpl(it.first, it.second, it.third) } } + transformSet = { it?.let { + ParsedAttributionImpl( + it.first, + it.second, + it.third + ) + } } ), getSetByValue2( AndroidPackage::getKeySetMapping, @@ -293,12 +311,14 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageImpl::addPermissionGroup, "test.permission.GROUP", transformGet = { it.singleOrNull()?.name }, - transformSet = { ParsedPermissionGroupImpl().apply { setName(it) } } + transformSet = { ParsedPermissionGroupImpl() + .apply { setName(it) } } ), getSetByValue2( AndroidPackage::getPreferredActivityFilters, PackageImpl::addPreferredActivityFilter, - "TestClassName" to ParsedIntentInfoImpl().apply { + "TestClassName" to ParsedIntentInfoImpl() + .apply { intentFilter.apply { addDataScheme("http") addDataAuthority("test.pm.server.android.com", null) @@ -347,42 +367,48 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageImpl::addActivity, "TestActivityName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedActivityImpl().apply { name = it }.withMimeGroups() } + transformSet = { ParsedActivityImpl() + .apply { name = it }.withMimeGroups() } ), getSetByValue( AndroidPackage::getApexSystemServices, PackageImpl::addApexSystemService, "TestApexSystemServiceName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedApexSystemServiceImpl().apply { name = it } } + transformSet = { ParsedApexSystemServiceImpl() + .apply { name = it } } ), getSetByValue( AndroidPackage::getReceivers, PackageImpl::addReceiver, "TestReceiverName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedActivityImpl().apply { name = it }.withMimeGroups() } + transformSet = { ParsedActivityImpl() + .apply { name = it }.withMimeGroups() } ), getSetByValue( AndroidPackage::getServices, PackageImpl::addService, "TestServiceName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedServiceImpl().apply { name = it }.withMimeGroups() } + transformSet = { ParsedServiceImpl() + .apply { name = it }.withMimeGroups() } ), getSetByValue( AndroidPackage::getProviders, PackageImpl::addProvider, "TestProviderName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedProviderImpl().apply { name = it }.withMimeGroups() } + transformSet = { ParsedProviderImpl() + .apply { name = it }.withMimeGroups() } ), getSetByValue( AndroidPackage::getInstrumentations, PackageImpl::addInstrumentation, "TestInstrumentationName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedInstrumentationImpl().apply { name = it } } + transformSet = { ParsedInstrumentationImpl() + .apply { name = it } } ), getSetByValue( AndroidPackage::getConfigPreferences, @@ -407,7 +433,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageImpl::addPermission, "test.PERMISSION", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedPermissionImpl().apply { name = it } } + transformSet = { ParsedPermissionImpl() + .apply { name = it } } ), getSetByValue( AndroidPackage::getUsesPermissions, @@ -418,7 +445,12 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag it.filterNot { it.name == "test.implicit.PERMISSION" } .singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedUsesPermissionImpl(it, 0) } + transformSet = { + ParsedUsesPermissionImpl( + it, + 0 + ) + } ), getSetByValue( AndroidPackage::getRequestedFeatures, @@ -443,7 +475,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag getSetByValue( AndroidPackage::getProcesses, PackageImpl::setProcesses, - mapOf("testProcess" to ParsedProcessImpl().apply { name = "testProcessName" }), + mapOf("testProcess" to ParsedProcessImpl() + .apply { name = "testProcessName" }), compare = { first, second -> equalBy( first, second, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt index 8170acfb02b7..a89b717fd90d 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt @@ -17,8 +17,8 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.ActivityInfo -import android.content.pm.parsing.component.ParsedActivity -import android.content.pm.parsing.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedActivity +import com.android.server.pm.pkg.component.ParsedActivityImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -27,7 +27,8 @@ class ParsedActivityTest : ParsedMainComponentTest( ParsedActivityImpl::class ) { - override val defaultImpl = ParsedActivityImpl() + override val defaultImpl = + ParsedActivityImpl() override val creator = ParsedActivityImpl.CREATOR override val mainComponentSubclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt index 503301b5151b..4e44e96aa710 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt @@ -16,15 +16,21 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedAttribution -import android.content.pm.parsing.component.ParsedAttributionImpl +import com.android.server.pm.pkg.component.ParsedAttribution +import com.android.server.pm.pkg.component.ParsedAttributionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts -class ParsedAttributionTest : ParcelableComponentTest(ParsedAttribution::class, +class ParsedAttributionTest : ParcelableComponentTest( + ParsedAttribution::class, ParsedAttributionImpl::class) { - override val defaultImpl = ParsedAttributionImpl("", 0, emptyList()) + override val defaultImpl = + ParsedAttributionImpl( + "", + 0, + emptyList() + ) override val creator = ParsedAttributionImpl.CREATOR override val baseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt index e978dd389543..058f6d69f3e7 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt @@ -17,10 +17,9 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedComponent -import android.content.pm.parsing.component.ParsedComponentImpl -import android.content.pm.parsing.component.ParsedIntentInfo -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedComponent +import com.android.server.pm.pkg.component.ParsedComponentImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Bundle import android.os.Parcelable import kotlin.contracts.ExperimentalContracts @@ -65,7 +64,8 @@ abstract class ParsedComponentTest(getterType: KClass<*>, setterType: KClass<out ParsedComponentImpl::addIntent, "TestLabel", transformGet = { it.singleOrNull()?.nonLocalizedLabel }, - transformSet = { ParsedIntentInfoImpl().setNonLocalizedLabel(it!!) }, + transformSet = { ParsedIntentInfoImpl() + .setNonLocalizedLabel(it!!) }, ), getSetByValue( ParsedComponent::getProperties, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt index f15b911294b3..eeb30b70c143 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedInstrumentation -import android.content.pm.parsing.component.ParsedInstrumentationImpl +import com.android.server.pm.pkg.component.ParsedInstrumentation +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -26,7 +26,8 @@ class ParsedInstrumentationTest : ParsedComponentTest( ParsedInstrumentationImpl::class ) { - override val defaultImpl = ParsedInstrumentationImpl() + override val defaultImpl = + ParsedInstrumentationImpl() override val creator = ParsedInstrumentationImpl.CREATOR override val subclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt index f04e85128c14..f27a51f63049 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedIntentInfo -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedIntentInfo +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Parcelable import android.os.PatternMatcher import kotlin.contracts.ExperimentalContracts @@ -28,7 +28,8 @@ class ParsedIntentInfoTest : ParcelableComponentTest( ParsedIntentInfoImpl::class, ) { - override val defaultImpl = ParsedIntentInfoImpl() + override val defaultImpl = + ParsedIntentInfoImpl() override val creator = ParsedIntentInfoImpl.CREATOR override val excludedMethods = listOf( @@ -43,7 +44,8 @@ class ParsedIntentInfoTest : ParcelableComponentTest( ParsedIntentInfo::getNonLocalizedLabel, ) - override fun initialObject() = ParsedIntentInfoImpl().apply { + override fun initialObject() = ParsedIntentInfoImpl() + .apply { intentFilter.apply { addAction("test.ACTION") addDataAuthority("testAuthority", "404") diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt index 214734a5fdbe..a0d8c44899d8 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt @@ -16,9 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedMainComponent -import android.content.pm.parsing.component.ParsedMainComponentImpl -import android.content.pm.parsing.component.ParsedService +import com.android.server.pm.pkg.component.ParsedMainComponent +import com.android.server.pm.pkg.component.ParsedMainComponentImpl import android.os.Parcelable import java.util.Arrays import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt index f876ed05e0e1..57562ef8588c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedPermissionGroup -import android.content.pm.parsing.component.ParsedPermissionGroupImpl +import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -26,7 +26,8 @@ class ParsedPermissionGroupTest : ParsedComponentTest( ParsedPermissionGroupImpl::class, ) { - override val defaultImpl = ParsedPermissionGroupImpl() + override val defaultImpl = + ParsedPermissionGroupImpl() override val creator = ParsedPermissionGroupImpl.CREATOR override val subclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt index 6f48e2442e56..c72a44e4c4e0 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt @@ -16,10 +16,10 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedPermission -import android.content.pm.parsing.component.ParsedPermissionGroup -import android.content.pm.parsing.component.ParsedPermissionGroupImpl -import android.content.pm.parsing.component.ParsedPermissionImpl +import com.android.server.pm.pkg.component.ParsedPermission +import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl +import com.android.server.pm.pkg.component.ParsedPermissionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -28,7 +28,8 @@ class ParsedPermissionTest : ParsedComponentTest( ParsedPermissionImpl::class ) { - override val defaultImpl = ParsedPermissionImpl() + override val defaultImpl = + ParsedPermissionImpl() override val creator = ParsedPermissionImpl.CREATOR override val subclassExcludedMethods = listOf( @@ -53,7 +54,8 @@ class ParsedPermissionTest : ParsedComponentTest( getSetByValue( ParsedPermission::getParsedPermissionGroup, ParsedPermissionImpl::setParsedPermissionGroup, - ParsedPermissionGroupImpl().apply { name = "test.permission.group" }, + ParsedPermissionGroupImpl() + .apply { name = "test.permission.group" }, compare = { first, second -> equalBy(first, second, ParsedPermissionGroup::getName) } ), ) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt index 005d3e81d3a3..8b9361a31d0a 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt @@ -16,15 +16,16 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedProcess -import android.content.pm.parsing.component.ParsedProcessImpl +import com.android.server.pm.pkg.component.ParsedProcess +import com.android.server.pm.pkg.component.ParsedProcessImpl import android.util.ArrayMap import kotlin.contracts.ExperimentalContracts @ExperimentalContracts class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedProcessImpl::class) { - override val defaultImpl = ParsedProcessImpl() + override val defaultImpl = + ParsedProcessImpl() override val creator = ParsedProcessImpl.CREATOR override val excludedMethods = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt index 78e4b796b44f..037da24a3304 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt @@ -17,15 +17,16 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PathPermission -import android.content.pm.parsing.component.ParsedProvider -import android.content.pm.parsing.component.ParsedProviderImpl +import com.android.server.pm.pkg.component.ParsedProvider +import com.android.server.pm.pkg.component.ParsedProviderImpl import android.os.PatternMatcher import kotlin.contracts.ExperimentalContracts @ExperimentalContracts class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) { - override val defaultImpl = ParsedProviderImpl() + override val defaultImpl = + ParsedProviderImpl() override val creator = ParsedProviderImpl.CREATOR override val mainComponentSubclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt index 9363aa37360c..e2c9439df9cf 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt @@ -16,14 +16,15 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedService -import android.content.pm.parsing.component.ParsedServiceImpl +import com.android.server.pm.pkg.component.ParsedService +import com.android.server.pm.pkg.component.ParsedServiceImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts class ParsedServiceTest : ParsedMainComponentTest(ParsedService::class, ParsedServiceImpl::class) { - override val defaultImpl = ParsedServiceImpl() + override val defaultImpl = + ParsedServiceImpl() override val creator = ParsedServiceImpl.CREATOR override val mainComponentSubclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt index 81e800f2d6d2..ad607366967f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedUsesPermission -import android.content.pm.parsing.component.ParsedUsesPermissionImpl +import com.android.server.pm.pkg.component.ParsedUsesPermission +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -26,7 +26,8 @@ class ParsedUsesPermissionTest : ParcelableComponentTest( ParsedUsesPermissionImpl::class ) { - override val defaultImpl = ParsedUsesPermissionImpl("", 0) + override val defaultImpl = + ParsedUsesPermissionImpl("", 0) override val creator = ParsedUsesPermissionImpl.CREATOR override val baseParams = listOf( @@ -34,5 +35,6 @@ class ParsedUsesPermissionTest : ParcelableComponentTest( ParsedUsesPermission::getUsesPermissionFlags ) - override fun initialObject() = ParsedUsesPermissionImpl("", 0) + override fun initialObject() = + ParsedUsesPermissionImpl("", 0) } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt index 33234d52c9ff..652dc38fa6ed 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.test.verify.domain import android.content.Intent import android.content.pm.ApplicationInfo -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Build import android.os.PatternMatcher import android.util.ArraySet @@ -94,7 +94,8 @@ class DomainVerificationCollectorTest { val activityList = listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { addAction(Intent.ACTION_VIEW) addCategory(Intent.CATEGORY_BROWSABLE) @@ -110,7 +111,8 @@ class DomainVerificationCollectorTest { }, ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(true) addAction(Intent.ACTION_VIEW) @@ -270,7 +272,8 @@ class DomainVerificationCollectorTest { val activityList = listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addAction(Intent.ACTION_VIEW) @@ -285,7 +288,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { addAction(Intent.ACTION_VIEW) addCategory(Intent.CATEGORY_BROWSABLE) @@ -300,7 +304,8 @@ class DomainVerificationCollectorTest { }, ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addAction(Intent.ACTION_VIEW) @@ -316,7 +321,8 @@ class DomainVerificationCollectorTest { }, ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addAction(Intent.ACTION_VIEW) @@ -329,7 +335,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addAction(Intent.ACTION_VIEW) @@ -342,7 +349,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addCategory(Intent.CATEGORY_BROWSABLE) @@ -355,7 +363,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addCategory(Intent.CATEGORY_BROWSABLE) @@ -365,7 +374,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addCategory(Intent.CATEGORY_BROWSABLE) @@ -375,7 +385,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addCategory(Intent.CATEGORY_BROWSABLE) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 089e9db9a755..92cdb348e60d 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -20,8 +20,8 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.SigningDetails -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationState import android.os.Build @@ -308,7 +308,8 @@ class DomainVerificationEnforcerTest { listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt index 334f503a4bfb..878bee012635 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -19,8 +19,8 @@ package com.android.server.pm.test.verify.domain import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageUserStateInternal import android.content.pm.verify.domain.DomainOwner import android.content.pm.verify.domain.DomainVerificationInfo @@ -526,7 +526,8 @@ class DomainVerificationManagerApiTest { ParsedActivityImpl().apply { domains.forEach { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index fb581d70a5fc..0369bab61f0f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -20,8 +20,8 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.pm.Signature import android.content.pm.SigningDetails -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageUserStateInternal import android.content.pm.verify.domain.DomainOwner import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED @@ -867,7 +867,8 @@ class DomainVerificationPackageTest { whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(isEnabled) { true } - fun baseIntent(domain: String) = ParsedIntentInfoImpl().apply { + fun baseIntent(domain: String) = ParsedIntentInfoImpl() + .apply { intentFilter.apply { addAction(Intent.ACTION_VIEW) addCategory(Intent.CATEGORY_BROWSABLE) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index a397d563144d..3a602a8b3c46 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -19,8 +19,8 @@ package com.android.server.pm.test.verify.domain import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageUserStateInternal import android.content.pm.verify.domain.DomainVerificationState import android.os.Build @@ -196,7 +196,8 @@ class DomainVerificationSettingsMutationTest { listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index 728da4992893..ffc287736066 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.test.verify.domain import android.content.Intent import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationState import android.content.pm.verify.domain.DomainVerificationUserState @@ -112,7 +112,8 @@ class DomainVerificationUserStateOverrideTest { val activityList = listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) @@ -126,7 +127,8 @@ class DomainVerificationUserStateOverrideTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) diff --git a/services/tests/apexsystemservices/Android.bp b/services/tests/apexsystemservices/Android.bp index 01e90a8d880c..a6ed1ae56567 100644 --- a/services/tests/apexsystemservices/Android.bp +++ b/services/tests/apexsystemservices/Android.bp @@ -37,5 +37,8 @@ java_test_host { "truth-prebuilt", "modules-utils-build-testing", ], - test_suites: ["device-tests"], + test_suites: [ + "device-tests", + "mts-core", + ], } diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 635f1360ff73..36246e5f3857 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -14,7 +14,7 @@ java_defaults { name: "FrameworkMockingServicesTests-jni-defaults", jni_libs: [ - "libactivitymanagermockingservicestestjni", + "libmockingservicestestjni", ], } diff --git a/services/tests/mockingservicestests/OWNERS b/services/tests/mockingservicestests/OWNERS index 0fb0c3021486..2bb16496e0f0 100644 --- a/services/tests/mockingservicestests/OWNERS +++ b/services/tests/mockingservicestests/OWNERS @@ -1 +1,5 @@ include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS +per-file FakeGameClassifier.java = file:/GAME_MANAGER_OWNERS +per-file FakeGameServiceProviderInstance = file:/GAME_MANAGER_OWNERS +per-file FakeServiceConnector.java = file:/GAME_MANAGER_OWNERS +per-file Game* = file:/GAME_MANAGER_OWNERS diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp index a32bf2c3b260..89b204b9c999 100644 --- a/services/tests/mockingservicestests/jni/Android.bp +++ b/services/tests/mockingservicestests/jni/Android.bp @@ -8,7 +8,7 @@ package { } cc_library_shared { - name: "libactivitymanagermockingservicestestjni", + name: "libmockingservicestestjni", cflags: [ "-Wall", @@ -19,12 +19,15 @@ cc_library_shared { srcs: [ ":lib_cachedAppOptimizer_native", + ":lib_gameManagerService_native", "onload.cpp", ], include_dirs: [ "frameworks/base/libs", "frameworks/native/services", + "frameworks/native/libs/math/include", + "frameworks/native/libs/ui/include", "system/memory/libmeminfo/include", ], @@ -33,10 +36,19 @@ cc_library_shared { "libandroid_runtime", "libbase", "libbinder", + "libgralloctypes", + "libgui", + "libhidlbase", "liblog", "libmeminfo", "libnativehelper", "libprocessgroup", "libutils", + "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + "android.hardware.graphics.common@1.2", + "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.mapper@4.0", + "android.hidl.token@1.0-utils", ], } diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp index 147cc479be18..23ccb22321b2 100644 --- a/services/tests/mockingservicestests/jni/onload.cpp +++ b/services/tests/mockingservicestests/jni/onload.cpp @@ -25,6 +25,7 @@ namespace android { int register_android_server_am_CachedAppOptimizer(JNIEnv* env); +int register_android_server_app_GameManagerService(JNIEnv* env); }; using namespace android; @@ -40,6 +41,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) } ALOG_ASSERT(env, "Could not retrieve the env!"); register_android_server_am_CachedAppOptimizer(env); + register_android_server_app_GameManagerService(env); return JNI_VERSION_1_4; } diff --git a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java index 5a6275d71d1e..cc97b8f792f9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java @@ -24,7 +24,6 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; @@ -226,7 +225,7 @@ public final class MasterClearReceiverTest { } private void expectWipeNonSystemUser() { - when(mUserManager.removeUserOrSetEphemeral(anyInt(), anyBoolean())) + when(mUserManager.removeUserWhenPossible(any(), anyBoolean())) .thenReturn(UserManager.REMOVE_RESULT_REMOVED); } @@ -266,7 +265,7 @@ public final class MasterClearReceiverTest { } private void verifyWipeNonSystemUser() { - verify(mUserManager).removeUserOrSetEphemeral(anyInt(), anyBoolean()); + verify(mUserManager).removeUserWhenPossible(any(), anyBoolean()); } private void setPendingResultForUser(int userId) { diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 1c21645c1626..d6705a508f5e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -190,7 +190,7 @@ public class GameManagerServiceTests { } private void mockDeviceConfigPerformance() { - String configString = "mode=2,downscaleFactor=0.5,useAngle=false"; + String configString = "mode=2,downscaleFactor=0.5,useAngle=false,fps=90"; when(DeviceConfig.getProperty(anyString(), anyString())) .thenReturn(configString); } @@ -203,13 +203,13 @@ public class GameManagerServiceTests { } private void mockDeviceConfigBattery() { - String configString = "mode=3,downscaleFactor=0.7"; + String configString = "mode=3,downscaleFactor=0.7,fps=30"; when(DeviceConfig.getProperty(anyString(), anyString())) .thenReturn(configString); } private void mockDeviceConfigAll() { - String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5"; + String configString = "mode=3,downscaleFactor=0.7,fps=30:mode=2,downscaleFactor=0.5,fps=90"; when(DeviceConfig.getProperty(anyString(), anyString())) .thenReturn(configString); } @@ -454,12 +454,12 @@ public class GameManagerServiceTests { gameManagerService.getGameMode(mPackageName, USER_ID_2)); } - private void checkReportedModes(int ...requiredModes) { - GameManagerService gameManagerService = - new GameManagerService(mMockContext, mTestLooper.getLooper()); - - startUser(gameManagerService, USER_ID_1); - gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + private void checkReportedModes(GameManagerService gameManagerService, int ...requiredModes) { + if (gameManagerService == null) { + gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + } ArraySet<Integer> reportedModes = new ArraySet<>(); int[] modes = gameManagerService.getAvailableGameModes(mPackageName); for (int mode : modes) { @@ -472,12 +472,13 @@ public class GameManagerServiceTests { } } - private void checkDownscaling(int gameMode, String scaling) { - GameManagerService gameManagerService = - new GameManagerService(mMockContext, mTestLooper.getLooper()); - - startUser(gameManagerService, USER_ID_1); - gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + private void checkDownscaling(GameManagerService gameManagerService, + int gameMode, String scaling) { + if (gameManagerService == null) { + gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + } GameManagerService.GamePackageConfiguration config = gameManagerService.getConfig(mPackageName); assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling); @@ -496,6 +497,17 @@ public class GameManagerServiceTests { assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled); } + private void checkFps(GameManagerService gameManagerService, int gameMode, int fps) { + if (gameManagerService == null) { + gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + } + GameManagerService.GamePackageConfiguration config = + gameManagerService.getConfig(mPackageName); + assertEquals(config.getGameModeConfiguration(gameMode).getFps(), fps); + } + /** * Phenotype device config exists, but is only propagating the default value. */ @@ -503,7 +515,7 @@ public class GameManagerServiceTests { public void testDeviceConfigDefault() { mockDeviceConfigDefault(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED); } /** @@ -513,7 +525,7 @@ public class GameManagerServiceTests { public void testDeviceConfigNone() { mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED); } /** @@ -523,7 +535,7 @@ public class GameManagerServiceTests { public void testDeviceConfigPerformance() { mockDeviceConfigPerformance(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); } /** @@ -533,7 +545,7 @@ public class GameManagerServiceTests { public void testDeviceConfigBattery() { mockDeviceConfigBattery(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkReportedModes(null, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } /** @@ -543,7 +555,7 @@ public class GameManagerServiceTests { public void testDeviceConfigAll() { mockDeviceConfigAll(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } @@ -554,7 +566,7 @@ public class GameManagerServiceTests { public void testDeviceConfigInvalid() { mockDeviceConfigInvalid(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED); } /** @@ -564,7 +576,171 @@ public class GameManagerServiceTests { public void testDeviceConfigMalformed() { mockDeviceConfigMalformed(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Override device config for performance mode exists and is valid. + */ + @Test + public void testSetDeviceConfigOverridePerformance() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); + } + + /** + * Override device config for battery mode exists and is valid. + */ + @Test + public void testSetDeviceConfigOverrideBattery() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY, + GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.5"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60); + } + + /** + * Override device configs for both battery and performance modes exists and are valid. + */ + @Test + public void testSetDeviceOverrideConfigAll() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.5"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60); + } + + /** + * Override device config for performance mode exists and is valid. + */ + @Test + public void testResetDeviceConfigOverridePerformance() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + + gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); + } + + /** + * Override device config for battery mode exists and is valid. + */ + @Test + public void testResetDeviceConfigOverrideBattery() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY, + GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); + } + + /** + * Override device configs for both battery and performance modes exists and are valid. + */ + @Test + public void testResetDeviceOverrideConfigAll() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, -1); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); + } + + /** + * Override device configs for both battery and performance modes exists and are valid. + * Only one mode is reset, and the other mode still has overridden config + */ + @Test + public void testResetDeviceOverrideConfigPartial() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); } /** @@ -575,10 +751,12 @@ public class GameManagerServiceTests { mockGameModeOptInAll(); mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } + + /** * BATTERY game mode is available through the app manifest opt-in. */ @@ -587,7 +765,7 @@ public class GameManagerServiceTests { mockGameModeOptInBattery(); mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkReportedModes(null, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } /** @@ -598,7 +776,7 @@ public class GameManagerServiceTests { mockGameModeOptInPerformance(); mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); } /** @@ -610,7 +788,7 @@ public class GameManagerServiceTests { mockGameModeOptInBattery(); mockDeviceConfigPerformance(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } @@ -623,7 +801,7 @@ public class GameManagerServiceTests { mockGameModeOptInPerformance(); mockDeviceConfigBattery(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } @@ -634,7 +812,7 @@ public class GameManagerServiceTests { public void testInterventionAllowScalingDefault() throws Exception { mockDeviceConfigPerformance(); mockModifyGameModeGranted(); - checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "0.5"); } /** @@ -645,7 +823,7 @@ public class GameManagerServiceTests { mockDeviceConfigPerformance(); mockInterventionAllowDownscaleFalse(); mockModifyGameModeGranted(); - checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "1.0"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "1.0"); } /** @@ -657,7 +835,7 @@ public class GameManagerServiceTests { mockDeviceConfigPerformance(); mockInterventionAllowDownscaleTrue(); mockModifyGameModeGranted(); - checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "0.5"); } /** @@ -708,6 +886,14 @@ public class GameManagerServiceTests { checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, true); } + @Test + public void testInterventionFps() throws Exception { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + checkFps(null, GameManager.GAME_MODE_PERFORMANCE, 90); + checkFps(null, GameManager.GAME_MODE_BATTERY, 30); + } + /** * PERFORMANCE game mode is configured through Phenotype, but the app has also opted into the * same mode. No interventions for this game mode should be available in this case. @@ -776,4 +962,8 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_STANDARD, gameManagerService.getGameMode(mPackageName, USER_ID_1)); } + + static { + System.loadLibrary("mockingservicestestjni"); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java index b6c706ed2730..167090693a10 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -18,26 +18,38 @@ package com.android.server.app; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityTaskManager; import android.app.ITaskStackListener; import android.content.ComponentName; import android.content.pm.PackageManager; -import android.os.IBinder; +import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.service.games.CreateGameSessionRequest; +import android.service.games.CreateGameSessionResult; +import android.service.games.GameSessionViewHostConfiguration; +import android.service.games.GameStartedEvent; import android.service.games.IGameService; +import android.service.games.IGameServiceController; import android.service.games.IGameSession; import android.service.games.IGameSessionService; +import android.view.SurfaceControlViewHost.SurfacePackage; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -45,19 +57,20 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; +import com.android.internal.util.Preconditions; +import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; +import java.util.HashMap; /** @@ -68,6 +81,9 @@ import java.util.function.Supplier; @Presubmit public final class GameServiceProviderInstanceImplTest { + private static final GameSessionViewHostConfiguration + DEFAULT_GAME_SESSION_VIEW_HOST_CONFIGURATION = + new GameSessionViewHostConfiguration(1, 500, 800); private static final int USER_ID = 10; private static final String APP_A_PACKAGE = "com.package.app.a"; private static final ComponentName APP_A_MAIN_ACTIVITY = @@ -82,14 +98,14 @@ public final class GameServiceProviderInstanceImplTest { @Mock private IActivityTaskManager mMockActivityTaskManager; @Mock - private IGameService mMockGameService; - @Mock - private IGameSessionService mMockGameSessionService; + private WindowManagerInternal mMockWindowManagerInternal; private FakeGameClassifier mFakeGameClassifier; + private FakeGameService mFakeGameService; private FakeServiceConnector<IGameService> mFakeGameServiceConnector; + private FakeGameSessionService mFakeGameSessionService; private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector; private ArrayList<ITaskStackListener> mTaskStackListeners; - private InOrder mInOrder; + private ArrayList<RunningTaskInfo> mRunningTaskInfos; @Before public void setUp() throws PackageManager.NameNotFoundException, RemoteException { @@ -98,13 +114,13 @@ public final class GameServiceProviderInstanceImplTest { .strictness(Strictness.LENIENT) .startMocking(); - mInOrder = inOrder(mMockGameService, mMockGameSessionService); - mFakeGameClassifier = new FakeGameClassifier(); mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE); - mFakeGameServiceConnector = new FakeServiceConnector<>(mMockGameService); - mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mMockGameSessionService); + mFakeGameService = new FakeGameService(); + mFakeGameServiceConnector = new FakeServiceConnector<>(mFakeGameService); + mFakeGameSessionService = new FakeGameSessionService(); + mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mFakeGameSessionService); mTaskStackListeners = new ArrayList<>(); doAnswer(invocation -> { @@ -112,6 +128,10 @@ public final class GameServiceProviderInstanceImplTest { return null; }).when(mMockActivityTaskManager).registerTaskStackListener(any()); + mRunningTaskInfos = new ArrayList<>(); + when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn( + mRunningTaskInfos); + doAnswer(invocation -> { mTaskStackListeners.remove(invocation.getArgument(0)); return null; @@ -122,6 +142,7 @@ public final class GameServiceProviderInstanceImplTest { ConcurrentUtils.DIRECT_EXECUTOR, mFakeGameClassifier, mMockActivityTaskManager, + mMockWindowManagerInternal, mFakeGameServiceConnector, mFakeGameSessionServiceConnector); } @@ -135,8 +156,7 @@ public final class GameServiceProviderInstanceImplTest { public void start_startsGameSession() throws Exception { mGameServiceProviderInstance.start(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED); assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -145,9 +165,9 @@ public final class GameServiceProviderInstanceImplTest { @Test public void start_multipleTimes_startsGameSessionOnce() throws Exception { mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.start(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED); assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -157,9 +177,10 @@ public final class GameServiceProviderInstanceImplTest { public void stop_neverStarted_doesNothing() throws Exception { mGameServiceProviderInstance.stop(); + + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); - mInOrder.verifyNoMoreInteractions(); } @Test @@ -167,9 +188,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(1); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -183,11 +203,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(2); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(2); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -199,9 +216,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.stop(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(1); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -211,7 +227,6 @@ public final class GameServiceProviderInstanceImplTest { public void gameTaskStarted_neverStarted_doesNothing() throws Exception { dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - mInOrder.verifyNoMoreInteractions(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); } @@ -220,35 +235,25 @@ public final class GameServiceProviderInstanceImplTest { public void gameTaskRemoved_neverStarted_doesNothing() throws Exception { dispatchTaskRemoved(10); - mInOrder.verifyNoMoreInteractions(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); } @Test - public void gameTaskStarted_afterStopped_doesNothing() throws Exception { + public void gameTaskStarted_afterStopped_doesNotSendGameStartedEvent() throws Exception { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test - public void appTaskStarted_doesNothing() throws Exception { + public void appTaskStarted_doesNotSendGameStartedEvent() throws Exception { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, APP_A_MAIN_ACTIVITY); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test @@ -256,280 +261,335 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, null); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test - public void gameTaskStarted_createsGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); + public void gameSessionRequested_withoutTaskDispatch_doesNotCrashAndDoesNotCreateGameSession() + throws Exception { + mGameServiceProviderInstance.start(); + mFakeGameService.requestCreateGameSession(10); + + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); + } + + @Test + public void gameTaskStarted_noSessionRequest_callsStartGame() throws Exception { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(gameSession10.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + GameStartedEvent expectedGameStartedEvent = new GameStartedEvent(10, GAME_A_PACKAGE); + assertThat(mFakeGameService.getGameStartedEvents()) + .containsExactly(expectedGameStartedEvent).inOrder(); } @Test - public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession() + public void gameTaskStarted_requestToCreateGameSessionIncludesTaskConfiguration() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation = + getOnlyElement(mFakeGameSessionService.getCapturedCreateInvocations()); + assertThat(capturedCreateInvocation.mGameSessionViewHostConfiguration) + .isEqualTo(DEFAULT_GAME_SESSION_VIEW_HOST_CONFIGURATION); + } + + @Test + public void gameTaskStarted_failsToDetermineTaskOverlayConfiguration_gameSessionNotCreated() + throws Exception { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); + } + + @Test + public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + assertThat(gameSession10.mIsDestroyed).isFalse(); + } + + @Test + public void gameTaskStartedAndSessionRequested_secondSessionRequest_ignoredAndDoesNotCrash() + throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + mFakeGameService.requestCreateGameSession(10); + + CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10, + GAME_A_PACKAGE); + assertThat(getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations()).mCreateGameSessionRequest) + .isEqualTo(expectedCreateGameSessionRequest); + } + + @Test + public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verifyNoMoreInteractions(mMockWindowManagerInternal); + } + + @Test + public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession() + throws Exception { + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + dispatchTaskRemoved(10); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + assertThat(gameSession10.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTaskRemoved_destroysGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); - + public void gameTaskRemoved_whileGameSessionAttached_destroysGameSession() throws Exception { mGameServiceProviderInstance.start(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + dispatchTaskRemoved(10); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTaskStarted_multipleTimes_createsMultipleGameSessions() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + public void gameTaskRemoved_removesTaskOverlay() throws Exception { + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + stopTask(10); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verifyNoMoreInteractions(mMockWindowManagerInternal); + } + @Test + public void gameTaskStartedAndSessionRequested_multipleTimes_createsMultipleGameSessions() + throws Exception { mGameServiceProviderInstance.start(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isFalse(); assertThat(gameSession11.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTaskRemoved_afterMultipleCreated_destroysOnlyThatGameSession() + public void gameTaskStartedTwice_sessionRequestedSecondTimeOnly_createsOneGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + startTask(11, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + assertThat(gameSession10.mIsDestroyed).isFalse(); + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).hasSize(1); + } + @Test + public void gameTaskRemoved_multipleSessions_destroysOnlyThatGameSession() + throws Exception { mGameServiceProviderInstance.start(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void allGameTasksRemoved_destroysAllGameSessions() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + public void allGameTasksRemoved_destroysAllGameSessionsAndGameSessionServiceIsDisconnected() { + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); - mGameServiceProviderInstance.start(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); dispatchTaskRemoved(11); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTasksCreated_afterAllPreviousSessionsDestroyed_createsSession() + public void createSessionRequested_afterAllPreviousSessionsDestroyed_createsSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); - CreateGameSessionRequest createGameSessionRequest12 = - new CreateGameSessionRequest(12, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> unusedGameSession12Future = - captureCreateGameSessionFuture(createGameSessionRequest12); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); - mGameServiceProviderInstance.start(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); dispatchTaskRemoved(11); - dispatchTaskCreated(12, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession12 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession12); + startTask(12, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(12); + + FakeGameSession gameSession12 = new FakeGameSession(); + SurfacePackage mockSurfacePackage12 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(12) + .complete(new CreateGameSessionResult(gameSession12, mockSurfacePackage12)); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest12), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); assertThat(gameSession12.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(2); } @Test public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); - mGameServiceProviderInstance.start(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } - private Supplier<AndroidFuture<IBinder>> captureCreateGameSessionFuture( - CreateGameSessionRequest expectedCreateGameSessionRequest) throws Exception { - final AtomicReference<AndroidFuture<IBinder>> gameSessionFuture = new AtomicReference<>(); - doAnswer(invocation -> { - gameSessionFuture.set(invocation.getArgument(1)); - return null; - }).when(mMockGameSessionService).create(eq(expectedCreateGameSessionRequest), any()); + private void startTask(int taskId, ComponentName componentName) { + RunningTaskInfo runningTaskInfo = new RunningTaskInfo(); + runningTaskInfo.taskId = taskId; + runningTaskInfo.displayId = 1; + runningTaskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 500, 800)); + mRunningTaskInfos.add(runningTaskInfo); + + dispatchTaskCreated(taskId, componentName); + } - return gameSessionFuture::get; + private void stopTask(int taskId) { + mRunningTaskInfos.removeIf(runningTaskInfo -> runningTaskInfo.taskId == taskId); + dispatchTaskRemoved(taskId); } + private void dispatchTaskRemoved(int taskId) { dispatchTaskChangeEvent(taskStackListener -> { taskStackListener.onTaskRemoved(taskId); @@ -549,7 +609,113 @@ public final class GameServiceProviderInstanceImplTest { } } - private static class IGameSessionStub extends IGameSession.Stub { + static final class FakeGameService extends IGameService.Stub { + private IGameServiceController mGameServiceController; + + public enum GameServiceState { + DISCONNECTED, + CONNECTED, + } + + private ArrayList<GameStartedEvent> mGameStartedEvents = new ArrayList<>(); + private int mConnectedCount = 0; + private GameServiceState mGameServiceState = GameServiceState.DISCONNECTED; + + public GameServiceState getState() { + return mGameServiceState; + } + + public int getConnectedCount() { + return mConnectedCount; + } + + public ArrayList<GameStartedEvent> getGameStartedEvents() { + return mGameStartedEvents; + } + + @Override + public void connected(IGameServiceController gameServiceController) { + Preconditions.checkState(mGameServiceState == GameServiceState.DISCONNECTED); + + mGameServiceState = GameServiceState.CONNECTED; + mConnectedCount += 1; + mGameServiceController = gameServiceController; + } + + @Override + public void disconnected() { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + mGameServiceState = GameServiceState.DISCONNECTED; + mGameServiceController = null; + } + + @Override + public void gameStarted(GameStartedEvent gameStartedEvent) { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + mGameStartedEvents.add(gameStartedEvent); + } + + public void requestCreateGameSession(int task) { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + try { + mGameServiceController.createGameSession(task); + } catch (RemoteException ex) { + throw new AssertionError(ex); + } + } + } + + static final class FakeGameSessionService extends IGameSessionService.Stub { + + private final ArrayList<CapturedCreateInvocation> mCapturedCreateInvocations = + new ArrayList<>(); + private final HashMap<Integer, AndroidFuture<CreateGameSessionResult>> + mPendingCreateGameSessionResultFutures = + new HashMap<>(); + + public static final class CapturedCreateInvocation { + private final CreateGameSessionRequest mCreateGameSessionRequest; + private final GameSessionViewHostConfiguration mGameSessionViewHostConfiguration; + + CapturedCreateInvocation( + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration) { + mCreateGameSessionRequest = createGameSessionRequest; + mGameSessionViewHostConfiguration = gameSessionViewHostConfiguration; + } + } + + public ArrayList<CapturedCreateInvocation> getCapturedCreateInvocations() { + return mCapturedCreateInvocations; + } + + public AndroidFuture<CreateGameSessionResult> removePendingFutureForTaskId(int taskId) { + return mPendingCreateGameSessionResultFutures.remove(taskId); + } + + @Override + public void create( + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + AndroidFuture createGameSessionResultFuture) { + + mCapturedCreateInvocations.add( + new CapturedCreateInvocation( + createGameSessionRequest, + gameSessionViewHostConfiguration)); + + Preconditions.checkState(!mPendingCreateGameSessionResultFutures.containsKey( + createGameSessionRequest.getTaskId())); + mPendingCreateGameSessionResultFutures.put( + createGameSessionRequest.getTaskId(), + createGameSessionResultFuture); + } + } + + private static class FakeGameSession extends IGameSession.Stub { boolean mIsDestroyed = false; @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java index edf68166bb7d..1a5888e7dd76 100644 --- a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java @@ -183,7 +183,8 @@ public class AppCompatOverridesServiceTest { mOverridesToRemoveByPackageConfigCaptor.getValue().packageNameToOverridesToRemove; Map<Long, PackageOverride> addedOverrides; assertThat(packageNameToAddedOverrides.keySet()).containsExactly(PACKAGE_1, PACKAGE_3); - assertThat(packageNameToRemovedOverrides.keySet()).containsExactly(PACKAGE_3, PACKAGE_4); + assertThat(packageNameToRemovedOverrides.keySet()).containsExactly(PACKAGE_2, PACKAGE_3, + PACKAGE_4); // Package 1 addedOverrides = packageNameToAddedOverrides.get(PACKAGE_1).overrides; assertThat(addedOverrides).hasSize(3); @@ -193,6 +194,9 @@ public class AppCompatOverridesServiceTest { new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build()); assertThat(addedOverrides.get(789L)).isEqualTo( new PackageOverride.Builder().setEnabled(false).build()); + // Package 2 + assertThat(packageNameToRemovedOverrides.get(PACKAGE_2).changeIds).containsExactly(123L, + 456L, 789L); // Package 3 addedOverrides = packageNameToAddedOverrides.get(PACKAGE_3).overrides; assertThat(addedOverrides).hasSize(1); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index 0c3e472b46a8..bdeb2b4fd839 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -122,15 +122,14 @@ public class JobSchedulerServiceTest { .when(() -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mock(AppStandbyInternal.class)) .when(() -> LocalServices.getService(AppStandbyInternal.class)); + doReturn(mock(BatteryManagerInternal.class)) + .when(() -> LocalServices.getService(BatteryManagerInternal.class)); doReturn(mock(UsageStatsManagerInternal.class)) .when(() -> LocalServices.getService(UsageStatsManagerInternal.class)); when(mContext.getString(anyInt())).thenReturn("some_test_string"); // Called in BackgroundJobsController constructor. doReturn(mock(AppStateTrackerImpl.class)) .when(() -> LocalServices.getService(AppStateTracker.class)); - // Called in BatteryController constructor. - doReturn(mock(BatteryManagerInternal.class)) - .when(() -> LocalServices.getService(BatteryManagerInternal.class)); // Called in ConnectivityController constructor. when(mContext.getSystemService(ConnectivityManager.class)) .thenReturn(mock(ConnectivityManager.class)); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java new file mode 100644 index 000000000000..52d0494bd9f0 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.job.controllers; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.verify; + +import android.app.AppGlobals; +import android.app.job.JobInfo; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManagerInternal; +import android.content.pm.ServiceInfo; +import android.os.BatteryManagerInternal; +import android.os.RemoteException; +import android.util.ArraySet; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.JobSchedulerBackgroundThread; +import com.android.server.LocalServices; +import com.android.server.job.JobSchedulerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +@RunWith(AndroidJUnit4.class) +public class BatteryControllerTest { + private static final int CALLING_UID = 1000; + private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; + private static final int SOURCE_USER_ID = 0; + + private BatteryController mBatteryController; + private BroadcastReceiver mPowerReceiver; + private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); + private int mSourceUid; + + private MockitoSession mMockingSession; + @Mock + private Context mContext; + @Mock + private BatteryManagerInternal mBatteryManagerInternal; + @Mock + private JobSchedulerService mJobSchedulerService; + @Mock + private PackageManagerInternal mPackageManagerInternal; + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(LocalServices.class) + .startMocking(); + + // Called in StateController constructor. + when(mJobSchedulerService.getTestableContext()).thenReturn(mContext); + when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService); + when(mJobSchedulerService.getConstants()).thenReturn(mConstants); + // Called in BatteryController constructor. + doReturn(mBatteryManagerInternal) + .when(() -> LocalServices.getService(BatteryManagerInternal.class)); + // Used in JobStatus. + doReturn(mPackageManagerInternal) + .when(() -> LocalServices.getService(PackageManagerInternal.class)); + + // Initialize real objects. + // Capture the listeners. + ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + mBatteryController = new BatteryController(mJobSchedulerService); + + verify(mContext).registerReceiver(receiverCaptor.capture(), + ArgumentMatchers.argThat(filter -> + filter.hasAction(Intent.ACTION_POWER_CONNECTED) + && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED))); + mPowerReceiver = receiverCaptor.getValue(); + try { + mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0); + // Need to do this since we're using a mock JS and not a real object. + doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE})) + .when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid); + } catch (RemoteException e) { + fail(e.getMessage()); + } + setPowerConnected(false); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private void setBatteryNotLow(boolean notLow) { + doReturn(notLow).when(mJobSchedulerService).isBatteryNotLow(); + synchronized (mBatteryController.mLock) { + mBatteryController.onBatteryStateChangedLocked(); + } + waitForNonDelayedMessagesProcessed(); + } + + private void setCharging() { + doReturn(true).when(mJobSchedulerService).isBatteryCharging(); + synchronized (mBatteryController.mLock) { + mBatteryController.onBatteryStateChangedLocked(); + } + waitForNonDelayedMessagesProcessed(); + } + + private void setDischarging() { + doReturn(false).when(mJobSchedulerService).isBatteryCharging(); + synchronized (mBatteryController.mLock) { + mBatteryController.onBatteryStateChangedLocked(); + } + waitForNonDelayedMessagesProcessed(); + } + + private void setPowerConnected(boolean connected) { + Intent intent = new Intent( + connected ? Intent.ACTION_POWER_CONNECTED : Intent.ACTION_POWER_DISCONNECTED); + mPowerReceiver.onReceive(mContext, intent); + } + + private void setUidBias(int uid, int bias) { + int prevBias = mJobSchedulerService.getUidBias(uid); + doReturn(bias).when(mJobSchedulerService).getUidBias(uid); + synchronized (mBatteryController.mLock) { + mBatteryController.onUidBiasChangedLocked(uid, prevBias, bias); + } + } + + private void trackJobs(JobStatus... jobs) { + for (JobStatus job : jobs) { + synchronized (mBatteryController.mLock) { + mBatteryController.maybeStartTrackingJobLocked(job, null); + } + } + } + + private void waitForNonDelayedMessagesProcessed() { + JobSchedulerBackgroundThread.getHandler().runWithScissors(() -> {}, 15_000); + } + + private JobInfo.Builder createBaseJobInfoBuilder(int jobId) { + return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestBatteryJobService")); + } + + private JobInfo.Builder createBaseJobInfoBuilder(int jobId, String pkgName) { + return new JobInfo.Builder(jobId, new ComponentName(pkgName, "TestBatteryJobService")); + } + + private JobStatus createJobStatus(String testTag, String packageName, int callingUid, + JobInfo jobInfo) { + JobStatus js = JobStatus.createFromJobInfo( + jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag); + js.serviceInfo = mock(ServiceInfo.class); + // Make sure tests aren't passing just because the default bucket is likely ACTIVE. + js.setStandbyBucket(FREQUENT_INDEX); + return js; + } + + @Test + public void testBatteryNotLow() { + JobStatus job1 = createJobStatus("testBatteryNotLow", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(1).setRequiresBatteryNotLow(true).build()); + JobStatus job2 = createJobStatus("testBatteryNotLow", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(2).setRequiresBatteryNotLow(true).build()); + + setBatteryNotLow(false); + trackJobs(job1); + assertFalse(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW)); + + setBatteryNotLow(true); + assertTrue(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW)); + + trackJobs(job2); + assertTrue(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW)); + } + + @Test + public void testCharging_BatteryNotLow() { + JobStatus job1 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(1) + .setRequiresCharging(true) + .setRequiresBatteryNotLow(true).build()); + JobStatus job2 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(2) + .setRequiresCharging(true) + .setRequiresBatteryNotLow(false).build()); + + setBatteryNotLow(true); + setDischarging(); + trackJobs(job1, job2); + assertFalse(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + setCharging(); + assertTrue(job1.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertTrue(job2.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + } + + @Test + public void testTopPowerConnectedExemption() { + final int uid1 = mSourceUid; + final int uid2 = mSourceUid + 1; + final int uid3 = mSourceUid + 2; + JobStatus jobFg = createJobStatus("testTopPowerConnectedExemption", SOURCE_PACKAGE, uid1, + createBaseJobInfoBuilder(1).setRequiresCharging(true).build()); + JobStatus jobFgRunner = createJobStatus("testTopPowerConnectedExemption", + SOURCE_PACKAGE, uid1, + createBaseJobInfoBuilder(2).setRequiresCharging(true).build()); + JobStatus jobFgLow = createJobStatus("testTopPowerConnectedExemption", SOURCE_PACKAGE, uid1, + createBaseJobInfoBuilder(3) + .setRequiresCharging(true) + .setPriority(JobInfo.PRIORITY_LOW) + .build()); + JobStatus jobBg = createJobStatus("testTopPowerConnectedExemption", + "some.background.app", uid2, + createBaseJobInfoBuilder(4, "some.background.app") + .setRequiresCharging(true) + .build()); + JobStatus jobLateFg = createJobStatus("testTopPowerConnectedExemption", + "switch.to.fg", uid3, + createBaseJobInfoBuilder(5, "switch.to.fg").setRequiresCharging(true).build()); + JobStatus jobLateFgLow = createJobStatus("testTopPowerConnectedExemption", + "switch.to.fg", uid3, + createBaseJobInfoBuilder(6, "switch.to.fg") + .setRequiresCharging(true) + .setPriority(JobInfo.PRIORITY_MIN) + .build()); + + setBatteryNotLow(false); + setDischarging(); + setUidBias(uid1, JobInfo.BIAS_TOP_APP); + setUidBias(uid2, JobInfo.BIAS_DEFAULT); + setUidBias(uid3, JobInfo.BIAS_DEFAULT); + + // Jobs are scheduled when power isn't connected. + setPowerConnected(false); + trackJobs(jobFg, jobFgLow, jobBg, jobLateFg, jobLateFgLow); + assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + // Power is connected. TOP app should be allowed to start job DEFAULT+ jobs. + setPowerConnected(true); + assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + // Test that newly scheduled job of TOP app is correctly allowed to run. + trackJobs(jobFgRunner); + assertTrue(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + // Switch top app. New TOP app should be allowed to run job and the running job of + // previously TOP app should be allowed to continue to run. + synchronized (mBatteryController.mLock) { + mBatteryController.prepareForExecutionLocked(jobFgRunner); + } + setUidBias(uid1, JobInfo.BIAS_DEFAULT); + setUidBias(uid2, JobInfo.BIAS_DEFAULT); + setUidBias(uid3, JobInfo.BIAS_TOP_APP); + assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertTrue(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertTrue(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + + setPowerConnected(false); + assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgRunner.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING)); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index a9853bf94eeb..f61d6ca750cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -48,18 +48,14 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.JobInfo; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManagerInternal; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; -import android.os.BatteryManager; -import android.os.BatteryManagerInternal; import android.os.Build; import android.os.Looper; import android.os.SystemClock; @@ -74,7 +70,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @@ -88,8 +83,6 @@ public class ConnectivityControllerTest { @Mock private Context mContext; @Mock - private BatteryManagerInternal mBatteryManagerInternal; - @Mock private ConnectivityManager mConnManager; @Mock private NetworkPolicyManager mNetPolicyManager; @@ -115,9 +108,6 @@ public class ConnectivityControllerTest { LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal); - LocalServices.removeServiceForTest(BatteryManagerInternal.class); - LocalServices.addService(BatteryManagerInternal.class, mBatteryManagerInternal); - when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); // Freeze the clocks at this moment in time @@ -158,18 +148,10 @@ public class ConnectivityControllerTest { .setMinimumNetworkChunkBytes(DataUnit.KIBIBYTES.toBytes(100)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); - final ArgumentCaptor<BroadcastReceiver> chargingCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) - .thenReturn(false); + when(mService.isBatteryCharging()).thenReturn(false); final ConnectivityController controller = new ConnectivityController(mService); - verify(mContext).registerReceiver(chargingCaptor.capture(), - ArgumentMatchers.argThat(filter -> - filter.hasAction(BatteryManager.ACTION_CHARGING) - && filter.hasAction(BatteryManager.ACTION_DISCHARGING))); when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L); - final BroadcastReceiver chargingReceiver = chargingCaptor.getValue(); - chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING)); + controller.onBatteryStateChangedLocked(); // Slow network is too slow assertFalse(controller.isSatisfied(createJobStatus(job), net, @@ -225,17 +207,15 @@ public class ConnectivityControllerTest { createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130) .setLinkDownstreamBandwidthKbps(130).build(), mConstants)); // Slow network is too slow, but device is charging and network is unmetered. - when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) - .thenReturn(true); - chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING)); + when(mService.isBatteryCharging()).thenReturn(true); + controller.onBatteryStateChangedLocked(); assertTrue(controller.isSatisfied(createJobStatus(job), net, createCapabilitiesBuilder().addCapability(NET_CAPABILITY_NOT_METERED) .setLinkUpstreamBandwidthKbps(1).setLinkDownstreamBandwidthKbps(1).build(), mConstants)); - when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) - .thenReturn(false); - chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING)); + when(mService.isBatteryCharging()).thenReturn(false); + controller.onBatteryStateChangedLocked(); when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(60_000L); // Slow network is too slow @@ -259,9 +239,8 @@ public class ConnectivityControllerTest { createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130) .setLinkDownstreamBandwidthKbps(130).build(), mConstants)); // Slow network is too slow, but device is charging and network is unmetered. - when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) - .thenReturn(true); - chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING)); + when(mService.isBatteryCharging()).thenReturn(true); + controller.onBatteryStateChangedLocked(); assertTrue(controller.isSatisfied(createJobStatus(job), net, createCapabilitiesBuilder().addCapability(NET_CAPABILITY_NOT_METERED) .setLinkUpstreamBandwidthKbps(1).setLinkDownstreamBandwidthKbps(1).build(), diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index 95912b26e830..8a954caad939 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -200,8 +200,10 @@ public class PrefetchControllerTest { } private void setUidBias(int uid, int bias) { + int prevBias = mJobSchedulerService.getUidBias(uid); + doReturn(bias).when(mJobSchedulerService).getUidBias(uid); synchronized (mPrefetchController.mLock) { - mPrefetchController.onUidBiasChangedLocked(uid, bias); + mPrefetchController.onUidBiasChangedLocked(uid, prevBias, bias); } } @@ -381,6 +383,10 @@ public class PrefetchControllerTest { inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0)) .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID); + verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1)) + .setWindow( + anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS), + anyLong(), eq(TAG_PREFETCH), any(), any()); assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 300f93feed28..cfae9a3d586a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -63,16 +63,13 @@ import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ServiceInfo; -import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.Handler; import android.os.Looper; @@ -126,7 +123,6 @@ public class QuotaControllerTest { private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; private static final int SOURCE_USER_ID = 0; - private BroadcastReceiver mChargingReceiver; private QuotaController mQuotaController; private QuotaController.QcConstants mQcConstants; private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); @@ -225,8 +221,6 @@ public class QuotaControllerTest { // Initialize real objects. // Capture the listeners. - ArgumentCaptor<BroadcastReceiver> receiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); ArgumentCaptor<IUidObserver> uidObserverCaptor = ArgumentCaptor.forClass(IUidObserver.class); ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor = @@ -236,11 +230,6 @@ public class QuotaControllerTest { mQuotaController = new QuotaController(mJobSchedulerService, mock(BackgroundJobsController.class), mock(ConnectivityController.class)); - verify(mContext).registerReceiver(receiverCaptor.capture(), - ArgumentMatchers.argThat(filter -> - filter.hasAction(BatteryManager.ACTION_CHARGING) - && filter.hasAction(BatteryManager.ACTION_DISCHARGING))); - mChargingReceiver = receiverCaptor.getValue(); verify(mPowerAllowlistInternal) .registerTempAllowlistChangeListener(taChangeCaptor.capture()); mTempAllowlistListener = taChangeCaptor.getValue(); @@ -280,13 +269,17 @@ public class QuotaControllerTest { } private void setCharging() { - Intent intent = new Intent(BatteryManager.ACTION_CHARGING); - mChargingReceiver.onReceive(mContext, intent); + doReturn(true).when(mJobSchedulerService).isBatteryCharging(); + synchronized (mQuotaController.mLock) { + mQuotaController.onBatteryStateChangedLocked(); + } } private void setDischarging() { - Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING); - mChargingReceiver.onReceive(mContext, intent); + doReturn(false).when(mJobSchedulerService).isBatteryCharging(); + synchronized (mQuotaController.mLock) { + mQuotaController.onBatteryStateChangedLocked(); + } } private void setProcessState(int procState) { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java index 93a2d317c40e..b7ab6f80167e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java @@ -109,15 +109,19 @@ public final class FakeGnssHal extends GnssNative.GnssHal { public static class GnssHalBatchingMode { public final long PeriodNanos; + public final float MinUpdateDistanceMeters; public final boolean WakeOnFifoFull; GnssHalBatchingMode() { PeriodNanos = 0; + MinUpdateDistanceMeters = 0.0f; WakeOnFifoFull = false; } - public GnssHalBatchingMode(long periodNanos, boolean wakeOnFifoFull) { + public GnssHalBatchingMode(long periodNanos, float minUpdateDistanceMeters, + boolean wakeOnFifoFull) { PeriodNanos = periodNanos; + MinUpdateDistanceMeters = minUpdateDistanceMeters; WakeOnFifoFull = wakeOnFifoFull; } @@ -132,12 +136,13 @@ public final class FakeGnssHal extends GnssNative.GnssHal { GnssHalBatchingMode that = (GnssHalBatchingMode) o; return PeriodNanos == that.PeriodNanos + && MinUpdateDistanceMeters == that.MinUpdateDistanceMeters && WakeOnFifoFull == that.WakeOnFifoFull; } @Override public int hashCode() { - return Objects.hash(PeriodNanos, WakeOnFifoFull); + return Objects.hash(PeriodNanos, MinUpdateDistanceMeters, WakeOnFifoFull); } } @@ -570,9 +575,11 @@ public final class FakeGnssHal extends GnssNative.GnssHal { protected void cleanupBatching() {} @Override - protected boolean startBatch(long periodNanos, boolean wakeOnFifoFull) { + protected boolean startBatch(long periodNanos, float minUpdateDistanceMeters, + boolean wakeOnFifoFull) { mState.mBatchingStarted = true; - mState.mBatchingMode = new GnssHalBatchingMode(periodNanos, wakeOnFifoFull); + mState.mBatchingMode = new GnssHalBatchingMode(periodNanos, minUpdateDistanceMeters, + wakeOnFifoFull); return true; } @@ -673,7 +680,8 @@ public final class FakeGnssHal extends GnssNative.GnssHal { protected void setAgpsSetId(int type, String setId) {} @Override - protected void setAgpsReferenceLocationCellId(int type, int mcc, int mnc, int lac, int cid) {} + protected void setAgpsReferenceLocationCellId(int type, int mcc, int mnc, int lac, long cid, + int tac, int pcid, int arfcn) {} @Override protected boolean isPsdsSupported() { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 04a6eeecb320..c2e0a04e3caa 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -27,8 +27,6 @@ import android.content.pm.ServiceInfo import android.content.pm.Signature import android.content.pm.SigningDetails import android.content.pm.UserInfo -import android.content.pm.parsing.ParsingPackage -import android.content.pm.parsing.ParsingPackageUtils import android.content.pm.parsing.result.ParseTypeImpl import android.content.res.Resources import android.hardware.display.DisplayManager @@ -68,6 +66,8 @@ import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.pm.permission.PermissionManagerServiceInternal +import com.android.server.pm.pkg.parsing.ParsingPackage +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.testutils.TestHandler import com.android.server.testutils.mock diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt index 6de12cb98719..2735e3d21ef7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -196,7 +196,8 @@ class SharedLibrariesImplTest { val newLibSetting = addPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L, staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L) - val latestInfo = mSharedLibrariesImpl.getLatestSharedLibraVersionLPr(newLibSetting.pkg)!! + val latestInfo = + mSharedLibrariesImpl.getLatestStaticSharedLibraVersionLPr(newLibSetting.pkg)!! assertThat(latestInfo).isNotNull() assertThat(latestInfo.name).isEqualTo(STATIC_LIB_NAME) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index f2415b4665d5..bdfdf7723c02 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -158,10 +158,10 @@ public class StagingManagerTest { mStagingManager.restoreSessions(Arrays.asList(session1, session2), true); - assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed"); - assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed"); } @@ -247,12 +247,12 @@ public class StagingManagerTest { verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); assertThat(apexSession.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a " + "staged session supposed to be activated"); assertThat(apkSession.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } @@ -303,22 +303,22 @@ public class StagingManagerTest { verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); assertThat(apexSession1.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. " + "Error: Failed for test"); assertThat(apexSession2.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't " + "activate nor fail. Marking it as failed anyway."); assertThat(apexSession3.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a " + "staged session supposed to be activated"); assertThat(apkSession.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } @@ -351,12 +351,12 @@ public class StagingManagerTest { verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); assertThat(apexSession.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't " + "activate nor fail. Marking it as failed anyway."); assertThat(apkSession.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } @@ -445,11 +445,11 @@ public class StagingManagerTest { verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); assertThat(apexSession.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state"); assertThat(apkSession.getErrorCode()) - .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } @@ -754,7 +754,7 @@ public class StagingManagerTest { /* isReady */ false, /* isFailed */ false, /* isApplied */false, - /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR, + /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.SESSION_NO_ERROR, /* stagedSessionErrorMessage */ "no error"); StagingManager.StagedSession stagedSession = spy(session.mStagedSession); diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java index b65c3e939954..0411b941c4a4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java @@ -263,7 +263,7 @@ public class PowerManagerServiceMockingTest { @Test public void testUserActivityOnDeviceStateChange() { createService(); - mService.systemReady(null); + mService.systemReady(); mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); final DisplayInfo info = new DisplayInfo(); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 966675819069..0b488b2add8e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -153,6 +153,9 @@ public class WallpaperManagerServiceTests { sContext.getTestablePermissions().setPermission( android.Manifest.permission.SET_WALLPAPER, PackageManager.PERMISSION_GRANTED); + sContext.getTestablePermissions().setPermission( + android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT, + PackageManager.PERMISSION_GRANTED); doNothing().when(sContext).sendBroadcastAsUser(any(), any()); //Wallpaper components @@ -433,6 +436,15 @@ public class WallpaperManagerServiceTests { assertTrue(timestamps[1] > timestamps[0]); } + @Test + public void testSetWallpaperDimAmount() throws RemoteException { + mService.switchUser(USER_SYSTEM, null); + float dimAmount = 0.7f; + mService.setWallpaperDimAmount(dimAmount); + assertEquals("Getting dim amount should match after setting the dim amount", + mService.getWallpaperDimAmount(), dimAmount, 0.0); + } + // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for // non-current user must not bind to wallpaper service. private void verifyNoConnectionBeforeLastUser(int lastUserId) { diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index c3a364e723fb..f24059c22dd7 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -25,6 +25,7 @@ android_test { "test-apps/JobTestApp/src/**/*.java", "test-apps/SuspendTestApp/src/**/*.java", + ":service-bluetooth-tests-sources", // TODO(b/214988855) : Remove once framework-bluetooth jar is ready ], static_libs: [ "frameworks-base-testutils", @@ -181,18 +182,41 @@ filegroup { java_genrule { name: "FrameworksServicesTests_apks_as_resources", srcs: [ - ":FrameworksCoreTests_install_complete_package_info", + ":FrameworksServicesTests_install", + ":FrameworksServicesTests_install_bad_dex", + ":FrameworksServicesTests_install_complete_package_info", + ":FrameworksServicesTests_install_decl_perm", ":FrameworksServicesTests_install_intent_filters", + ":FrameworksServicesTests_install_loc_auto", + ":FrameworksServicesTests_install_loc_internal", + ":FrameworksServicesTests_install_loc_sdcard", + ":FrameworksServicesTests_install_loc_unspecified", ":FrameworksServicesTests_install_split_base", ":FrameworksServicesTests_install_split_feature_a", + ":FrameworksServicesTests_install_use_perm_good", + ":FrameworksServicesTests_install_uses_feature", ":FrameworksServicesTests_install_uses_sdk_0", ":FrameworksServicesTests_install_uses_sdk_q0", ":FrameworksServicesTests_install_uses_sdk_q0_r0", - ":FrameworksServicesTests_install_uses_sdk_r_none", ":FrameworksServicesTests_install_uses_sdk_r0", ":FrameworksServicesTests_install_uses_sdk_r5", + ":FrameworksServicesTests_install_uses_sdk_r_none", ":FrameworksServicesTests_install_uses_sdk_r0_s0", ":FrameworksServicesTests_install_uses_sdk_r0_s5", + ":FrameworksServicesTests_keyset_permdef_sa_unone", + ":FrameworksServicesTests_keyset_permuse_sa_ua_ub", + ":FrameworksServicesTests_keyset_permuse_sb_ua_ub", + ":FrameworksServicesTests_keyset_sa_ua", + ":FrameworksServicesTests_keyset_sa_ua_ub", + ":FrameworksServicesTests_keyset_sa_uab", + ":FrameworksServicesTests_keyset_sa_ub", + ":FrameworksServicesTests_keyset_sa_unone", + ":FrameworksServicesTests_keyset_sab_ua", + ":FrameworksServicesTests_keyset_sau_ub", + ":FrameworksServicesTests_keyset_sb_ua", + ":FrameworksServicesTests_keyset_sb_ub", + ":FrameworksServicesTests_keyset_splat_api", + ":FrameworksServicesTests_keyset_splata_api", ], out: ["FrameworkServicesTests_apks_as_resources.res.zip"], tools: ["soong_zip"], diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 202a54d36cc9..d9f73d9aa54e 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -95,9 +95,12 @@ <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/> <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/> <uses-permission android:name="android.permission.KILL_UID"/> + <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/> <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/> + <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" /> + <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> </queries> @@ -268,4 +271,11 @@ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.servicestests" android:label="Frameworks Services Tests"/> + <key-sets> + <key-set android:name="A" > + <public-key android:name="keyA" + android:value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMpNthdOxud7roPDZMMomOqXgJJdRfIWpkKEqmC61Mv+Nf6QY3TorEwJeghjSmqj7IbBKrtvfQq4E2XJO1HuspmQO4Ng2gvn+r+6EwNfKc9k55d6s+27SR867jKurBbHNtZMG+tjL1yH4r+tNzcuJCsgyAFqLmxFdcxEwzNvREyRpoYc5RDR0mmTwkMCUhJ6CId1EYEKiCEdNzxv+fWPEb21u+/MWpleGCILs8kglRVb2q/WOzAAvGr4FY5plfaE6N+lr7+UschQ+aMi1+uqewo2o0qPFVmZP5hnwj55K4UMzu/NhhDqQQsX4cSGES1KgHo5MTqRqZjN/I7emw5pFQIDAQAB"/> + </key-set> + <upgrade-key-set android:name="A"/> + </key-sets> </manifest> diff --git a/services/tests/servicestests/OWNERS b/services/tests/servicestests/OWNERS index 0fb0c3021486..d07848e1bbe2 100644 --- a/services/tests/servicestests/OWNERS +++ b/services/tests/servicestests/OWNERS @@ -1 +1,2 @@ include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS +per-file GameManagerServiceSettingsTests.java = file:/GAME_MANAGER_OWNERS diff --git a/core/tests/coretests/apks/install/Android.bp b/services/tests/servicestests/apks/install/Android.bp index 652b49130433..12175fdb7327 100644 --- a/core/tests/coretests/apks/install/Android.bp +++ b/services/tests/servicestests/apks/install/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install/AndroidManifest.xml b/services/tests/servicestests/apks/install/AndroidManifest.xml index 60f1ba043c2c..60f1ba043c2c 100644 --- a/core/tests/coretests/apks/install/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install/res/values/strings.xml b/services/tests/servicestests/apks/install/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install/res/values/strings.xml +++ b/services/tests/servicestests/apks/install/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_bad_dex/Android.bp b/services/tests/servicestests/apks/install_bad_dex/Android.bp index 7b96c9b47553..ad7566810a62 100644 --- a/core/tests/coretests/apks/install_bad_dex/Android.bp +++ b/services/tests/servicestests/apks/install_bad_dex/Android.bp @@ -8,25 +8,25 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_bad_dex_", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_bad_dex_", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["src/**/*.java"], } // Inject bad classes.dex file. java_genrule { - name: "FrameworksCoreTests_install_bad_dex", + name: "FrameworksServicesTests_install_bad_dex", tools: [ "soong_zip", "merge_zips", ], srcs: [ - ":FrameworksCoreTests_install_bad_dex_", + ":FrameworksServicesTests_install_bad_dex_", "classes.dex", ], - out: ["FrameworksCoreTests_install_bad_dex.apk"], + out: ["FrameworksServicesTests_install_bad_dex.apk"], cmd: "$(location soong_zip) -o $(genDir)/classes.dex.zip -j -f $(location classes.dex) && " + "$(location merge_zips) -ignore-duplicates $(out) $(genDir)/classes.dex.zip " + - "$(location :FrameworksCoreTests_install_bad_dex_)", + "$(location :FrameworksServicesTests_install_bad_dex_)", } diff --git a/core/tests/coretests/apks/install_bad_dex/AndroidManifest.xml b/services/tests/servicestests/apks/install_bad_dex/AndroidManifest.xml index 76f0fe588f7b..76f0fe588f7b 100644 --- a/core/tests/coretests/apks/install_bad_dex/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_bad_dex/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_bad_dex/classes.dex b/services/tests/servicestests/apks/install_bad_dex/classes.dex index 284b6d400e15..284b6d400e15 100644 --- a/core/tests/coretests/apks/install_bad_dex/classes.dex +++ b/services/tests/servicestests/apks/install_bad_dex/classes.dex diff --git a/core/tests/coretests/apks/install_bad_dex/res/values/strings.xml b/services/tests/servicestests/apks/install_bad_dex/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_bad_dex/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_bad_dex/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java b/services/tests/servicestests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java index 10d0551a3a6f..10d0551a3a6f 100644 --- a/core/tests/coretests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java +++ b/services/tests/servicestests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java diff --git a/services/tests/servicestests/apks/install_complete_package_info/Android.bp b/services/tests/servicestests/apks/install_complete_package_info/Android.bp new file mode 100644 index 000000000000..98aa750231d7 --- /dev/null +++ b/services/tests/servicestests/apks/install_complete_package_info/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_install_complete_package_info", + defaults: ["FrameworksServicesTests_apks_defaults"], + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml b/services/tests/servicestests/apks/install_complete_package_info/AndroidManifest.xml index 2c430e08c16a..2c430e08c16a 100644 --- a/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_complete_package_info/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java index 10d0551a3a6f..10d0551a3a6f 100644 --- a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java +++ b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java diff --git a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java index 59f9f10c6efe..59f9f10c6efe 100644 --- a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java +++ b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java diff --git a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java index 21f6263a38bc..21f6263a38bc 100644 --- a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java +++ b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java diff --git a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java index b330e75308f9..b330e75308f9 100644 --- a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java +++ b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java diff --git a/core/tests/coretests/apks/install_decl_perm/Android.bp b/services/tests/servicestests/apks/install_decl_perm/Android.bp index bf1f0de9672a..ef65f5de6a1b 100644 --- a/core/tests/coretests/apks/install_decl_perm/Android.bp +++ b/services/tests/servicestests/apks/install_decl_perm/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_decl_perm", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_decl_perm", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install_decl_perm/AndroidManifest.xml b/services/tests/servicestests/apks/install_decl_perm/AndroidManifest.xml index 6a0c4215ce52..6a0c4215ce52 100644 --- a/core/tests/coretests/apks/install_decl_perm/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_decl_perm/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_decl_perm/res/values/strings.xml b/services/tests/servicestests/apks/install_decl_perm/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_decl_perm/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_decl_perm/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_loc_auto/Android.bp b/services/tests/servicestests/apks/install_loc_auto/Android.bp index 37daf7608a43..4e4ae526d0dc 100644 --- a/core/tests/coretests/apks/install_loc_auto/Android.bp +++ b/services/tests/servicestests/apks/install_loc_auto/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_loc_auto", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_loc_auto", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install_loc_auto/AndroidManifest.xml b/services/tests/servicestests/apks/install_loc_auto/AndroidManifest.xml index 5a903e2903d3..5a903e2903d3 100644 --- a/core/tests/coretests/apks/install_loc_auto/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_loc_auto/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_loc_auto/res/values/strings.xml b/services/tests/servicestests/apks/install_loc_auto/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_loc_auto/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_loc_auto/res/values/strings.xml diff --git a/services/tests/servicestests/apks/install_loc_internal/Android.bp b/services/tests/servicestests/apks/install_loc_internal/Android.bp new file mode 100644 index 000000000000..39cdd5178a6d --- /dev/null +++ b/services/tests/servicestests/apks/install_loc_internal/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_install_loc_internal", + defaults: ["FrameworksServicesTests_apks_defaults"], + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_loc_internal/AndroidManifest.xml b/services/tests/servicestests/apks/install_loc_internal/AndroidManifest.xml index 2568f3729523..2568f3729523 100644 --- a/core/tests/coretests/apks/install_loc_internal/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_loc_internal/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_loc_internal/res/values/strings.xml b/services/tests/servicestests/apks/install_loc_internal/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_loc_internal/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_loc_internal/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_loc_internal/Android.bp b/services/tests/servicestests/apks/install_loc_sdcard/Android.bp index 3e233132b58d..ed82793ff6e6 100644 --- a/core/tests/coretests/apks/install_loc_internal/Android.bp +++ b/services/tests/servicestests/apks/install_loc_sdcard/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_loc_internal", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_loc_sdcard", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install_loc_sdcard/AndroidManifest.xml b/services/tests/servicestests/apks/install_loc_sdcard/AndroidManifest.xml index 647f4e5f60ff..647f4e5f60ff 100644 --- a/core/tests/coretests/apks/install_loc_sdcard/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_loc_sdcard/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_loc_sdcard/res/values/strings.xml b/services/tests/servicestests/apks/install_loc_sdcard/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_loc_sdcard/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_loc_sdcard/res/values/strings.xml diff --git a/services/tests/servicestests/apks/install_loc_unspecified/Android.bp b/services/tests/servicestests/apks/install_loc_unspecified/Android.bp new file mode 100644 index 000000000000..fd15cb8e9f92 --- /dev/null +++ b/services/tests/servicestests/apks/install_loc_unspecified/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_install_loc_unspecified", + defaults: ["FrameworksServicesTests_apks_defaults"], + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_loc_unspecified/AndroidManifest.xml b/services/tests/servicestests/apks/install_loc_unspecified/AndroidManifest.xml index 07b1eb3105e3..07b1eb3105e3 100644 --- a/core/tests/coretests/apks/install_loc_unspecified/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_loc_unspecified/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_loc_unspecified/res/values/strings.xml b/services/tests/servicestests/apks/install_loc_unspecified/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_loc_unspecified/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_loc_unspecified/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_complete_package_info/Android.bp b/services/tests/servicestests/apks/install_use_perm_good/Android.bp index 3fee0c6e7f7c..959ffbcc48b1 100644 --- a/core/tests/coretests/apks/install_complete_package_info/Android.bp +++ b/services/tests/servicestests/apks/install_use_perm_good/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_complete_package_info", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_use_perm_good", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install_use_perm_good/AndroidManifest.xml b/services/tests/servicestests/apks/install_use_perm_good/AndroidManifest.xml index dadce7d55fba..dadce7d55fba 100644 --- a/core/tests/coretests/apks/install_use_perm_good/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_use_perm_good/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_use_perm_good/res/values/strings.xml b/services/tests/servicestests/apks/install_use_perm_good/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_use_perm_good/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_use_perm_good/res/values/strings.xml diff --git a/services/tests/servicestests/apks/install_uses_feature/Android.bp b/services/tests/servicestests/apks/install_uses_feature/Android.bp new file mode 100644 index 000000000000..fa25af4c5b30 --- /dev/null +++ b/services/tests/servicestests/apks/install_uses_feature/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_install_uses_feature", + defaults: ["FrameworksServicesTests_apks_defaults"], + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_uses_feature/AndroidManifest.xml b/services/tests/servicestests/apks/install_uses_feature/AndroidManifest.xml index ecbd7c423370..ecbd7c423370 100644 --- a/core/tests/coretests/apks/install_uses_feature/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_uses_feature/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_uses_feature/res/values/strings.xml b/services/tests/servicestests/apks/install_uses_feature/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_uses_feature/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_uses_feature/res/values/strings.xml diff --git a/services/tests/servicestests/apks/keyset/Android.bp b/services/tests/servicestests/apks/keyset/Android.bp new file mode 100644 index 000000000000..ce7919c9d0a8 --- /dev/null +++ b/services/tests/servicestests/apks/keyset/Android.bp @@ -0,0 +1,129 @@ +//apks signed by keyset_A +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_unone", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uNone/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_ua", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uA/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uB/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_uab", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uAB/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_ua_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uAuB/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_permdef_sa_unone", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "permDef/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_permuse_sa_ua_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "permUse/AndroidManifest.xml", +} + +//apks signed by keyset_B +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sb_ua", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_B_cert", + manifest: "uA/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sb_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_B_cert", + manifest: "uB/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_permuse_sb_ua_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_B_cert", + manifest: "permUse/AndroidManifest.xml", +} + +//apks signed by keyset_A and keyset_B +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sab_ua", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + additional_certificates: [":FrameworksServicesTests_keyset_B_cert"], + manifest: "uA/AndroidManifest.xml", +} + +//apks signed by keyset_A and unit_test +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sau_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + additional_certificates: [":FrameworksServicesTests_keyset_B_cert"], + manifest: "uB/AndroidManifest.xml", +} + +//apks signed by platform only +android_test_helper_app { + name: "FrameworksServicesTests_keyset_splat_api", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: "platform", + manifest: "api_test/AndroidManifest.xml", +} + +//apks signed by platform and keyset_A +android_test_helper_app { + name: "FrameworksServicesTests_keyset_splata_api", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: "platform", + additional_certificates: [":FrameworksServicesTests_keyset_A_cert"], + manifest: "api_test/AndroidManifest.xml", +} diff --git a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/api_test/AndroidManifest.xml index 2897bd5d0b55..2897bd5d0b55 100644 --- a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/api_test/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/permDef/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/permDef/AndroidManifest.xml index 8f7ad4aec603..8f7ad4aec603 100644 --- a/core/tests/coretests/apks/keyset/permDef/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/permDef/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/permUse/AndroidManifest.xml index c56079f8d3cf..c56079f8d3cf 100644 --- a/core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/permUse/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/res/values/strings.xml b/services/tests/servicestests/apks/keyset/res/values/strings.xml index d811ec29ef19..d811ec29ef19 100644 --- a/core/tests/coretests/apks/keyset/res/values/strings.xml +++ b/services/tests/servicestests/apks/keyset/res/values/strings.xml diff --git a/core/tests/coretests/apks/keyset/uA/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uA/AndroidManifest.xml index 8c440f54993b..8c440f54993b 100644 --- a/core/tests/coretests/apks/keyset/uA/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uA/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uAB/AndroidManifest.xml index 015c3ad2993e..015c3ad2993e 100644 --- a/core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uAB/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uAuB/AndroidManifest.xml index 9491dbeacc48..9491dbeacc48 100644 --- a/core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uAuB/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/uB/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uB/AndroidManifest.xml index f4918400f38b..f4918400f38b 100644 --- a/core/tests/coretests/apks/keyset/uB/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uB/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/uNone/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uNone/AndroidManifest.xml index 9c9ef2b2ea6f..9c9ef2b2ea6f 100644 --- a/core/tests/coretests/apks/keyset/uNone/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uNone/AndroidManifest.xml diff --git a/services/tests/servicestests/certs/Android.bp b/services/tests/servicestests/certs/Android.bp new file mode 100644 index 000000000000..61367c0a370b --- /dev/null +++ b/services/tests/servicestests/certs/Android.bp @@ -0,0 +1,20 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + // SPDX-license-identifier-MIT + // SPDX-license-identifier-Unicode-DFS + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app_certificate { + name: "FrameworksServicesTests_keyset_A_cert", + certificate: "keyset_A", +} + +android_app_certificate { + name: "FrameworksServicesTests_keyset_B_cert", + certificate: "keyset_B", +} diff --git a/services/tests/servicestests/certs/README b/services/tests/servicestests/certs/README new file mode 100644 index 000000000000..00917a188934 --- /dev/null +++ b/services/tests/servicestests/certs/README @@ -0,0 +1,4 @@ +Generate with: + +development/tools/make_key unit_test '/CN=unit_test' +development/tools/make_key unit_test_diff '/CN=unit_test_diff' diff --git a/core/tests/coretests/certs/keyset_A.pk8 b/services/tests/servicestests/certs/keyset_A.pk8 Binary files differindex 407631376ed9..407631376ed9 100644 --- a/core/tests/coretests/certs/keyset_A.pk8 +++ b/services/tests/servicestests/certs/keyset_A.pk8 diff --git a/core/tests/coretests/certs/keyset_A.x509.pem b/services/tests/servicestests/certs/keyset_A.x509.pem index 548bf130593f..548bf130593f 100644 --- a/core/tests/coretests/certs/keyset_A.x509.pem +++ b/services/tests/servicestests/certs/keyset_A.x509.pem diff --git a/core/tests/coretests/certs/keyset_B.pk8 b/services/tests/servicestests/certs/keyset_B.pk8 Binary files differindex 839d96cb8622..839d96cb8622 100644 --- a/core/tests/coretests/certs/keyset_B.pk8 +++ b/services/tests/servicestests/certs/keyset_B.pk8 diff --git a/core/tests/coretests/certs/keyset_B.x509.pem b/services/tests/servicestests/certs/keyset_B.x509.pem index 537e047d90e8..537e047d90e8 100644 --- a/core/tests/coretests/certs/keyset_B.x509.pem +++ b/services/tests/servicestests/certs/keyset_B.x509.pem diff --git a/core/tests/coretests/res/raw/install_app1_cert1 b/services/tests/servicestests/res/raw/install_app1_cert1 Binary files differindex f880c0b1126b..f880c0b1126b 100644 --- a/core/tests/coretests/res/raw/install_app1_cert1 +++ b/services/tests/servicestests/res/raw/install_app1_cert1 diff --git a/core/tests/coretests/res/raw/install_app1_cert1_cert2 b/services/tests/servicestests/res/raw/install_app1_cert1_cert2 Binary files differindex ed89fbb2eb35..ed89fbb2eb35 100644 --- a/core/tests/coretests/res/raw/install_app1_cert1_cert2 +++ b/services/tests/servicestests/res/raw/install_app1_cert1_cert2 diff --git a/core/tests/coretests/res/raw/install_app1_cert2 b/services/tests/servicestests/res/raw/install_app1_cert2 Binary files differindex 5551c7ebff07..5551c7ebff07 100644 --- a/core/tests/coretests/res/raw/install_app1_cert2 +++ b/services/tests/servicestests/res/raw/install_app1_cert2 diff --git a/core/tests/coretests/res/raw/install_app1_cert3 b/services/tests/servicestests/res/raw/install_app1_cert3 Binary files differindex 0d1a4dcce854..0d1a4dcce854 100644 --- a/core/tests/coretests/res/raw/install_app1_cert3 +++ b/services/tests/servicestests/res/raw/install_app1_cert3 diff --git a/core/tests/coretests/res/raw/install_app1_cert3_cert4 b/services/tests/servicestests/res/raw/install_app1_cert3_cert4 Binary files differindex 29ff3b6c5971..29ff3b6c5971 100644 --- a/core/tests/coretests/res/raw/install_app1_cert3_cert4 +++ b/services/tests/servicestests/res/raw/install_app1_cert3_cert4 diff --git a/core/tests/coretests/res/raw/install_app1_cert5 b/services/tests/servicestests/res/raw/install_app1_cert5 Binary files differindex 138b6113ea6b..138b6113ea6b 100644 --- a/core/tests/coretests/res/raw/install_app1_cert5 +++ b/services/tests/servicestests/res/raw/install_app1_cert5 diff --git a/core/tests/coretests/res/raw/install_app1_cert5_rotated_cert6 b/services/tests/servicestests/res/raw/install_app1_cert5_rotated_cert6 Binary files differindex 2da2436d9b16..2da2436d9b16 100644 --- a/core/tests/coretests/res/raw/install_app1_cert5_rotated_cert6 +++ b/services/tests/servicestests/res/raw/install_app1_cert5_rotated_cert6 diff --git a/core/tests/coretests/res/raw/install_app1_cert6 b/services/tests/servicestests/res/raw/install_app1_cert6 Binary files differindex 256e03a2de54..256e03a2de54 100644 --- a/core/tests/coretests/res/raw/install_app1_cert6 +++ b/services/tests/servicestests/res/raw/install_app1_cert6 diff --git a/core/tests/coretests/res/raw/install_app1_unsigned b/services/tests/servicestests/res/raw/install_app1_unsigned Binary files differindex 01b39e28866f..01b39e28866f 100644 --- a/core/tests/coretests/res/raw/install_app1_unsigned +++ b/services/tests/servicestests/res/raw/install_app1_unsigned diff --git a/core/tests/coretests/res/raw/install_app2_cert1 b/services/tests/servicestests/res/raw/install_app2_cert1 Binary files differindex 12bfc6f5aa5d..12bfc6f5aa5d 100644 --- a/core/tests/coretests/res/raw/install_app2_cert1 +++ b/services/tests/servicestests/res/raw/install_app2_cert1 diff --git a/core/tests/coretests/res/raw/install_app2_cert1_cert2 b/services/tests/servicestests/res/raw/install_app2_cert1_cert2 Binary files differindex 39095ba7faa2..39095ba7faa2 100644 --- a/core/tests/coretests/res/raw/install_app2_cert1_cert2 +++ b/services/tests/servicestests/res/raw/install_app2_cert1_cert2 diff --git a/core/tests/coretests/res/raw/install_app2_cert2 b/services/tests/servicestests/res/raw/install_app2_cert2 Binary files differindex f6d965be6f37..f6d965be6f37 100644 --- a/core/tests/coretests/res/raw/install_app2_cert2 +++ b/services/tests/servicestests/res/raw/install_app2_cert2 diff --git a/core/tests/coretests/res/raw/install_app2_cert3 b/services/tests/servicestests/res/raw/install_app2_cert3 Binary files differindex 3d8b6f17f397..3d8b6f17f397 100644 --- a/core/tests/coretests/res/raw/install_app2_cert3 +++ b/services/tests/servicestests/res/raw/install_app2_cert3 diff --git a/core/tests/coretests/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/servicestests/res/raw/install_app2_cert5_rotated_cert6 Binary files differindex 30bb6478d18d..30bb6478d18d 100644 --- a/core/tests/coretests/res/raw/install_app2_cert5_rotated_cert6 +++ b/services/tests/servicestests/res/raw/install_app2_cert5_rotated_cert6 diff --git a/core/tests/coretests/res/raw/install_app2_unsigned b/services/tests/servicestests/res/raw/install_app2_unsigned Binary files differindex b69d9fe5c6f9..b69d9fe5c6f9 100644 --- a/core/tests/coretests/res/raw/install_app2_unsigned +++ b/services/tests/servicestests/res/raw/install_app2_unsigned diff --git a/core/tests/coretests/res/raw/install_shared1_cert1 b/services/tests/servicestests/res/raw/install_shared1_cert1 Binary files differindex 714f9fffde4e..714f9fffde4e 100644 --- a/core/tests/coretests/res/raw/install_shared1_cert1 +++ b/services/tests/servicestests/res/raw/install_shared1_cert1 diff --git a/core/tests/coretests/res/raw/install_shared1_cert1_cert2 b/services/tests/servicestests/res/raw/install_shared1_cert1_cert2 Binary files differindex 83725e0d2b26..83725e0d2b26 100644 --- a/core/tests/coretests/res/raw/install_shared1_cert1_cert2 +++ b/services/tests/servicestests/res/raw/install_shared1_cert1_cert2 diff --git a/core/tests/coretests/res/raw/install_shared1_cert2 b/services/tests/servicestests/res/raw/install_shared1_cert2 Binary files differindex 6a3157e1196c..6a3157e1196c 100644 --- a/core/tests/coretests/res/raw/install_shared1_cert2 +++ b/services/tests/servicestests/res/raw/install_shared1_cert2 diff --git a/core/tests/coretests/res/raw/install_shared1_unsigned b/services/tests/servicestests/res/raw/install_shared1_unsigned Binary files differindex 2a2e5f5fdad8..2a2e5f5fdad8 100644 --- a/core/tests/coretests/res/raw/install_shared1_unsigned +++ b/services/tests/servicestests/res/raw/install_shared1_unsigned diff --git a/core/tests/coretests/res/raw/install_shared2_cert1 b/services/tests/servicestests/res/raw/install_shared2_cert1 Binary files differindex 7006edcc67b2..7006edcc67b2 100644 --- a/core/tests/coretests/res/raw/install_shared2_cert1 +++ b/services/tests/servicestests/res/raw/install_shared2_cert1 diff --git a/core/tests/coretests/res/raw/install_shared2_cert1_cert2 b/services/tests/servicestests/res/raw/install_shared2_cert1_cert2 Binary files differindex b7b084c0a779..b7b084c0a779 100644 --- a/core/tests/coretests/res/raw/install_shared2_cert1_cert2 +++ b/services/tests/servicestests/res/raw/install_shared2_cert1_cert2 diff --git a/core/tests/coretests/res/raw/install_shared2_cert2 b/services/tests/servicestests/res/raw/install_shared2_cert2 Binary files differindex 0f04388c371e..0f04388c371e 100644 --- a/core/tests/coretests/res/raw/install_shared2_cert2 +++ b/services/tests/servicestests/res/raw/install_shared2_cert2 diff --git a/core/tests/coretests/res/raw/install_shared2_unsigned b/services/tests/servicestests/res/raw/install_shared2_unsigned Binary files differindex 2794282512de..2794282512de 100644 --- a/core/tests/coretests/res/raw/install_shared2_unsigned +++ b/services/tests/servicestests/res/raw/install_shared2_unsigned diff --git a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java deleted file mode 100644 index a1d4c203de18..000000000000 --- a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 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; - -import static org.mockito.Mockito.*; - -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.os.Looper; -import android.provider.Settings; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; - -@MediumTest -@RunWith(AndroidJUnit4.class) -public class BluetoothAirplaneModeListenerTest { - private Context mContext; - private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener; - private BluetoothAdapter mBluetoothAdapter; - private BluetoothModeChangeHelper mHelper; - - @Mock BluetoothManagerService mBluetoothManagerService; - - @Before - public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); - - mHelper = mock(BluetoothModeChangeHelper.class); - when(mHelper.getSettingsInt(BluetoothAirplaneModeListener.TOAST_COUNT)) - .thenReturn(BluetoothAirplaneModeListener.MAX_TOAST_COUNT); - doNothing().when(mHelper).setSettingsInt(anyString(), anyInt()); - doNothing().when(mHelper).showToastMessage(); - doNothing().when(mHelper).onAirplaneModeChanged(any(BluetoothManagerService.class)); - - mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener( - mBluetoothManagerService, Looper.getMainLooper(), mContext); - mBluetoothAirplaneModeListener.start(mHelper); - } - - @Test - public void testIgnoreOnAirplanModeChange() { - Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - - when(mHelper.isBluetoothOn()).thenReturn(true); - Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - - when(mHelper.isMediaProfileConnected()).thenReturn(true); - Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - - when(mHelper.isAirplaneModeOn()).thenReturn(true); - Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - } - - @Test - public void testHandleAirplaneModeChange_InvokeAirplaneModeChanged() { - mBluetoothAirplaneModeListener.handleAirplaneModeChange(); - verify(mHelper).onAirplaneModeChanged(mBluetoothManagerService); - } - - @Test - public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_NotPopToast() { - mBluetoothAirplaneModeListener.mToastCount = BluetoothAirplaneModeListener.MAX_TOAST_COUNT; - when(mHelper.isBluetoothOn()).thenReturn(true); - when(mHelper.isMediaProfileConnected()).thenReturn(true); - when(mHelper.isAirplaneModeOn()).thenReturn(true); - mBluetoothAirplaneModeListener.handleAirplaneModeChange(); - - verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON, - BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); - verify(mHelper, times(0)).showToastMessage(); - verify(mHelper, times(0)).onAirplaneModeChanged(mBluetoothManagerService); - } - - @Test - public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_PopToast() { - mBluetoothAirplaneModeListener.mToastCount = 0; - when(mHelper.isBluetoothOn()).thenReturn(true); - when(mHelper.isMediaProfileConnected()).thenReturn(true); - when(mHelper.isAirplaneModeOn()).thenReturn(true); - mBluetoothAirplaneModeListener.handleAirplaneModeChange(); - - verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON, - BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); - verify(mHelper).showToastMessage(); - verify(mHelper, times(0)).onAirplaneModeChanged(mBluetoothManagerService); - } - - @Test - public void testIsPopToast_PopToast() { - mBluetoothAirplaneModeListener.mToastCount = 0; - Assert.assertTrue(mBluetoothAirplaneModeListener.shouldPopToast()); - verify(mHelper).setSettingsInt(BluetoothAirplaneModeListener.TOAST_COUNT, 1); - } - - @Test - public void testIsPopToast_NotPopToast() { - mBluetoothAirplaneModeListener.mToastCount = BluetoothAirplaneModeListener.MAX_TOAST_COUNT; - Assert.assertFalse(mBluetoothAirplaneModeListener.shouldPopToast()); - verify(mHelper, times(0)).setSettingsInt(anyString(), anyInt()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index f1a63bcb0602..6818d1f6851b 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -16,6 +16,13 @@ package com.android.server.am; +import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; +import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; +import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; @@ -64,6 +71,7 @@ import android.app.KeyguardManager; import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.pm.UserInfo.UserInfoFlag; import android.os.Binder; @@ -617,6 +625,100 @@ public class UserControllerTest { assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true); } + /** Tests handleIncomingUser() for a variety of permissions and situations. */ + @Test + public void testHandleIncomingUser() throws Exception { + final UserInfo user1a = new UserInfo(111, "user1a", 0); + final UserInfo user1b = new UserInfo(112, "user1b", 0); + final UserInfo user2 = new UserInfo(113, "user2", 0); + // user1a and user2b are in the same profile group; user2 is in a different one. + user1a.profileGroupId = 5; + user1b.profileGroupId = 5; + user2.profileGroupId = 6; + + final List<UserInfo> users = Arrays.asList(user1a, user1b, user2); + when(mInjector.mUserManagerMock.getUsers(false)).thenReturn(users); + mUserController.onSystemReady(); // To set the profileGroupIds in UserController. + + + // Has INTERACT_ACROSS_USERS_FULL. + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mInjector.checkPermissionForPreflight( + eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(false); + + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, true); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, true); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, true); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, true); + + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true); + + + // Has INTERACT_ACROSS_USERS. + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mInjector.checkPermissionForPreflight( + eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(false); + + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, true); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, true); + + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, false); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true); + + + // Has INTERACT_ACROSS_PROFILES. + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mInjector.checkPermissionForPreflight( + eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(true); + + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, false); + + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, false); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, false); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, false); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true); + } + + private void checkHandleIncomingUser(int fromUser, int toUser, int allowMode, boolean pass) { + final int pid = 100; + final int uid = fromUser * UserHandle.PER_USER_RANGE + 34567 + fromUser; + final String name = "whatever"; + final String pkg = "some.package"; + final boolean allowAll = false; + + if (pass) { + mUserController.handleIncomingUser(pid, uid, toUser, allowAll, allowMode, name, pkg); + } else { + assertThrows(SecurityException.class, () -> mUserController.handleIncomingUser( + pid, uid, toUser, allowAll, allowMode, name, pkg)); + } + } + private void setUpAndStartUserInBackground(int userId) throws Exception { setUpUser(userId, 0); mUserController.startUser(userId, /* foreground= */ false); @@ -784,6 +886,23 @@ public class UserControllerTest { } @Override + int checkComponentPermission(String permission, int pid, int uid, int owner, boolean exp) { + Log.i(TAG, "checkComponentPermission " + permission); + return PERMISSION_GRANTED; + } + + @Override + boolean checkPermissionForPreflight(String permission, int pid, int uid, String pkg) { + Log.i(TAG, "checkPermissionForPreflight " + permission); + return true; + } + + @Override + boolean isCallerRecents(int uid) { + return false; + } + + @Override WindowManagerService getWindowManager() { return mWindowManagerMock; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java new file mode 100644 index 000000000000..2b72fabe7cca --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java @@ -0,0 +1,255 @@ +/* + * 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.biometrics.log; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyFloat; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.Sensor; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.input.InputSensorInfo; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.sensors.BaseClientMonitor; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@SmallTest +public class BiometricLoggerTest { + + private static final int DEFAULT_MODALITY = BiometricsProtoEnums.MODALITY_FINGERPRINT; + private static final int DEFAULT_ACTION = BiometricsProtoEnums.ACTION_AUTHENTICATE; + private static final int DEFAULT_CLIENT = BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT; + + @Rule + public TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + @Mock + private BiometricFrameworkStatsLogger mSink; + @Mock + private SensorManager mSensorManager; + @Mock + private BaseClientMonitor mClient; + + private BiometricLogger mLogger; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext.addMockSystemService(SensorManager.class, mSensorManager); + when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn( + new Sensor(new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0, + "", "", 0, 0, 0)) + ); + } + + private BiometricLogger createLogger() { + return createLogger(DEFAULT_MODALITY, DEFAULT_ACTION, DEFAULT_CLIENT); + } + + private BiometricLogger createLogger(int statsModality, int statsAction, int statsClient) { + return new BiometricLogger(statsModality, statsAction, statsClient, mSink, mSensorManager); + } + + @Test + public void testAcquired() { + mLogger = createLogger(); + + final int acquiredInfo = 2; + final int vendorCode = 3; + final boolean isCrypto = true; + final int targetUserId = 9; + + mLogger.logOnAcquired(mContext, acquiredInfo, vendorCode, isCrypto, targetUserId); + + verify(mSink).acquired( + eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), + eq(acquiredInfo), eq(vendorCode), eq(isCrypto), eq(targetUserId)); + } + + @Test + public void testAuth() { + mLogger = createLogger(); + + final boolean authenticated = true; + final boolean requireConfirmation = false; + final boolean isCrypto = false; + final int targetUserId = 11; + final boolean isBiometricPrompt = true; + + mLogger.logOnAuthenticated(mContext, + authenticated, requireConfirmation, isCrypto, targetUserId, isBiometricPrompt); + + verify(mSink).authenticate( + eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), + anyLong(), eq(authenticated), anyInt(), eq(requireConfirmation), eq(isCrypto), + eq(targetUserId), eq(isBiometricPrompt), anyFloat()); + } + + @Test + public void testEnroll() { + mLogger = createLogger(); + + final int targetUserId = 4; + final long latency = 44; + final boolean enrollSuccessful = true; + + mLogger.logOnEnrolled(targetUserId, latency, enrollSuccessful); + + verify(mSink).enroll( + eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), + eq(targetUserId), eq(latency), eq(enrollSuccessful), anyFloat()); + } + + @Test + public void testError() { + mLogger = createLogger(); + + final int error = 7; + final int vendorCode = 11; + final boolean isCrypto = false; + final int targetUserId = 9; + + mLogger.logOnError(mContext, error, vendorCode, isCrypto, targetUserId); + + verify(mSink).error( + eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), + anyLong(), eq(error), eq(vendorCode), eq(isCrypto), eq(targetUserId)); + } + + @Test + public void testBadModalityActsDisabled() { + mLogger = createLogger( + BiometricsProtoEnums.MODALITY_UNKNOWN, DEFAULT_ACTION, DEFAULT_CLIENT); + testDisabledMetrics(true /* isBadConfig */); + } + + @Test + public void testBadActionActsDisabled() { + mLogger = createLogger( + DEFAULT_MODALITY, BiometricsProtoEnums.ACTION_UNKNOWN, DEFAULT_CLIENT); + testDisabledMetrics(true /* isBadConfig */); + } + + @Test + public void testDisableLogger() { + mLogger = createLogger(); + testDisabledMetrics(false /* isBadConfig */); + } + + private void testDisabledMetrics(boolean isBadConfig) { + mLogger.disableMetrics(); + mLogger.logOnAcquired(mContext, + 0 /* acquiredInfo */, + 1 /* vendorCode */, + true /* isCrypto */, + 8 /* targetUserId */); + mLogger.logOnAuthenticated(mContext, + true /* authenticated */, + true /* requireConfirmation */, + false /* isCrypto */, + 4 /* targetUserId */, + true/* isBiometricPrompt */); + mLogger.logOnEnrolled(2 /* targetUserId */, + 10 /* latency */, + true /* enrollSuccessful */); + mLogger.logOnError(mContext, + 4 /* error */, + 0 /* vendorCode */, + false /* isCrypto */, + 6 /* targetUserId */); + + verify(mSink, never()).acquired( + anyInt(), anyInt(), anyInt(), anyBoolean(), + anyInt(), anyInt(), anyBoolean(), anyInt()); + verify(mSink, never()).authenticate( + anyInt(), anyInt(), anyInt(), anyBoolean(), + anyLong(), anyBoolean(), anyInt(), anyBoolean(), + anyBoolean(), anyInt(), anyBoolean(), anyFloat()); + verify(mSink, never()).enroll( + anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat()); + verify(mSink, never()).error( + anyInt(), anyInt(), anyInt(), anyBoolean(), + anyLong(), anyInt(), anyInt(), anyBoolean(), anyInt()); + + mLogger.logUnknownEnrollmentInFramework(); + mLogger.logUnknownEnrollmentInHal(); + + verify(mSink, times(isBadConfig ? 0 : 1)) + .reportUnknownTemplateEnrolledHal(eq(DEFAULT_MODALITY)); + verify(mSink, times(isBadConfig ? 0 : 1)) + .reportUnknownTemplateEnrolledFramework(eq(DEFAULT_MODALITY)); + } + + @Test + public void systemHealthBadHalTemplate() { + mLogger = createLogger(); + mLogger.logUnknownEnrollmentInHal(); + verify(mSink).reportUnknownTemplateEnrolledHal(eq(DEFAULT_MODALITY)); + } + + @Test + public void systemHealthBadFrameworkTemplate() { + mLogger = createLogger(); + mLogger.logUnknownEnrollmentInFramework(); + verify(mSink).reportUnknownTemplateEnrolledFramework(eq(DEFAULT_MODALITY)); + } + + @Test + public void testALSCallback() { + mLogger = createLogger(); + final CallbackWithProbe<Probe> callback = + mLogger.createALSCallback(true /* startWithClient */); + + callback.onClientStarted(mClient); + verify(mSensorManager).registerListener(any(), any(), anyInt()); + + callback.onClientFinished(mClient, true /* success */); + verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); + } + + @Test + public void testALSCallbackDoesNotStart() { + mLogger = createLogger(); + final CallbackWithProbe<Probe> callback = + mLogger.createALSCallback(false /* startWithClient */); + + callback.onClientStarted(mClient); + callback.onClientFinished(mClient, true /* success */); + verify(mSensorManager, never()).registerListener(any(), any(), anyInt()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index 0891eca9f61c..2718bf90d857 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -33,6 +33,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.CoexCoordinator; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -82,8 +83,10 @@ public class SensorTest { new Handler(mLooper.getLooper()), BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, + mBiometricService, () -> USER_ID, - mUserSwitchCallback); + mUserSwitchCallback, + CoexCoordinator.getInstance()); mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), TAG, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index a012b8b06c7f..d4609b55afba 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -33,6 +33,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.CoexCoordinator; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -82,8 +83,10 @@ public class SensorTest { new Handler(mLooper.getLooper()), BiometricScheduler.SENSOR_TYPE_FP_OTHER, null /* gestureAvailabilityDispatcher */, + mBiometricService, () -> USER_ID, - mUserSwitchCallback); + mUserSwitchCallback, + CoexCoordinator.getInstance()); mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), TAG, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index c7c0756bc0d0..a6b4aecf1cb6 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -23,9 +23,12 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; import android.Manifest; +import android.app.admin.DevicePolicyManager; +import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.content.ContextWrapper; import android.graphics.Point; @@ -70,6 +73,10 @@ public class VirtualDeviceManagerServiceTest { private InputController.NativeWrapper mNativeWrapperMock; @Mock private DisplayManagerInternal mDisplayManagerInternalMock; + @Mock + private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; + @Mock + private DevicePolicyManager mDevicePolicyManagerMock; @Before public void setUp() { @@ -81,10 +88,22 @@ public class VirtualDeviceManagerServiceTest { mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); doNothing().when(mContext).enforceCallingOrSelfPermission( eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( + mDevicePolicyManagerMock); + mInputController = new InputController(new Object(), mNativeWrapperMock); mDeviceImpl = new VirtualDeviceImpl(mContext, /* association info */ null, new Binder(), /* uid */ 0, mInputController, - (int associationId) -> {}); + (int associationId) -> {}, mPendingTrampolineCallback, + new VirtualDeviceParams.Builder().build()); + } + + @Test + public void onVirtualDisplayRemovedLocked_doesNotThrowException() { + final int displayId = 2; + mDeviceImpl.onVirtualDisplayCreatedLocked(displayId); + // This call should not throw any exceptions. + mDeviceImpl.onVirtualDisplayRemovedLocked(displayId); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java new file mode 100644 index 000000000000..77f1e24ee771 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java @@ -0,0 +1,51 @@ +/* + * 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.companion.virtual; + +import static com.google.common.truth.Truth.assertThat; + +import android.companion.virtual.VirtualDeviceParams; +import android.os.Parcel; +import android.os.UserHandle; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +public class VirtualDeviceParamsTest { + + @Test + public void parcelable_shouldRecreateSuccessfully() { + VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder() + .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) + .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456))) + .build(); + Parcel parcel = Parcel.obtain(); + originalParams.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + VirtualDeviceParams params = VirtualDeviceParams.CREATOR.createFromParcel(parcel); + assertThat(params).isEqualTo(originalParams); + assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED); + assertThat(params.getUsersWithMatchingAccounts()) + .containsExactly(UserHandle.of(123), UserHandle.of(456)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 3c809f9e4814..1228d625325f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -37,6 +37,8 @@ import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.PasswordMetrics.computeForPasswordOrPin; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.InetAddresses.parseNumericAddress; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; @@ -100,7 +102,7 @@ import android.content.pm.StringParceledListSlice; import android.content.pm.UserInfo; import android.graphics.Color; import android.hardware.usb.UsbManager; -import android.net.ConnectivityManager; +import android.net.ProfileNetworkPreference; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION_CODES; @@ -4058,12 +4060,15 @@ public class DevicePolicyManagerTest extends DpmTestBase { mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL); dpms.handleStartUser(managedProfileUserId); - verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference( - eq(UserHandle.of(managedProfileUserId)), - anyInt(), - any(), - any() - ); + ProfileNetworkPreference preferenceDetails = + new ProfileNetworkPreference.Builder() + .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT) + .build(); + List<ProfileNetworkPreference> preferences = new ArrayList<>(); + preferences.add(preferenceDetails); + verify(getServices().connectivityManager, times(1)) + .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences, + null, null); } @Test @@ -4075,12 +4080,15 @@ public class DevicePolicyManagerTest extends DpmTestBase { mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL); dpms.handleStopUser(managedProfileUserId); - verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference( - eq(UserHandle.of(managedProfileUserId)), - eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT), - any(), - any() - ); + ProfileNetworkPreference preferenceDetails = + new ProfileNetworkPreference.Builder() + .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT) + .build(); + List<ProfileNetworkPreference> preferences = new ArrayList<>(); + preferences.add(preferenceDetails); + verify(getServices().connectivityManager, times(1)) + .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences, + null, null); } @Test @@ -4098,21 +4106,29 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setPreferentialNetworkServiceEnabled(false); assertThat(dpm.isPreferentialNetworkServiceEnabled()).isFalse(); - verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference( - eq(UserHandle.of(managedProfileUserId)), - eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT), - any(), - any() - ); + + ProfileNetworkPreference preferenceDetails = + new ProfileNetworkPreference.Builder() + .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT) + .build(); + List<ProfileNetworkPreference> preferences = new ArrayList<>(); + preferences.add(preferenceDetails); + verify(getServices().connectivityManager, times(1)) + .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences, + null, null); dpm.setPreferentialNetworkServiceEnabled(true); assertThat(dpm.isPreferentialNetworkServiceEnabled()).isTrue(); - verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference( - eq(UserHandle.of(managedProfileUserId)), - eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE), - any(), - any() - ); + + ProfileNetworkPreference preferenceDetails2 = + new ProfileNetworkPreference.Builder() + .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE) + .build(); + List<ProfileNetworkPreference> preferences2 = new ArrayList<>(); + preferences2.add(preferenceDetails); + verify(getServices().connectivityManager, times(1)) + .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences2, + null, null); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java index e286cb27cc41..d54524e94139 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java @@ -41,10 +41,10 @@ public final class DeviceStateTest { @Test public void testConstruct() { final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */, - "CLOSED" /* name */, DeviceState.FLAG_CANCEL_STICKY_REQUESTS /* flags */); + "TEST_CLOSED" /* name */, DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS /* flags */); assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE); - assertEquals(state.getName(), "CLOSED"); - assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_STICKY_REQUESTS); + assertEquals(state.getName(), "TEST_CLOSED"); + assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java index c9cf2f06640d..b94fc4308ce2 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -213,6 +213,25 @@ public final class OverrideRequestControllerTest { assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); } + @Test + public void cancelOverrideRequestsTest() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, 0 /* flags */); + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 2 /* requestedState */, 0 /* flags */); + + mController.addRequest(firstRequest); + mController.addRequest(secondRequest); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + + mController.cancelOverrideRequests(); + + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + } + private static final class TestStatusChangeListener implements OverrideRequestController.StatusChangeListener { private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>(); diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index abe7d89dfa41..54945e44fee5 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -16,7 +16,10 @@ package com.android.server.display; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyFloat; @@ -32,18 +35,22 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.testutils.OffsettableClock; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest @@ -59,7 +66,11 @@ public class AutomaticBrightnessControllerTest { private static final float DOZE_SCALE_FACTOR = 0.0f; private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; - + private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000; + private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000; + private static final float EPSILON = 0.001f; + private OffsettableClock mClock = new OffsettableClock(); + private TestLooper mTestLooper; private Context mContext; private AutomaticBrightnessController mController; @@ -89,21 +100,36 @@ public class AutomaticBrightnessControllerTest { } } + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + private AutomaticBrightnessController setupController(Sensor lightSensor) { + mClock = new OffsettableClock.Stopped(); + mTestLooper = new TestLooper(mClock::now); + AutomaticBrightnessController controller = new AutomaticBrightnessController( new AutomaticBrightnessController.Injector() { @Override public Handler getBackgroundThreadHandler() { return mNoOpHandler; } - }, - () -> { }, mContext.getMainLooper(), mSensorManager, lightSensor, + + @Override + AutomaticBrightnessController.Clock createClock() { + return mClock::now; + } + + }, // pass in test looper instead, pass in offsetable clock + () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor, mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT, BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds, mScreenBrightnessThresholds, - mContext, mHbmController, mIdleBrightnessMappingStrategy + mContext, mHbmController, mIdleBrightnessMappingStrategy, + AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG ); when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT); @@ -111,7 +137,7 @@ public class AutomaticBrightnessControllerTest { // Configure the brightness controller and grab an instance of the sensor listener, // through which we can deliver fake (for test) sensor values. - controller.configure(true /* enable */, null /* configuration */, + controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */, 0 /* brightness */, false /* userChangedBrightness */, 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT); @@ -227,7 +253,7 @@ public class AutomaticBrightnessControllerTest { listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000)); // User sets brightness to 100 - mController.configure(true /* enable */, null /* configuration */, + mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */, 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT); @@ -250,7 +276,7 @@ public class AutomaticBrightnessControllerTest { listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000)); // User sets brightness to 100 - mController.configure(true /* enable */, null /* configuration */, + mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */, 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT); @@ -267,11 +293,102 @@ public class AutomaticBrightnessControllerTest { verifyNoMoreInteractions(mBrightnessMappingStrategy); // User sets idle brightness to 0.5 - mController.configure(true /* enable */, null /* configuration */, + mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */, 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT); // Ensure we use the correct mapping strategy verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f); } + + @Test + public void testAmbientLightHorizon() throws Exception { + // create abc + Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); + mController = setupController(lightSensor); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + long increment = 500; + // set autobrightness to low + // t = 0 + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + + // t = 500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + + // t = 1000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 1500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // ensure that our reading is at 0. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // first 10000 lux sensor event reading + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 3000 + // lux reading should still not yet be 10000. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 3500 + mClock.fastForward(increment); + // lux has been high (10000) for 1000ms. + // lux reading should be 10000 + // short horizon (ambient lux) is high, long horizon is still not high + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4000 + // stay high + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4500 + Mockito.clearInvocations(mBrightnessMappingStrategy); + mClock.fastForward(increment); + // short horizon is high, long horizon is high too + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 5000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 5500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 6000 + mClock.fastForward(increment); + // ambient lux goes to 0 + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + } } diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java index aca863299b33..4bb5d7482e25 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -20,10 +20,17 @@ import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; +import static com.android.server.display.AutomaticBrightnessController + .AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE; + import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -47,6 +54,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; @@ -87,6 +95,7 @@ public class HighBrightnessModeControllerTest { private TestLooper mTestLooper; private Handler mHandler; private Binder mDisplayToken; + private String mDisplayUniqueId; private Context mContextSpy; @Rule @@ -108,6 +117,7 @@ public class HighBrightnessModeControllerTest { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); mDisplayToken = null; + mDisplayUniqueId = "unique_id"; mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy); when(mContextSpy.getContentResolver()).thenReturn(resolver); @@ -123,8 +133,8 @@ public class HighBrightnessModeControllerTest { public void testNoHbmData() { initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( - mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN, - DEFAULT_MAX, null, () -> {}, mContextSpy); + mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, + mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f); } @@ -133,9 +143,9 @@ public class HighBrightnessModeControllerTest { public void testNoHbmData_Enabled() { initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( - mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN, - DEFAULT_MAX, null, () -> {}, mContextSpy); - hbmc.setAutoBrightnessEnabled(true); + mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, + mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f); @@ -152,7 +162,7 @@ public class HighBrightnessModeControllerTest { public void testAutoBrightnessEnabled_NoLux() { final HighBrightnessModeController hbmc = createDefaultHbm(); - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); } @@ -160,7 +170,7 @@ public class HighBrightnessModeControllerTest { public void testAutoBrightnessEnabled_LowLux() { final HighBrightnessModeController hbmc = createDefaultHbm(); - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); } @@ -169,7 +179,7 @@ public class HighBrightnessModeControllerTest { public void testAutoBrightnessEnabled_HighLux() { final HighBrightnessModeController hbmc = createDefaultHbm(); - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); } @@ -178,9 +188,9 @@ public class HighBrightnessModeControllerTest { public void testAutoBrightnessEnabled_HighLux_ThenDisable() { final HighBrightnessModeController hbmc = createDefaultHbm(); - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.setAutoBrightnessEnabled(false); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_DISABLED); assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); } @@ -189,7 +199,7 @@ public class HighBrightnessModeControllerTest { public void testWithinHighRange_thenOverTime_thenEarnBackTime() { final HighBrightnessModeController hbmc = createDefaultHbm(); - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); @@ -221,7 +231,7 @@ public class HighBrightnessModeControllerTest { public void testInHBM_ThenLowLux() { final HighBrightnessModeController hbmc = createDefaultHbm(); - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); @@ -245,7 +255,7 @@ public class HighBrightnessModeControllerTest { public void testInHBM_TestMultipleEvents_DueToAutoBrightness() { final HighBrightnessModeController hbmc = createDefaultHbm(); - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); @@ -274,7 +284,7 @@ public class HighBrightnessModeControllerTest { public void testInHBM_TestMultipleEvents_DueToLux() { final HighBrightnessModeController hbmc = createDefaultHbm(); - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); // Go into HBM for half the allowed window @@ -316,7 +326,7 @@ public class HighBrightnessModeControllerTest { listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL)); // Try to go into HBM mode but fail - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); advanceTime(10); @@ -335,7 +345,7 @@ public class HighBrightnessModeControllerTest { listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_LIGHT)); // Try to go into HBM mode - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); advanceTime(1); @@ -378,7 +388,7 @@ public class HighBrightnessModeControllerTest { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); // Turn on sunlight - hbmc.setAutoBrightnessEnabled(true); + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); advanceTime(0); assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); @@ -451,6 +461,155 @@ public class HighBrightnessModeControllerTest { assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); } + @Test + public void testHbmStats_StateChange() { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onBrightnessChanged(TRANSITION_POINT); + hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/); + advanceTime(0); + assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode()); + + // Verify Stats HBM_ON_HDR + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/, + 0, 0, 0 /*flags*/); + advanceTime(0); + + // Verify Stats HBM_OFF + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + + // Verify Stats HBM_ON_SUNLIGHT + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + hbmc.onAmbientLuxChange(1); + advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1); + + // Verify Stats HBM_OFF + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP)); + } + + @Test + public void tetHbmStats_NbmHdrNoReport() { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onBrightnessChanged(DEFAULT_MIN); + hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/); + advanceTime(0); + assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode()); + + // Verify Stats HBM_ON_HDR not report + verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + anyInt()); + } + + @Test + public void testHbmStats_ThermalOff() throws Exception { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener thermListener = mThermalEventListenerCaptor.getValue(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + advanceTime(1); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + thermListener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL)); + advanceTime(10); + assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode()); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); + } + + @Test + public void testHbmStats_TimeOut() { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + advanceTime(0); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + // Use up all the time in the window. + advanceTime(TIME_WINDOW_MILLIS + 1); + + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT)); + } + + @Test + public void testHbmStats_DisplayOff() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + advanceTime(0); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF)); + } + + @Test + public void testHbmStats_HdrPlaying() { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + advanceTime(0); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/); + advanceTime(0); + + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING)); + } + private void assertState(HighBrightnessModeController hbmc, float brightnessMin, float brightnessMax, int hbmMode) { assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON); @@ -466,8 +625,8 @@ public class HighBrightnessModeControllerTest { private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) { initHandler(clock); return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH, - DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {}, - mContextSpy); + DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, + DEFAULT_HBM_DATA, () -> {}, mContextSpy); } private void initHandler(OffsettableClock clock) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index b588db66a08f..18f264277b41 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -92,7 +92,6 @@ public class ActiveSourceActionTest { this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index ff01cb1a3a1d..e4c5ad6769d7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -109,7 +109,6 @@ public class ArcInitiationActionFromAvrTest { hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); hdmiControlService.setCecController(hdmiCecController); hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); - hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); hdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); hdmiControlService.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index a44a5cde0276..d73cdb5f53b0 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -102,7 +102,6 @@ public class ArcTerminationActionFromAvrTest { hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); hdmiControlService.setCecController(hdmiCecController); hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); - hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); hdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); hdmiControlService.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 9c99240628a4..5cec8ad1e63d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -53,7 +53,7 @@ public class DetectTvSystemAudioModeSupportActionTest { @Before public void SetUp() { - mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); + mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234); HdmiControlService hdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), Collections.emptyList()) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java index 638b38633cd2..52a0b6cdc2be 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java @@ -109,7 +109,6 @@ public class DevicePowerStatusActionTest { this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java index 41231e0d673b..35432edfcf16 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java @@ -128,10 +128,8 @@ public class DeviceSelectActionFromPlaybackTest { mHdmiControlService.setCecController(mHdmiCecController); mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService); mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService, mHdmiCecController, mHdmiMhlControllerStub); mHdmiControlService.setHdmiCecNetwork(mHdmiCecNetwork); @@ -156,13 +154,13 @@ public class DeviceSelectActionFromPlaybackTest { mPlaybackLogicalAddress3 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_3 ? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_3; - mReportPowerStatusOn = new HdmiCecMessage( + mReportPowerStatusOn = HdmiCecMessage.build( mPlaybackLogicalAddress2, mPlaybackLogicalAddress1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); - mReportPowerStatusStandby = new HdmiCecMessage( + mReportPowerStatusStandby = HdmiCecMessage.build( mPlaybackLogicalAddress2, mPlaybackLogicalAddress1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY); - mReportPowerStatusTransientToOn = new HdmiCecMessage( + mReportPowerStatusTransientToOn = HdmiCecMessage.build( mPlaybackLogicalAddress2, mPlaybackLogicalAddress1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON); mSetStreamPath = HdmiCecMessageBuilder.buildSetStreamPath( @@ -173,21 +171,36 @@ public class DeviceSelectActionFromPlaybackTest { mActiveSource = HdmiCecMessageBuilder.buildActiveSource( mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2); - HdmiDeviceInfo infoPlayback1 = new HdmiDeviceInfo( - mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1, - HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 1", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - HdmiDeviceInfo infoPlayback2 = new HdmiDeviceInfo( - mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2, - HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 2", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - HdmiDeviceInfo infoPlayback3 = new HdmiDeviceInfo( - mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3, PORT_3, - HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 3", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + HdmiDeviceInfo infoPlayback1 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mPlaybackLogicalAddress1) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_1) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 1") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); + HdmiDeviceInfo infoPlayback2 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mPlaybackLogicalAddress2) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_2) + .setPortId(PORT_2) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 2") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); + HdmiDeviceInfo infoPlayback3 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mPlaybackLogicalAddress3) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_3) + .setPortId(PORT_3) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 3") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback1); mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback2); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index dd74864bd81f..e77cd91b46d8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -64,22 +64,34 @@ public class DeviceSelectActionFromTvTest { private static final byte[] POWER_ON = new byte[] { POWER_STATUS_ON }; private static final byte[] POWER_STANDBY = new byte[] { POWER_STATUS_STANDBY }; private static final byte[] POWER_TRANSIENT_TO_ON = new byte[] { POWER_STATUS_TRANSIENT_TO_ON }; - private static final HdmiCecMessage REPORT_POWER_STATUS_ON = new HdmiCecMessage( + private static final HdmiCecMessage REPORT_POWER_STATUS_ON = HdmiCecMessage.build( ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); - private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = new HdmiCecMessage( + private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = HdmiCecMessage.build( ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY); - private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = new HdmiCecMessage( + private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = HdmiCecMessage.build( ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON); private static final HdmiCecMessage SET_STREAM_PATH = HdmiCecMessageBuilder.buildSetStreamPath( ADDR_TV, PHYSICAL_ADDRESS_PLAYBACK_1); - private static final HdmiDeviceInfo INFO_PLAYBACK_1 = new HdmiDeviceInfo( - ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1, HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 1", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - private static final HdmiDeviceInfo INFO_PLAYBACK_2 = new HdmiDeviceInfo( - ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2, HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 2", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private static final HdmiDeviceInfo INFO_PLAYBACK_1 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_PLAYBACK_1) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_1) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Plyback 1") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); + private static final HdmiDeviceInfo INFO_PLAYBACK_2 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_PLAYBACK_2) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_2) + .setPortId(PORT_2) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 2") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; @@ -123,7 +135,6 @@ public class DeviceSelectActionFromTvTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; hdmiPortInfos[0] = diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index d630ef6a5cd3..559a2c0d6a09 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -25,6 +25,7 @@ import com.android.server.hdmi.HdmiCecController.NativeWrapper; import com.google.common.collect.Iterables; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -77,7 +78,8 @@ final class FakeNativeWrapper implements NativeWrapper { if (body.length == 0) { return mPollAddressResponse[dstAddress]; } else { - HdmiCecMessage message = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body); + HdmiCecMessage message = HdmiCecMessage.build(srcAddress, dstAddress, body[0], + Arrays.copyOfRange(body, 1, body.length)); mResultMessages.add(message); return mMessageSendResult.getOrDefault(message.getOpcode(), SendMessageResult.SUCCESS); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java new file mode 100644 index 000000000000..0b31db69f0a9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java @@ -0,0 +1,198 @@ +/* + * 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.hdmi; + +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN; +import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_VERSION_2_0; +import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; + +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.os.Looper; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.server.SystemService; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class GiveFeaturesActionTest { + private HdmiControlService mHdmiControlServiceSpy; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevicePlayback mPlaybackDevice; + private FakeNativeWrapper mNativeWrapper; + private FakePowerManagerWrapper mPowerManager; + private Looper mLooper; + private Context mContextSpy; + private TestLooper mTestLooper = new TestLooper(); + private int mPhysicalAddress = 0x1100; + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mPlaybackLogicalAddress; + + private TestCallback mTestCallback; + private GiveFeaturesAction mAction; + + /** + * Setup: Local Playback device queries the features of a connected TV. + */ + @Before + public void setUp() throws RemoteException { + mContextSpy = spy(new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList())); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); + + mLooper = mTestLooper.getLooper(); + mHdmiControlServiceSpy.setIoLooper(mLooper); + mHdmiControlServiceSpy.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); + + mNativeWrapper = new FakeNativeWrapper(); + mNativeWrapper.setPhysicalAddress(mPhysicalAddress); + + mHdmiCecController = HdmiCecController.createWithNativeWrapper( + mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter()); + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + mHdmiControlServiceSpy.setHdmiMhlController( + HdmiMhlControllerStub.create(mHdmiControlServiceSpy)); + mHdmiControlServiceSpy.initService(); + mPowerManager = new FakePowerManagerWrapper(mContextSpy); + mHdmiControlServiceSpy.setPowerManager(mPowerManager); + + mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy); + mPlaybackDevice.init(); + mLocalDevices.add(mPlaybackDevice); + + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + mTestLooper.dispatchAll(); + + synchronized (mPlaybackDevice.mLock) { + mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress(); + } + + // Setup specific to these tests + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + Constants.ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV)); + mTestLooper.dispatchAll(); + + mTestCallback = new TestCallback(); + mAction = new GiveFeaturesAction(mPlaybackDevice, Constants.ADDR_TV, mTestCallback); + } + + @Test + public void sendsGiveFeaturesMessage() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveFeatures = HdmiCecMessageBuilder.buildGiveFeatures( + mPlaybackLogicalAddress, Constants.ADDR_TV); + assertThat(mNativeWrapper.getResultMessages()).contains(giveFeatures); + } + + @Test + public void noMatchingReportFeaturesReceived_actionFailsAndNetworkIsNotUpdated() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + // Wrong source + mNativeWrapper.onCecMessage(ReportFeaturesMessage.build( + Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0, + Arrays.asList(DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE, + Collections.emptyList(), DeviceFeatures.NO_FEATURES_SUPPORTED)); + mTestLooper.dispatchAll(); + + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_SUPPORT_UNKNOWN); + assertThat(mTestCallback.getResult()).isEqualTo( + HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } + + @Test + public void matchingReportFeaturesReceived_actionSucceedsAndNetworkIsUpdated() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + mNativeWrapper.onCecMessage( + ReportFeaturesMessage.build( + Constants.ADDR_TV, HDMI_CEC_VERSION_2_0, Collections.emptyList(), + Constants.RC_PROFILE_TV, Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED) + .build() + ) + ); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_SUPPORTED); + assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + private static class TestCallback extends IHdmiControlCallback.Stub { + private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>(); + + @Override + public void onComplete(int result) { + mCallbackResult.add(result); + } + + private int getResult() { + assertThat(mCallbackResult.size()).isEqualTo(1); + return mCallbackResult.get(0); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java index f30e97a98b70..30bcc7e8afa1 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java @@ -98,8 +98,6 @@ public class HdmiCecAtomLoggingTest { doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig(); mHdmiControlServiceSpy.setIoLooper(mLooper); - mHdmiControlServiceSpy.setMessageValidator( - new HdmiCecMessageValidator(mHdmiControlServiceSpy)); mHdmiControlServiceSpy.setCecMessageBuffer( new CecMessageBuffer(mHdmiControlServiceSpy)); @@ -226,7 +224,7 @@ public class HdmiCecAtomLoggingTest { @Test public void testMessageReported_writesAtom_userControlPressed_noParams() { - HdmiCecMessage message = new HdmiCecMessage( + HdmiCecMessage message = HdmiCecMessage.build( Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, Constants.MESSAGE_USER_CONTROL_PRESSED, @@ -279,7 +277,7 @@ public class HdmiCecAtomLoggingTest { @Test public void testMessageReported_writesAtom_featureAbort_noParams() { - HdmiCecMessage message = new HdmiCecMessage( + HdmiCecMessage message = HdmiCecMessage.build( Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, Constants.MESSAGE_FEATURE_ABORT, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index a4113924294b..70bc460411c8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -188,7 +188,6 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); mLocalDevices.add(mHdmiCecLocalDevicePlayback); mHdmiCecLocalDeviceAudioSystem.setRoutingControlFeatureEnabled(true); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 2d13e692a3a9..6fc3354f07e7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -59,10 +59,16 @@ public class HdmiCecLocalDevicePlaybackTest { HotplugDetectionAction.POLLING_INTERVAL_MS_FOR_PLAYBACK; private static final int PORT_1 = 1; - private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo( - ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV, - 0x1234, "TV", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private static final HdmiDeviceInfo INFO_TV = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_TV) + .setPhysicalAddress(0x0000) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_TV) + .setVendorId(0x1234) + .setDisplayName("TV") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; @@ -138,7 +144,6 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDevicePlayback); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = @@ -1691,10 +1696,16 @@ public class HdmiCecLocalDevicePlaybackTest { public void hotplugDetectionAction_removeDevice() { mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlService.getHdmiCecNetwork().clearDeviceList(); - HdmiDeviceInfo infoPlayback = new HdmiDeviceInfo( - Constants.ADDR_PLAYBACK_2, 0x1234, PORT_1, - HdmiDeviceInfo.DEVICE_PLAYBACK, 0x1234, "Playback 2", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + HdmiDeviceInfo infoPlayback = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_2) + .setPhysicalAddress(0x1234) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 2") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback); // This logical address (ADDR_PLAYBACK_2) won't acknowledge the poll message sent by the // HotplugDetectionAction so it shall be removed. @@ -1726,8 +1737,15 @@ public class HdmiCecLocalDevicePlaybackTest { @Test public void getActiveSource_deviceInNetworkIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x3000, 0, - Constants.ADDR_PLAYBACK_1, 0, "Test Device"); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_3) + .setPhysicalAddress(0x3000) + .setPortId(0) + .setDeviceType(Constants.ADDR_PLAYBACK_1) + .setVendorId(0) + .setDisplayName("Test Device") + .build(); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(externalDevice); mTestLooper.dispatchAll(); @@ -1739,8 +1757,14 @@ public class HdmiCecLocalDevicePlaybackTest { @Test public void getActiveSource_unknownDeviceIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x3000, 0, - Constants.ADDR_PLAYBACK_1, 0, "Test Device"); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_3) + .setPhysicalAddress(0x3000) + .setPortId(0) + .setDeviceType(Constants.ADDR_PLAYBACK_1) + .setVendorId(0) + .setDisplayName("Test Device") + .build(); mHdmiControlService.setActiveSource(externalDevice.getLogicalAddress(), externalDevice.getPhysicalAddress(), "HdmiControlServiceTest"); @@ -1933,7 +1957,7 @@ public class HdmiCecLocalDevicePlaybackTest { @Test public void doesNotSupportRecordTvScreen() { - HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_TV, mPlaybackLogicalAddress, + HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_TV, mPlaybackLogicalAddress, Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM); mNativeWrapper.onCecMessage(recordTvScreen); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index f5af6dfc2aae..fb8baa30e6b4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -109,11 +109,6 @@ public class HdmiCecLocalDeviceTest { protected List<Integer> getRcFeatures() { return Collections.emptyList(); } - - @Override - protected List<Integer> getDeviceFeatures() { - return Collections.emptyList(); - } } private MyHdmiCecLocalDevice mHdmiLocalDevice; @@ -187,14 +182,6 @@ public class HdmiCecLocalDeviceTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiLocalDevice = new MyHdmiCecLocalDevice(mHdmiControlService, DEVICE_TV); - mMessageValidator = - new HdmiCecMessageValidator(mHdmiControlService) { - @Override - int isValid(HdmiCecMessage message, boolean isMessageReceived) { - return HdmiCecMessageValidator.OK; - } - }; - mHdmiControlService.setMessageValidator(mMessageValidator); mLocalDevices.add(mHdmiLocalDevice); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; @@ -230,7 +217,7 @@ public class HdmiCecLocalDeviceTest { @Test public void dispatchMessage_logicalAddressDoesNotMatch() { HdmiCecMessage msg = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, ADDR_PLAYBACK_1, Constants.MESSAGE_CEC_VERSION, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index a260a6d34920..b6c4bc23f0e4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -121,7 +121,6 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; hdmiPortInfos[0] = @@ -172,8 +171,14 @@ public class HdmiCecLocalDeviceTvTest { @Test public void getActiveSource_deviceInNetworkIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x1000, 0, - Constants.ADDR_PLAYBACK_1, 0, "Test Device"); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_3) + .setPhysicalAddress(0x3000) + .setPortId(0) + .setDeviceType(Constants.ADDR_PLAYBACK_1) + .setVendorId(0) + .setDisplayName("Test Device") + .build(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(externalDevice); mTestLooper.dispatchAll(); @@ -185,7 +190,7 @@ public class HdmiCecLocalDeviceTvTest { @Test public void getActiveSource_unknownLogicalAddressInNetworkIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(0x1000, 1); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.hardwarePort(0x1000, 1); mHdmiControlService.setActiveSource(Constants.ADDR_UNREGISTERED, externalDevice.getPhysicalAddress(), "HdmiControlServiceTest"); @@ -197,8 +202,14 @@ public class HdmiCecLocalDeviceTvTest { @Test public void getActiveSource_unknownDeviceIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x1000, 0, - Constants.ADDR_PLAYBACK_1, 0, "Test Device"); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_3) + .setPhysicalAddress(0x0000) + .setPortId(0) + .setDeviceType(ADDR_PLAYBACK_1) + .setVendorId(0) + .setDisplayName("Test Device") + .build(); mHdmiControlService.setActiveSource(externalDevice.getLogicalAddress(), externalDevice.getPhysicalAddress(), "HdmiControlServiceTest"); @@ -240,7 +251,7 @@ public class HdmiCecLocalDeviceTvTest { HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED); mTestLooper.dispatchAll(); mPowerManager.setInteractive(false); - HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, + HdmiCecMessage imageViewOn = HdmiCecMessage.build(ADDR_PLAYBACK_1, mTvLogicalAddress, Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); @@ -268,7 +279,7 @@ public class HdmiCecLocalDeviceTvTest { HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED); mTestLooper.dispatchAll(); mPowerManager.setInteractive(false); - HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, + HdmiCecMessage imageViewOn = HdmiCecMessage.build(ADDR_PLAYBACK_1, mTvLogicalAddress, Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); @@ -478,7 +489,7 @@ public class HdmiCecLocalDeviceTvTest { @Test public void supportsRecordTvScreen() { - HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_RECORDER_1, mTvLogicalAddress, + HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_RECORDER_1, mTvLogicalAddress, Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM); mNativeWrapper.onCecMessage(recordTvScreen); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java index 453303eb5715..f869462f46c5 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java @@ -22,20 +22,15 @@ import static com.android.server.hdmi.HdmiUtils.buildMessage; import static com.google.common.truth.Truth.assertThat; -import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; -import com.google.android.collect.Lists; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.Collections; - @SmallTest @Presubmit @RunWith(JUnit4.class) @@ -100,89 +95,4 @@ public class HdmiCecMessageBuilderTest { assertThat(message).isEqualTo(buildMessage("40:A5")); } - - @Test - public void buildReportFeatures_basicTv_1_4() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_1_4_B, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("0F:A6:05:80:00:00")); - } - - @Test - public void buildReportFeatures_basicPlayback_1_4() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1, - HdmiControlManager.HDMI_CEC_VERSION_1_4_B, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("4F:A6:05:10:00:00")); - } - - @Test - public void buildReportFeatures_basicPlaybackAudioSystem_1_4() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1, - HdmiControlManager.HDMI_CEC_VERSION_1_4_B, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK, - HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("4F:A6:05:18:00:00")); - } - - @Test - public void buildReportFeatures_basicTv_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:00")); - } - - @Test - public void buildReportFeatures_remoteControlTv_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_ONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:02:00")); - } - - @Test - public void buildReportFeatures_remoteControlPlayback_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE, - Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU, - Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:00")); - } - - @Test - public void buildReportFeatures_deviceFeaturesTv_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), - Lists.newArrayList(Constants.DEVICE_FEATURE_TV_SUPPORTS_RECORD_TV_SCREEN)); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:40")); - } - - @Test - public void buildReportFeatures_deviceFeaturesPlayback_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE, - Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU, - Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), - Lists.newArrayList(Constants.DEVICE_FEATURE_SUPPORTS_DECK_CONTROL)); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:10")); - } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java index cca5094065e6..2984cfa06550 100755 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java @@ -41,12 +41,12 @@ public class HdmiCecMessageTest { new EqualsTester() .addEqualityGroup( - new HdmiCecMessage(source, destination, opcode, params1), - new HdmiCecMessage(source, destination, opcode, params1)) - .addEqualityGroup(new HdmiCecMessage(source, destination, opcode, params2)) - .addEqualityGroup(new HdmiCecMessage(source + 1, destination, opcode, params1)) - .addEqualityGroup(new HdmiCecMessage(source, destination + 1, opcode, params1)) - .addEqualityGroup(new HdmiCecMessage(source, destination, opcode + 1, params1)) + HdmiCecMessage.build(source, destination, opcode, params1), + HdmiCecMessage.build(source, destination, opcode, params1)) + .addEqualityGroup(HdmiCecMessage.build(source, destination, opcode, params2)) + .addEqualityGroup(HdmiCecMessage.build(source + 1, destination, opcode, params1)) + .addEqualityGroup(HdmiCecMessage.build(source, destination + 1, opcode, params1)) + .addEqualityGroup(HdmiCecMessage.build(source, destination, opcode + 1, params1)) .testEquals(); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index 548a439024ca..50c9f70ccb03 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -54,7 +54,6 @@ public class HdmiCecMessageValidatorTest { InstrumentationRegistry.getTargetContext(), Collections.emptyList()); mHdmiControlService.setIoLooper(mTestLooper.getLooper()); - mHdmiCecMessageValidator = new HdmiCecMessageValidator(mHdmiControlService); } @Test @@ -400,16 +399,6 @@ public class HdmiCecMessageValidatorTest { } @Test - public void isValid_reportFeatures() { - assertMessageValidity("0F:A6:05:80:00:00").isEqualTo(OK); - - assertMessageValidity("04:A6:05:80:00:00").isEqualTo(ERROR_DESTINATION); - assertMessageValidity("FF:A6:05:80:00:00").isEqualTo(ERROR_SOURCE); - - assertMessageValidity("0F:A6").isEqualTo(ERROR_PARAMETER_SHORT); - } - - @Test public void isValid_deckControl() { assertMessageValidity("40:42:01:6E").isEqualTo(OK); assertMessageValidity("40:42:04").isEqualTo(OK); @@ -649,6 +638,6 @@ public class HdmiCecMessageValidatorTest { } private IntegerSubject assertMessageValidity(String message) { - return assertThat(mHdmiCecMessageValidator.isValid(HdmiUtils.buildMessage(message), false)); + return assertThat(HdmiUtils.buildMessage(message).getValidationResult()); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java index 1048eb5acd1a..42fa32cabc06 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java @@ -17,9 +17,12 @@ package com.android.server.hdmi; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; + import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -80,7 +83,6 @@ public class HdmiCecNetworkTest { mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService, mHdmiCecController, mHdmiMhlControllerStub); @@ -178,7 +180,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( HdmiUtils.getDefaultDeviceName(logicalAddress)); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_UNKNOWN); @@ -216,7 +218,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( HdmiUtils.getDefaultDeviceName(logicalAddress)); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_UNKNOWN); } @@ -258,7 +260,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( Constants.INVALID_PHYSICAL_ADDRESS); assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( HdmiUtils.getDefaultDeviceName(logicalAddress)); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); @@ -279,7 +281,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( Constants.INVALID_PHYSICAL_ADDRESS); assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_UNKNOWN); @@ -471,7 +473,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( Constants.INVALID_PHYSICAL_ADDRESS); assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( HdmiUtils.getDefaultDeviceName(logicalAddress)); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); @@ -514,12 +516,14 @@ public class HdmiCecNetworkTest { int logicalAddress = Constants.ADDR_PLAYBACK_1; int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; mHdmiCecNetwork.handleCecMessage( - HdmiCecMessageBuilder.buildReportFeatures(logicalAddress, + ReportFeaturesMessage.build(logicalAddress, cecVersion, Collections.emptyList(), Constants.RC_PROFILE_SOURCE, Collections.emptyList(), - Collections.emptyList())); + DeviceFeatures.NO_FEATURES_SUPPORTED)); - assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + synchronized (mHdmiCecNetwork.mLock) { + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + } HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); @@ -531,12 +535,14 @@ public class HdmiCecNetworkTest { int logicalAddress = Constants.ADDR_PLAYBACK_1; int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; mHdmiCecNetwork.handleCecMessage( - HdmiCecMessageBuilder.buildReportFeatures(logicalAddress, + ReportFeaturesMessage.build(logicalAddress, cecVersion, Collections.emptyList(), Constants.RC_PROFILE_SOURCE, Collections.emptyList(), - Collections.emptyList())); + DeviceFeatures.NO_FEATURES_SUPPORTED)); - assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + synchronized (mHdmiCecNetwork.mLock) { + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + } HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); @@ -544,10 +550,33 @@ public class HdmiCecNetworkTest { } @Test - public void getSafeCecDevicesLocked_addDevice_sizeOne() { - HdmiDeviceInfo cecDeviceInfo = new HdmiDeviceInfo(); + public void cecDevices_tracking_reportFeatures_updatesDeviceFeatures() { + // Features should be set correctly with the initial <Report Features> + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; + DeviceFeatures deviceFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED; + mHdmiCecNetwork.handleCecMessage( + ReportFeaturesMessage.build(logicalAddress, + cecVersion, Collections.emptyList(), + Constants.RC_PROFILE_SOURCE, Collections.emptyList(), deviceFeatures)); - mHdmiCecNetwork.addCecDevice(cecDeviceInfo); + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getDeviceFeatures()).isEqualTo(deviceFeatures); + + // New information from <Report Features> should override old information + DeviceFeatures updatedFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED).build(); + mHdmiCecNetwork.handleCecMessage( + ReportFeaturesMessage.build(logicalAddress, + cecVersion, Collections.emptyList(), + Constants.RC_PROFILE_SOURCE, Collections.emptyList(), updatedFeatures)); + cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getDeviceFeatures()).isEqualTo(updatedFeatures); + } + + @Test + public void getSafeCecDevicesLocked_addDevice_sizeOne() { + mHdmiCecNetwork.addCecDevice(HdmiDeviceInfo.INACTIVE_DEVICE); assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java index bff12968e8cd..7a68285bc003 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java @@ -98,7 +98,6 @@ public class HdmiCecPowerStatusControllerTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDevicePlayback); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index a44bd8e98b57..7751ef564138 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -111,8 +111,6 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.setCecController(mHdmiCecController); mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create( mHdmiControlServiceSpy)); - mHdmiControlServiceSpy.setMessageValidator(new HdmiCecMessageValidator( - mHdmiControlServiceSpy)); mLocalDevices.add(mAudioSystemDeviceSpy); mLocalDevices.add(mPlaybackDeviceSpy); @@ -487,7 +485,7 @@ public class HdmiControlServiceTest { Constants.ADDR_PLAYBACK_1)); mTestLooper.dispatchAll(); - HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( + HdmiCecMessage reportFeatures = ReportFeaturesMessage.build( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), @@ -505,7 +503,7 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); - HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( + HdmiCecMessage reportFeatures = ReportFeaturesMessage.build( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), @@ -522,7 +520,7 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); - HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( + HdmiCecMessage reportFeatures = ReportFeaturesMessage.build( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java index 22ad956fcc10..561e6a5fec41 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java @@ -57,10 +57,16 @@ public class OneTouchPlayActionTest { new byte[]{HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON}; private static final int PORT_1 = 1; - private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo( - ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV, - 0x1234, "TV", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private static final HdmiDeviceInfo INFO_TV = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_TV) + .setPhysicalAddress(0x0000) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_TV) + .setVendorId(0x1234) + .setDisplayName("TV") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); private Context mContextSpy; private HdmiControlService mHdmiControlService; @@ -113,7 +119,6 @@ public class OneTouchPlayActionTest { this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); @@ -165,7 +170,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -218,7 +223,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -271,7 +276,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusTransientToOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -284,7 +289,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -428,7 +433,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -482,7 +487,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java index 2f22bce18558..c878f99f7912 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -99,7 +99,6 @@ public class PowerStatusMonitorActionTest { this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService); mTvDevice.init(); mLocalDevices.add(mTvDevice); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java new file mode 100644 index 000000000000..22f1f431db0a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java @@ -0,0 +1,176 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; +import static com.android.server.hdmi.Constants.ADDR_TV; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE; +import static com.android.server.hdmi.HdmiCecMessageValidator.OK; +import static com.android.server.hdmi.HdmiUtils.buildMessage; + +import static com.google.common.truth.Truth.assertThat; + +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.google.android.collect.Lists; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class ReportFeaturesMessageTest { + @Test + public void build_invalidMessages() { + assertThat(HdmiUtils.buildMessage("FF:A6:05:80:00:00") + .getValidationResult()).isEqualTo(ERROR_SOURCE); + assertThat(HdmiUtils.buildMessage("04:A6:05:80:00:00") + .getValidationResult()).isEqualTo(ERROR_DESTINATION); + assertThat(HdmiUtils.buildMessage("0F:A6") + .getValidationResult()).isEqualTo(ERROR_PARAMETER_SHORT); + assertThat(HdmiUtils.buildMessage("4F:A6:06:00:80:80:00") + .getValidationResult()).isEqualTo(ERROR_PARAMETER_SHORT); + } + + @Test + public void build_longMessage() { + HdmiCecMessage longMessage = HdmiUtils.buildMessage("4F:A6:05:00:80:80:00:81:80:00"); + assertThat(longMessage).isInstanceOf(ReportFeaturesMessage.class); + ReportFeaturesMessage longReportFeaturesMessage = (ReportFeaturesMessage) longMessage; + + HdmiCecMessage shortMessage = HdmiUtils.buildMessage("4F:A6:05:00:00:01"); + assertThat(shortMessage).isInstanceOf(ReportFeaturesMessage.class); + ReportFeaturesMessage shortReportFeaturesMessage = (ReportFeaturesMessage) shortMessage; + + assertThat(longReportFeaturesMessage.getDeviceFeatures()).isEqualTo( + shortReportFeaturesMessage.getDeviceFeatures()); + assertThat(longReportFeaturesMessage.getCecVersion()).isEqualTo( + shortReportFeaturesMessage.getCecVersion()); + } + + @Test + public void build_basicTv_1_4() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_1_4_B, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:05:80:00:00")); + } + + @Test + public void build_basicPlayback_1_4() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_PLAYBACK_1, + HdmiControlManager.HDMI_CEC_VERSION_1_4_B, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("4F:A6:05:10:00:00")); + } + + @Test + public void build_basicPlaybackAudioSystem_1_4() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_PLAYBACK_1, + HdmiControlManager.HDMI_CEC_VERSION_1_4_B, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK, + HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("4F:A6:05:18:00:00")); + } + + @Test + public void build_basicTv_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:00")); + } + + @Test + public void build_remoteControlTv_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_ONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:02:00")); + } + + @Test + public void build_remoteControlPlayback_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE, + Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU, + Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:00")); + } + + @Test + public void build_deviceFeaturesTv_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setRecordTvScreenSupport(DeviceFeatures.FEATURE_SUPPORTED) + .build()); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:40")); + } + + @Test + public void build_deviceFeaturesPlayback_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE, + Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU, + Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setDeckControlSupport(DeviceFeatures.FEATURE_SUPPORTED) + .build()); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:10")); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index 2d81fc94e274..6184c2116e1d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -123,7 +123,6 @@ public class RequestSadActionTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(context); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java index 302c0ac399d0..0587864eeb20 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java @@ -101,19 +101,29 @@ public class RoutingControlActionTest { private static final byte[] PLAYER_PARAM = new byte[]{(PHYSICAL_ADDRESS_PLAYER >> 8) & 0xFF, PHYSICAL_ADDRESS_PLAYER & 0xFF}; - private static final HdmiDeviceInfo DEVICE_INFO_AVR = - new HdmiDeviceInfo(ADDR_AUDIO_SYSTEM, PHYSICAL_ADDRESS_AVR, PORT_1, - HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, VENDOR_ID_AVR, "Audio"); - private static final HdmiDeviceInfo DEVICE_INFO_PLAYER = - new HdmiDeviceInfo(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYER, PORT_1, - HdmiDeviceInfo.DEVICE_PLAYBACK, VENDOR_ID_AVR, "Player"); - private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = new HdmiCecMessage( + private static final HdmiDeviceInfo DEVICE_INFO_AVR = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_AUDIO_SYSTEM) + .setPhysicalAddress(PHYSICAL_ADDRESS_AVR) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) + .setVendorId(VENDOR_ID_AVR) + .setDisplayName("Audio") + .build(); + private static final HdmiDeviceInfo DEVICE_INFO_PLAYER = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_PLAYBACK_1) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYER) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(VENDOR_ID_AVR) + .setDisplayName("Player") + .build(); + private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = HdmiCecMessage.build( ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, TUNER_PARAM); - private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = new HdmiCecMessage( + private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = HdmiCecMessage.build( ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, PLAYER_PARAM); - private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = new HdmiCecMessage( + private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = HdmiCecMessage.build( ADDR_TUNER_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, TUNER_PARAM); - private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = new HdmiCecMessage( + private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = HdmiCecMessage.build( ADDR_PLAYBACK_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, PLAYER_PARAM); private HdmiControlService mHdmiControlService; @@ -169,7 +179,6 @@ public class RoutingControlActionTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java new file mode 100644 index 000000000000..a34b55c00308 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java @@ -0,0 +1,218 @@ +/* + * 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.hdmi; + +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN; + +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.Looper; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.server.SystemService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Collections; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class SetAudioVolumeLevelDiscoveryActionTest { + private HdmiControlService mHdmiControlServiceSpy; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevicePlayback mPlaybackDevice; + private FakeNativeWrapper mNativeWrapper; + private FakePowerManagerWrapper mPowerManager; + private Looper mLooper; + private Context mContextSpy; + private TestLooper mTestLooper = new TestLooper(); + private int mPhysicalAddress = 0x1100; + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mPlaybackLogicalAddress; + + private TestCallback mTestCallback; + private SetAudioVolumeLevelDiscoveryAction mAction; + + /** + * Setup: Local Playback device attempts to determine whether a connected TV supports + * <Set Audio Volume Level>. + */ + @Before + public void setUp() throws RemoteException { + mContextSpy = spy(new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList())); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); + + mLooper = mTestLooper.getLooper(); + mHdmiControlServiceSpy.setIoLooper(mLooper); + mHdmiControlServiceSpy.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); + + mNativeWrapper = new FakeNativeWrapper(); + mNativeWrapper.setPhysicalAddress(mPhysicalAddress); + + mHdmiCecController = HdmiCecController.createWithNativeWrapper( + mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter()); + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + mHdmiControlServiceSpy.setHdmiMhlController( + HdmiMhlControllerStub.create(mHdmiControlServiceSpy)); + mHdmiControlServiceSpy.initService(); + mPowerManager = new FakePowerManagerWrapper(mContextSpy); + mHdmiControlServiceSpy.setPowerManager(mPowerManager); + + mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy); + mPlaybackDevice.init(); + mLocalDevices.add(mPlaybackDevice); + + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + mTestLooper.dispatchAll(); + + synchronized (mPlaybackDevice.mLock) { + mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress(); + } + + // Setup specific to these tests + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + Constants.ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV)); + mTestLooper.dispatchAll(); + + mTestCallback = new TestCallback(); + mAction = new SetAudioVolumeLevelDiscoveryAction(mPlaybackDevice, + Constants.ADDR_TV, mTestCallback); + } + + @Test + public void sendsSetAudioVolumeLevel() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + HdmiCecMessage setAudioVolumeLevel = SetAudioVolumeLevelMessage.build( + mPlaybackLogicalAddress, Constants.ADDR_TV, + Constants.AUDIO_VOLUME_STATUS_UNKNOWN); + assertThat(mNativeWrapper.getResultMessages()).contains(setAudioVolumeLevel); + } + + @Test + public void noMatchingFeatureAbortReceived_actionSucceedsAndSetsFeatureSupported() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + // Wrong opcode + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand( + Constants.ADDR_TV, + mPlaybackLogicalAddress, + Constants.MESSAGE_GIVE_DECK_STATUS, + Constants.ABORT_UNRECOGNIZED_OPCODE)); + // Wrong source + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand( + Constants.ADDR_AUDIO_SYSTEM, + mPlaybackLogicalAddress, + Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + Constants.ABORT_UNRECOGNIZED_OPCODE)); + mTestLooper.dispatchAll(); + + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_SUPPORTED); + assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void matchingFeatureAbortReceived_actionSucceedsAndSetsFeatureNotSupported() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand( + Constants.ADDR_TV, + mPlaybackLogicalAddress, + Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + Constants.ABORT_UNRECOGNIZED_OPCODE)); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_NOT_SUPPORTED); + assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void messageFailedToSend_actionFailsAndDoesNotUpdateFeatureSupport() { + mNativeWrapper.setMessageSendResult(Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + SendMessageResult.FAIL); + mTestLooper.dispatchAll(); + + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_SUPPORT_UNKNOWN); + assertThat(mTestCallback.getResult()).isEqualTo( + HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } + + private static class TestCallback extends IHdmiControlCallback.Stub { + private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>(); + + @Override + public void onComplete(int result) { + mCallbackResult.add(result); + } + + private int getResult() { + assertThat(mCallbackResult.size()).isEqualTo(1); + return mCallbackResult.get(0); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java new file mode 100644 index 000000000000..0201c68361f8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java @@ -0,0 +1,68 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE; +import static com.android.server.hdmi.HdmiUtils.buildMessage; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class SetAudioVolumeLevelMessageTest { + @Test + public void build_maxVolume() { + HdmiCecMessage message = SetAudioVolumeLevelMessage.build( + Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, 100); + assertThat(message.getValidationResult()).isEqualTo(HdmiCecMessageValidator.OK); + assertThat(message).isEqualTo(buildMessage("04:73:64")); + } + + @Test + public void build_noVolumeChange() { + HdmiCecMessage message = SetAudioVolumeLevelMessage.build( + Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM, 0x7F); + assertThat(message.getValidationResult()).isEqualTo(HdmiCecMessageValidator.OK); + assertThat(message).isEqualTo(buildMessage("05:73:7F")); + } + + @Test + public void build_invalid() { + assertThat(SetAudioVolumeLevelMessage + .build(Constants.ADDR_UNREGISTERED, Constants.ADDR_AUDIO_SYSTEM, 50) + .getValidationResult()) + .isEqualTo(ERROR_SOURCE); + assertThat(SetAudioVolumeLevelMessage + .build(Constants.ADDR_TV, Constants.ADDR_BROADCAST, 50) + .getValidationResult()) + .isEqualTo(ERROR_DESTINATION); + assertThat(HdmiUtils.buildMessage("04:73") + .getValidationResult()) + .isEqualTo(ERROR_PARAMETER_SHORT); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java index b34b853b4ca3..9d143418fd4c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java @@ -100,7 +100,6 @@ public class SystemAudioAutoInitiationActionTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; hdmiPortInfos[0] = diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index b40650e767fd..095c69c776a2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -64,7 +64,7 @@ public class SystemAudioInitiationActionFromAvrTest { @Before public void SetUp() { - mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); + mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234); Context context = InstrumentationRegistry.getTargetContext(); diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java index 70e78eb41999..c77100045118 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java @@ -17,9 +17,7 @@ package com.android.server.locales; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; @@ -43,11 +41,10 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Binder; -import android.os.Environment; import android.os.LocaleList; import android.os.RemoteException; import android.os.SimpleClock; -import android.util.AtomicFile; +import android.util.SparseArray; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; @@ -57,6 +54,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.content.PackageMonitor; import com.android.internal.util.XmlUtils; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -65,10 +63,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.time.Clock; @@ -95,9 +90,8 @@ public class LocaleManagerBackupRestoreTest { LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS); private static final Map<String, String> DEFAULT_PACKAGE_LOCALES_MAP = Map.of( DEFAULT_PACKAGE_NAME, DEFAULT_LOCALE_TAGS); - private static final File STAGED_LOCALES_DIR = new File( - Environment.getExternalStorageDirectory(), "lmsUnitTests"); - + private static final SparseArray<LocaleManagerBackupHelper.StagedData> STAGE_DATA = + new SparseArray<>(); private LocaleManagerBackupHelper mBackupHelper; private long mCurrentTimeMillis; @@ -138,14 +132,17 @@ public class LocaleManagerBackupRestoreTest { doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext, - mMockLocaleManagerService, mMockPackageManagerInternal, - new File(Environment.getExternalStorageDirectory(), "lmsUnitTests"), mClock)); + mMockLocaleManagerService, mMockPackageManagerInternal, mClock, STAGE_DATA)); doNothing().when(mBackupHelper).notifyBackupManager(); mUserMonitor = mBackupHelper.getUserMonitor(); mPackageMonitor = mBackupHelper.getPackageMonitor(); setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS); - cleanStagedFiles(); + } + + @After + public void tearDown() throws Exception { + STAGE_DATA.clear(); } @Test @@ -203,25 +200,25 @@ public class LocaleManagerBackupRestoreTest { } @Test - public void testRestore_nullPayload_nothingRestoredAndNoStageFile() throws Exception { + public void testRestore_nullPayload_nothingRestoredAndNoStageData() throws Exception { mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ null, DEFAULT_USER_ID); verifyNothingRestored(); - checkStageFileDoesNotExist(DEFAULT_USER_ID); + checkStageDataDoesNotExist(DEFAULT_USER_ID); } @Test - public void testRestore_zeroLengthPayload_nothingRestoredAndNoStageFile() throws Exception { + public void testRestore_zeroLengthPayload_nothingRestoredAndNoStageData() throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream(); mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ out.toByteArray(), DEFAULT_USER_ID); verifyNothingRestored(); - checkStageFileDoesNotExist(DEFAULT_USER_ID); + checkStageDataDoesNotExist(DEFAULT_USER_ID); } @Test - public void testRestore_allAppsInstalled_noStageFileCreated() throws Exception { + public void testRestore_allAppsInstalled_noStageDataCreated() throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream(); writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP); @@ -234,8 +231,7 @@ public class LocaleManagerBackupRestoreTest { verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, DEFAULT_LOCALES); - // Stage file wasn't created. - checkStageFileDoesNotExist(DEFAULT_USER_ID); + checkStageDataDoesNotExist(DEFAULT_USER_ID); } @Test @@ -248,8 +244,8 @@ public class LocaleManagerBackupRestoreTest { mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); verifyNothingRestored(); - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, - getStageFileIfExists(DEFAULT_USER_ID), DEFAULT_CREATION_TIME_MILLIS); + verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); } @Test @@ -257,8 +253,10 @@ public class LocaleManagerBackupRestoreTest { final ByteArrayOutputStream out = new ByteArrayOutputStream(); HashMap<String, String> pkgLocalesMap = new HashMap<>(); - String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB"; - String langTagsA = "ru", langTagsB = "hi,fr"; + String pkgNameA = "com.android.myAppA"; + String pkgNameB = "com.android.myAppB"; + String langTagsA = "ru"; + String langTagsB = "hi,fr"; pkgLocalesMap.put(pkgNameA, langTagsA); pkgLocalesMap.put(pkgNameB, langTagsB); writeTestPayload(out, pkgLocalesMap); @@ -273,12 +271,12 @@ public class LocaleManagerBackupRestoreTest { LocaleList.forLanguageTags(langTagsA)); pkgLocalesMap.remove(pkgNameA); - verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID), - DEFAULT_CREATION_TIME_MILLIS); + verifyStageDataForUser(pkgLocalesMap, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); } @Test - public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageFile() throws Exception { + public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageData() throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream(); writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP); @@ -289,8 +287,7 @@ public class LocaleManagerBackupRestoreTest { // Since locales are already set, we should not restore anything for it. verifyNothingRestored(); - // Stage file wasn't created - checkStageFileDoesNotExist(DEFAULT_USER_ID); + checkStageDataDoesNotExist(DEFAULT_USER_ID); } @Test @@ -299,9 +296,12 @@ public class LocaleManagerBackupRestoreTest { final ByteArrayOutputStream out = new ByteArrayOutputStream(); HashMap<String, String> pkgLocalesMap = new HashMap<>(); - String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB", pkgNameC = - "com.android.myAppC"; - String langTagsA = "ru", langTagsB = "hi,fr", langTagsC = "zh,es"; + String pkgNameA = "com.android.myAppA"; + String pkgNameB = "com.android.myAppB"; + String pkgNameC = "com.android.myAppC"; + String langTagsA = "ru"; + String langTagsB = "hi,fr"; + String langTagsC = "zh,es"; pkgLocalesMap.put(pkgNameA, langTagsA); pkgLocalesMap.put(pkgNameB, langTagsB); pkgLocalesMap.put(pkgNameC, langTagsC); @@ -328,8 +328,8 @@ public class LocaleManagerBackupRestoreTest { // App C is staged. pkgLocalesMap.remove(pkgNameA); pkgLocalesMap.remove(pkgNameB); - verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID), - DEFAULT_CREATION_TIME_MILLIS); + verifyStageDataForUser(pkgLocalesMap, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); } @Test @@ -341,15 +341,15 @@ public class LocaleManagerBackupRestoreTest { mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID), - DEFAULT_CREATION_TIME_MILLIS); + verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); final long newCreationTime = DEFAULT_CREATION_TIME_MILLIS + 100; setCurrentTimeMillis(newCreationTime); mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID), - newCreationTime); + verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP, + newCreationTime, DEFAULT_USER_ID); } @Test @@ -357,8 +357,10 @@ public class LocaleManagerBackupRestoreTest { final ByteArrayOutputStream out = new ByteArrayOutputStream(); HashMap<String, String> pkgLocalesMap = new HashMap<>(); - String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB"; - String langTagsA = "ru", langTagsB = "hi,fr"; + String pkgNameA = "com.android.myAppA"; + String pkgNameB = "com.android.myAppB"; + String langTagsA = "ru"; + String langTagsB = "hi,fr"; pkgLocalesMap.put(pkgNameA, langTagsA); pkgLocalesMap.put(pkgNameB, langTagsB); writeTestPayload(out, pkgLocalesMap); @@ -380,8 +382,7 @@ public class LocaleManagerBackupRestoreTest { LocaleList.forLanguageTags(langTagsA)); pkgLocalesMap.remove(pkgNameA); - verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID), - DEFAULT_CREATION_TIME_MILLIS); + verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); setUpPackageInstalled(pkgNameB); @@ -389,7 +390,7 @@ public class LocaleManagerBackupRestoreTest { verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsB)); - checkStageFileDoesNotExist(DEFAULT_USER_ID); + checkStageDataDoesNotExist(DEFAULT_USER_ID); } @Test @@ -404,8 +405,8 @@ public class LocaleManagerBackupRestoreTest { mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); verifyNothingRestored(); - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID), - DEFAULT_CREATION_TIME_MILLIS); + verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); // App is installed later (post SUW). setUpPackageInstalled(DEFAULT_PACKAGE_NAME); @@ -415,11 +416,11 @@ public class LocaleManagerBackupRestoreTest { // Since locales are already set, we should not restore anything for it. verifyNothingRestored(); - checkStageFileDoesNotExist(DEFAULT_USER_ID); + checkStageDataDoesNotExist(DEFAULT_USER_ID); } @Test - public void testStageFileDeletion_backupPassRunAfterRetentionPeriod_stageFileDeleted() + public void testStageDataDeletion_backupPassRunAfterRetentionPeriod_stageDataDeleted() throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream(); writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP); @@ -429,8 +430,8 @@ public class LocaleManagerBackupRestoreTest { mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); verifyNothingRestored(); - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID), - DEFAULT_CREATION_TIME_MILLIS); + verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); // Retention period has not elapsed. setCurrentTimeMillis( @@ -439,32 +440,78 @@ public class LocaleManagerBackupRestoreTest { .getInstalledApplications(anyLong(), anyInt(), anyInt()); assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID)); - // Stage file should NOT be deleted. - checkStageFileExists(DEFAULT_USER_ID); + checkStageDataExists(DEFAULT_USER_ID); - // Exactly RETENTION_PERIOD amount of time has passed so stage file should still not be + // Exactly RETENTION_PERIOD amount of time has passed so stage data should still not be // removed. setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis()); doReturn(List.of()).when(mMockPackageManagerInternal) .getInstalledApplications(anyLong(), anyInt(), anyInt()); assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID)); - // Stage file should NOT be deleted. - checkStageFileExists(DEFAULT_USER_ID); + checkStageDataExists(DEFAULT_USER_ID); - // Retention period has now expired, stage file should be deleted. + // Retention period has now expired, stage data should be deleted. setCurrentTimeMillis( DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis()); doReturn(List.of()).when(mMockPackageManagerInternal) .getInstalledApplications(anyLong(), anyInt(), anyInt()); assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID)); - // Stage file should be deleted. - checkStageFileDoesNotExist(DEFAULT_USER_ID); + checkStageDataDoesNotExist(DEFAULT_USER_ID); } @Test - public void testUserRemoval_userRemoved_stageFileDeleted() throws Exception { + public void testStageDataDeletion_lazyRestoreAfterRetentionPeriod_stageDataDeleted() + throws Exception { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + HashMap<String, String> pkgLocalesMap = new HashMap<>(); + + String pkgNameA = "com.android.myAppA"; + String pkgNameB = "com.android.myAppB"; + String langTagsA = "ru"; + String langTagsB = "hi,fr"; + pkgLocalesMap.put(pkgNameA, langTagsA); + pkgLocalesMap.put(pkgNameB, langTagsB); + writeTestPayload(out, pkgLocalesMap); + + setUpPackageNotInstalled(pkgNameA); + setUpPackageNotInstalled(pkgNameB); + setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList()); + setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList()); + + mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); + + verifyNothingRestored(); + verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); + + // Retention period has not elapsed. + setCurrentTimeMillis( + DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis()); + + setUpPackageInstalled(pkgNameA); + mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID); + + verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID, + LocaleList.forLanguageTags(langTagsA)); + + pkgLocalesMap.remove(pkgNameA); + verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); + + // Retention period has now expired, stage data should be deleted. + setCurrentTimeMillis( + DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis()); + setUpPackageInstalled(pkgNameB); + mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID); + + verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(), + any()); + + checkStageDataDoesNotExist(DEFAULT_USER_ID); + } + + @Test + public void testUserRemoval_userRemoved_stageDataDeleted() throws Exception { final ByteArrayOutputStream outDefault = new ByteArrayOutputStream(); writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_MAP); @@ -485,119 +532,20 @@ public class LocaleManagerBackupRestoreTest { verifyNothingRestored(); - // Verify stage file contents. - AtomicFile stageFileDefaultUser = getStageFileIfExists(DEFAULT_USER_ID); - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, stageFileDefaultUser, - DEFAULT_CREATION_TIME_MILLIS); - - AtomicFile stageFileWorkProfile = getStageFileIfExists(WORK_PROFILE_USER_ID); - verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile, - DEFAULT_CREATION_TIME_MILLIS); + verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); + verifyStageDataForUser(pkgLocalesMapWorkProfile, + DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID); Intent intent = new Intent(); intent.setAction(Intent.ACTION_USER_REMOVED); intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID); mUserMonitor.onReceive(mMockContext, intent); - // Stage file should be removed only for DEFAULT_USER_ID. - checkStageFileDoesNotExist(DEFAULT_USER_ID); - verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile, - DEFAULT_CREATION_TIME_MILLIS); - } - - @Test - public void testLoadStageFiles_invalidNameFormat_stageFileDeleted() throws Exception { - // Stage file name should be : staged_locales_<user_id_int>.xml - File stageFile = new File(STAGED_LOCALES_DIR, "xyz.xml"); - assertTrue(stageFile.createNewFile()); - assertTrue(stageFile.isFile()); - - // Putting valid xml data in file. - FileOutputStream out = new FileOutputStream(stageFile); - writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */ - true, /* creationTimeMillis= */ 0); - out.flush(); - out.close(); - - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, - new AtomicFile(stageFile), /* creationTimeMillis= */ 0); - - mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService, - mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock); - assertFalse(stageFile.isFile()); - } - - @Test - public void testLoadStageFiles_userIdNotParseable_stageFileDeleted() throws Exception { - // Stage file name should be : staged_locales_<user_id_int>.xml - File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_abc.xml"); - assertTrue(stageFile.createNewFile()); - assertTrue(stageFile.isFile()); - - // Putting valid xml data in file. - FileOutputStream out = new FileOutputStream(stageFile); - writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */ - true, /* creationTimeMillis= */ 0); - out.flush(); - out.close(); - - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, - new AtomicFile(stageFile), /* creationTimeMillis= */ 0); - - mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService, - mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock); - assertFalse(stageFile.isFile()); - } - - @Test - public void testLoadStageFiles_invalidContent_stageFileDeleted() throws Exception { - File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml"); - assertTrue(stageFile.createNewFile()); - assertTrue(stageFile.isFile()); - - FileOutputStream out = new FileOutputStream(stageFile); - out.write("some_non_xml_string".getBytes()); - out.close(); - - mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService, - mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock); - assertFalse(stageFile.isFile()); - } - - @Test - public void testLoadStageFiles_validContent_doesLazyRestore() throws Exception { - File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml"); - assertTrue(stageFile.createNewFile()); - assertTrue(stageFile.isFile()); - - // Putting valid xml data in file. - FileOutputStream out = new FileOutputStream(stageFile); - writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */ - true, DEFAULT_CREATION_TIME_MILLIS); - out.flush(); - out.close(); - - verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, - new AtomicFile(stageFile), DEFAULT_CREATION_TIME_MILLIS); - - mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService, - mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock); - mPackageMonitor = mBackupHelper.getPackageMonitor(); - - // Stage file still exists. - assertTrue(stageFile.isFile()); - - // App is installed later. - setUpPackageInstalled(DEFAULT_PACKAGE_NAME); - setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList()); - - mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID); - - verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME, - DEFAULT_USER_ID, DEFAULT_LOCALES); - - // Stage file gets deleted here because all staged locales have been applied. - assertFalse(stageFile.isFile()); + // Stage data should be removed only for DEFAULT_USER_ID. + checkStageDataDoesNotExist(DEFAULT_USER_ID); + verifyStageDataForUser(pkgLocalesMapWorkProfile, + DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID); } private void setUpPackageInstalled(String packageName) throws Exception { @@ -633,27 +581,15 @@ public class LocaleManagerBackupRestoreTest { any()); } - private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap, byte[] payload) throws IOException, XmlPullParserException { - verifyPayloadForAppLocales(expectedPkgLocalesMap, payload, /* forStage= */ false, -1); - } - - private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap, - byte[] payload, boolean forStage, long expectedCreationTime) - throws IOException, XmlPullParserException { final ByteArrayInputStream stream = new ByteArrayInputStream(payload); final TypedXmlPullParser parser = Xml.newFastPullParser(); parser.setInput(stream, StandardCharsets.UTF_8.name()); Map<String, String> backupDataMap = new HashMap<>(); XmlUtils.beginDocument(parser, TEST_LOCALES_XML_TAG); - if (forStage) { - long actualCreationTime = parser.getAttributeLong(/* namespace= */ null, - "creationTimeMillis"); - assertEquals(expectedCreationTime, actualCreationTime); - } int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { if (parser.getName().equals("package")) { @@ -668,13 +604,6 @@ public class LocaleManagerBackupRestoreTest { private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap) throws IOException { - writeTestPayload(stream, pkgLocalesMap, /* forStage= */ false, /* creationTimeMillis= */ - -1); - } - - private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap, - boolean forStage, long creationTimeMillis) - throws IOException { if (pkgLocalesMap.isEmpty()) { return; } @@ -684,11 +613,6 @@ public class LocaleManagerBackupRestoreTest { out.startDocument(/* encoding= */ null, /* standalone= */ true); out.startTag(/* namespace= */ null, TEST_LOCALES_XML_TAG); - if (forStage) { - out.attribute(/* namespace= */ null, "creationTimeMillis", - Long.toString(creationTimeMillis)); - } - for (String pkg : pkgLocalesMap.keySet()) { out.startTag(/* namespace= */ null, "package"); out.attribute(/* namespace= */ null, "name", pkg); @@ -700,41 +624,19 @@ public class LocaleManagerBackupRestoreTest { out.endDocument(); } - private static void verifyStageFileContent(Map<String, String> expectedPkgLocalesMap, - AtomicFile stageFile, - long creationTimeMillis) - throws Exception { - assertNotNull(stageFile); - try (InputStream stagedDataInputStream = stageFile.openRead()) { - verifyPayloadForAppLocales(expectedPkgLocalesMap, stagedDataInputStream.readAllBytes(), - /* forStage= */ true, creationTimeMillis); - } catch (IOException | XmlPullParserException e) { - throw e; - } - } - - private static void checkStageFileDoesNotExist(int userId) { - assertNull(getStageFileIfExists(userId)); + private void verifyStageDataForUser(Map<String, String> expectedPkgLocalesMap, + long expectedCreationTimeMillis, int userId) { + LocaleManagerBackupHelper.StagedData stagedDataForUser = STAGE_DATA.get(userId); + assertNotNull(stagedDataForUser); + assertEquals(expectedCreationTimeMillis, stagedDataForUser.mCreationTimeMillis); + assertEquals(expectedPkgLocalesMap, stagedDataForUser.mPackageStates); } - private static void checkStageFileExists(int userId) { - assertNotNull(getStageFileIfExists(userId)); + private static void checkStageDataExists(int userId) { + assertNotNull(STAGE_DATA.get(userId)); } - private static AtomicFile getStageFileIfExists(int userId) { - File file = new File(STAGED_LOCALES_DIR, String.format("staged_locales_%d.xml", userId)); - if (file.isFile()) { - return new AtomicFile(file); - } - return null; - } - - private static void cleanStagedFiles() { - File[] files = STAGED_LOCALES_DIR.listFiles(); - if (files != null) { - for (File f : files) { - f.delete(); - } - } + private static void checkStageDataDoesNotExist(int userId) { + assertNull(STAGE_DATA.get(userId)); } } diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java index 658f8d52d74b..ee2bb0ad429a 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -58,6 +59,7 @@ import org.mockito.Mock; @RunWith(AndroidJUnit4.class) public class LocaleManagerServiceTest { private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp"; + private static final String DEFAULT_INSTALLER_PACKAGE_NAME = "com.android.myapp.installer"; private static final int DEFAULT_USER_ID = 0; private static final int DEFAULT_UID = Binder.getCallingUid() + 100; private static final int INVALID_UID = -1; @@ -66,7 +68,8 @@ public class LocaleManagerServiceTest { LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS); private static final InstallSourceInfo DEFAULT_INSTALL_SOURCE_INFO = new InstallSourceInfo( /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, /* installingPackageName = */ null); + /* originatingPackageName = */ null, + /* installingPackageName = */ DEFAULT_INSTALLER_PACKAGE_NAME); private LocaleManagerService mLocaleManagerService; private LocaleManagerBackupHelper mMockBackupHelper; @@ -89,7 +92,7 @@ public class LocaleManagerServiceTest { mMockActivityManager = mock(ActivityManagerInternal.class); mMockPackageManagerInternal = mock(PackageManagerInternal.class); - // For unit tests, set the default (null) installer info + // For unit tests, set the default installer info PackageManager mockPackageManager = mock(PackageManager.class); doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mockPackageManager) .getInstallSourceInfo(anyString()); @@ -275,6 +278,23 @@ public class LocaleManagerServiceTest { assertEquals(DEFAULT_LOCALES, locales); } + @Test + public void testGetApplicationLocales_callerIsInstaller_returnsLocales() + throws Exception { + doReturn(DEFAULT_UID).when(mMockPackageManagerInternal) + .getPackageUid(eq(DEFAULT_PACKAGE_NAME), anyLong(), anyInt()); + doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal) + .getPackageUid(eq(DEFAULT_INSTALLER_PACKAGE_NAME), anyLong(), anyInt()); + doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES)) + .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt()); + + LocaleList locales = + mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + verify(mMockContext, never()).enforceCallingOrSelfPermission(any(), any()); + assertEquals(DEFAULT_LOCALES, locales); + } + private static void assertNoLocalesStored(LocaleList locales) { assertNull(locales); } diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java index 93972c32a50f..b0fc6363b701 100644 --- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java +++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java @@ -18,8 +18,8 @@ package com.android.server.locales; import android.content.Context; import android.content.pm.PackageManagerInternal; +import android.util.SparseArray; -import java.io.File; import java.time.Clock; /** @@ -30,7 +30,8 @@ import java.time.Clock; public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper { ShadowLocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, - PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) { - super(context, localeManagerService, pmInternal, stagedLocalesDir, clock); + PackageManagerInternal pmInternal, Clock clock, + SparseArray<LocaleManagerBackupHelper.StagedData> stagedData) { + super(context, localeManagerService, pmInternal, clock, stagedData); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java new file mode 100644 index 000000000000..51ddcef425ce --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java @@ -0,0 +1,186 @@ +/* + * 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.locksettings; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.admin.PasswordMetrics; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.widget.IWeakEscrowTokenActivatedListener; +import com.android.internal.widget.IWeakEscrowTokenRemovedListener; +import com.android.internal.widget.LockscreenCredential; +import com.android.internal.widget.VerifyCredentialResponse; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** atest FrameworksServicesTests:WeakEscrowTokenTests */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WeakEscrowTokenTests extends BaseLockSettingsServiceTests{ + + @Test + public void testWeakTokenActivatedImmediatelyIfNoUserPassword() + throws RemoteException { + mockAutoHardware(); + final byte[] token = "some-high-entropy-secure-token".getBytes(); + IWeakEscrowTokenActivatedListener mockListener = + mock(IWeakEscrowTokenActivatedListener.Stub.class); + long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener); + assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID)); + verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID); + } + + @Test + public void testWeakTokenActivatedLaterWithUserPassword() + throws RemoteException { + mockAutoHardware(); + byte[] token = "some-high-entropy-secure-token".getBytes(); + IWeakEscrowTokenActivatedListener mockListener = + mock(IWeakEscrowTokenActivatedListener.Stub.class); + LockscreenCredential password = newPassword("password"); + mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID); + + long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener); + // Token not activated immediately since user password exists + assertFalse(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID)); + // Activate token (password gets migrated to SP at the same time) + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); + // Verify token is activated and valid + assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID)); + verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID); + } + + @Test + public void testWeakTokensRemovedIfCredentialChanged() throws Exception { + mockAutoHardware(); + byte[] token = "some-high-entropy-secure-token".getBytes(); + IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener(); + IWeakEscrowTokenActivatedListener mockActivateListener = + mock(IWeakEscrowTokenActivatedListener.Stub.class); + LockscreenCredential password = newPassword("password"); + LockscreenCredential pattern = newPattern("123654"); + mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID); + mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener); + + long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener); + + // Activate token + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); + + // Verify token removed + assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertTrue(mLocalService.setLockCredentialWithToken( + pattern, handle, token, PRIMARY_USER_ID)); + assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID); + } + + @Test + public void testWeakTokenRemovedListenerRegistered() throws Exception { + mockAutoHardware(); + IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener(); + IWeakEscrowTokenActivatedListener mockActivateListener = + mock(IWeakEscrowTokenActivatedListener.Stub.class); + byte[] token = "some-high-entropy-secure-token".getBytes(); + long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener); + + mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener); + mService.removeWeakEscrowToken(handle, PRIMARY_USER_ID); + + verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID); + } + + @Test + public void testWeakTokenRemovedListenerUnregistered() throws Exception { + mockAutoHardware(); + IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener(); + IWeakEscrowTokenActivatedListener mockActivateListener = + mock(IWeakEscrowTokenActivatedListener.Stub.class); + byte[] token0 = "some-high-entropy-secure-token-0".getBytes(); + byte[] token1 = "some-high-entropy-secure-token-1".getBytes(); + long handle0 = mService.addWeakEscrowToken(token0, PRIMARY_USER_ID, mockActivateListener); + long handle1 = mService.addWeakEscrowToken(token1, PRIMARY_USER_ID, mockActivateListener); + + mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener); + mService.removeWeakEscrowToken(handle0, PRIMARY_USER_ID); + verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle0, PRIMARY_USER_ID); + + mService.unregisterWeakEscrowTokenRemovedListener(mockRemoveListener); + mService.removeWeakEscrowToken(handle1, PRIMARY_USER_ID); + verify(mockRemoveListener, never()).onWeakEscrowTokenRemoved(handle1, PRIMARY_USER_ID); + } + + @Test + public void testUnlockUserWithToken_weakEscrowToken() throws Exception { + mockAutoHardware(); + IWeakEscrowTokenActivatedListener mockActivateListener = + mock(IWeakEscrowTokenActivatedListener.Stub.class); + LockscreenCredential password = newPassword("password"); + byte[] token = "some-high-entropy-secure-token".getBytes(); + mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID); + // Disregard any reportPasswordChanged() invocations as part of credential setup. + flushHandlerTasks(); + reset(mDevicePolicyManager); + + long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); + assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID)); + + mService.onCleanupUser(PRIMARY_USER_ID); + assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID)); + + assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID)); + assertEquals(PasswordMetrics.computeForCredential(password), + mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID)); + } + + private void mockAutoHardware() { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true); + } + + private IWeakEscrowTokenRemovedListener mockAliveRemoveListener() { + IWeakEscrowTokenRemovedListener mockListener = + mock(IWeakEscrowTokenRemovedListener.Stub.class); + IBinder mockIBinder = mock(IBinder.class); + when(mockIBinder.isBinderAlive()).thenReturn(true); + when(mockListener.asBinder()).thenReturn(mockIBinder); + return mockListener; + } +} diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index b811e28a3f71..c544f5c2e245 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -49,12 +49,8 @@ import static android.net.NetworkPolicyManager.blockedReasonsToString; import static android.net.NetworkPolicyManager.uidPoliciesToString; import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; -import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.METERED_NO; import static android.net.NetworkStats.METERED_YES; -import static android.net.NetworkStats.SET_ALL; -import static android.net.NetworkStats.TAG_ALL; -import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkTemplate.buildTemplateCarrierMetered; import static android.net.NetworkTemplate.buildTemplateWifi; import static android.net.TrafficStats.MB_IN_BYTES; @@ -75,6 +71,7 @@ import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOO import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID; import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING; import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons; +import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -108,6 +105,8 @@ import android.app.IActivityManager; import android.app.IUidObserver; import android.app.Notification; import android.app.NotificationManager; +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.Context; import android.content.Intent; @@ -125,8 +124,6 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicy; import android.net.NetworkStateSnapshot; -import android.net.NetworkStats; -import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TelephonyNetworkSpecifier; import android.net.wifi.WifiInfo; @@ -138,7 +135,6 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.RemoteException; import android.os.SimpleClock; -import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -263,12 +259,13 @@ public class NetworkPolicyManagerServiceTest { private @Mock CarrierConfigManager mCarrierConfigManager; private @Mock TelephonyManager mTelephonyManager; private @Mock UserManager mUserManager; + private @Mock NetworkStatsManager mStatsManager; + private TestDependencies mDeps; private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor = ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); private ActivityManagerInternal mActivityManagerInternal; - private NetworkStatsManagerInternal mStatsService; private IUidObserver mUidObserver; private INetworkManagementEventObserver mNetworkObserver; @@ -335,8 +332,47 @@ public class NetworkPolicyManagerServiceTest { .setBatterySaverEnabled(false).build(); final PowerManagerInternal pmInternal = addLocalServiceMock(PowerManagerInternal.class); when(pmInternal.getLowPowerState(anyInt())).thenReturn(state); + } + + private class TestDependencies extends NetworkPolicyManagerService.Dependencies { + private final SparseArray<NetworkStats.Bucket> mMockedStats = new SparseArray<>(); + + TestDependencies(Context context) { + super(context); + } + + @Override + long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { + int total = 0; + for (int i = 0; i < mMockedStats.size(); i++) { + NetworkStats.Bucket bucket = mMockedStats.valueAt(i); + total += bucket.getRxBytes() + bucket.getTxBytes(); + } + return total; + } + + @Override + List<NetworkStats.Bucket> getNetworkUidBytes(NetworkTemplate template, long start, + long end) { + final List<NetworkStats.Bucket> ret = new ArrayList<>(); + for (int i = 0; i < mMockedStats.size(); i++) { + ret.add(mMockedStats.valueAt(i)); + } + return ret; + } + + private void setMockedTotalBytes(int uid, long rxBytes, long txBytes) { + final NetworkStats.Bucket bucket = mock(NetworkStats.Bucket.class); + when(bucket.getUid()).thenReturn(uid); + when(bucket.getRxBytes()).thenReturn(rxBytes); + when(bucket.getTxBytes()).thenReturn(txBytes); + mMockedStats.set(uid, bucket); + } - mStatsService = addLocalServiceMock(NetworkStatsManagerInternal.class); + private void increaseMockedTotalBytes(int uid, long rxBytes, long txBytes) { + final NetworkStats.Bucket bucket = mMockedStats.get(uid); + setMockedTotalBytes(uid, bucket.getRxBytes() + rxBytes, bucket.getTxBytes() + txBytes); + } } @Before @@ -376,6 +412,8 @@ public class NetworkPolicyManagerServiceTest { return mConnManager; case Context.USER_SERVICE: return mUserManager; + case Context.NETWORK_STATS_SERVICE: + return mStatsManager; default: return super.getSystemService(name); } @@ -400,8 +438,9 @@ public class NetworkPolicyManagerServiceTest { }).when(mActivityManager).registerUidObserver(any(), anyInt(), anyInt(), any(String.class)); mFutureIntent = newRestrictBackgroundChangedFuture(); + mDeps = new TestDependencies(mServiceContext); mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, - mNetworkManager, mIpm, mClock, mPolicyDir, true); + mNetworkManager, mIpm, mClock, mPolicyDir, true, mDeps); mService.bindConnectivityManager(); mPolicyListener = new NetworkPolicyListenerAnswer(mService); @@ -456,6 +495,9 @@ public class NetworkPolicyManagerServiceTest { verify(mNetworkManager).registerObserver(networkObserver.capture()); mNetworkObserver = networkObserver.getValue(); + // Simulate NetworkStatsService broadcast stats updated to signal its readiness. + mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_UPDATED)); + NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, ""); mDefaultWarningBytes = defaultPolicy.warningBytes; mDefaultLimitBytes = defaultPolicy.limitBytes; @@ -479,7 +521,6 @@ public class NetworkPolicyManagerServiceTest { LocalServices.removeServiceForTest(DeviceIdleInternal.class); LocalServices.removeServiceForTest(AppStandbyInternal.class); LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); - LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class); } @After @@ -1108,10 +1149,7 @@ public class NetworkPolicyManagerServiceTest { when(mConnManager.getAllNetworkStateSnapshots()).thenReturn(snapshots); // pretend that 512 bytes total have happened - stats = new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, 256L, 2L, 256L, 2L); - when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START, CYCLE_END)) - .thenReturn(stats.getTotalBytes()); + mDeps.setMockedTotalBytes(UID_A, 256L, 256L); mPolicyListener.expect().onMeteredIfacesChanged(any()); setNetworkPolicies(new NetworkPolicy( @@ -1124,26 +1162,6 @@ public class NetworkPolicyManagerServiceTest { @Test public void testNotificationWarningLimitSnooze() throws Exception { - // Create a place to store fake usage - final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1)); - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenAnswer(new Answer<Long>() { - @Override - public Long answer(InvocationOnMock invocation) throws Throwable { - final NetworkStatsHistory.Entry entry = history.getValues( - invocation.getArgument(1), invocation.getArgument(2), null); - return entry.rxBytes + entry.txBytes; - } - }); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenAnswer(new Answer<NetworkStats>() { - @Override - public NetworkStats answer(InvocationOnMock invocation) throws Throwable { - return stats; - } - }); - // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); @@ -1161,9 +1179,7 @@ public class NetworkPolicyManagerServiceTest { // Normal usage means no notification { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0); reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); @@ -1178,9 +1194,7 @@ public class NetworkPolicyManagerServiceTest { // Push over warning { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1799), 0); reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); @@ -1196,9 +1210,7 @@ public class NetworkPolicyManagerServiceTest { // Push over warning, but with a config that isn't from an identified carrier { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1799), 0); reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); @@ -1215,9 +1227,7 @@ public class NetworkPolicyManagerServiceTest { // Push over limit { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1810), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1810), 0); reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); @@ -1248,26 +1258,6 @@ public class NetworkPolicyManagerServiceTest { @Test public void testNotificationRapid() throws Exception { - // Create a place to store fake usage - final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1)); - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenAnswer(new Answer<Long>() { - @Override - public Long answer(InvocationOnMock invocation) throws Throwable { - final NetworkStatsHistory.Entry entry = history.getValues( - invocation.getArgument(1), invocation.getArgument(2), null); - return entry.rxBytes + entry.txBytes; - } - }); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenAnswer(new Answer<NetworkStats>() { - @Override - public NetworkStats answer(InvocationOnMock invocation) throws Throwable { - return stats; - } - }); - // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); @@ -1285,9 +1275,7 @@ public class NetworkPolicyManagerServiceTest { // Using 20% data in 20% time is normal { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0); reset(mNotifManager); mService.updateNetworks(); @@ -1297,16 +1285,9 @@ public class NetworkPolicyManagerServiceTest { // Using 80% data in 20% time is alarming; but spread equally among // three UIDs means we get generic alert { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0)); - stats.clear(); - stats.insertEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); - stats.insertEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); - stats.insertEntry(IFACE_ALL, UID_C, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(480), 0); + mDeps.setMockedTotalBytes(UID_B, DataUnit.MEGABYTES.toBytes(480), 0); + mDeps.setMockedTotalBytes(UID_C, DataUnit.MEGABYTES.toBytes(480), 0); reset(mNotifManager); mService.updateNetworks(); @@ -1325,14 +1306,9 @@ public class NetworkPolicyManagerServiceTest { // Using 80% data in 20% time is alarming; but mostly done by one UID // means we get specific alert { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0)); - stats.clear(); - stats.insertEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0); - stats.insertEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(960), 0); + mDeps.setMockedTotalBytes(UID_B, DataUnit.MEGABYTES.toBytes(480), 0); + mDeps.setMockedTotalBytes(UID_C, 0, 0); reset(mNotifManager); mService.updateNetworks(); @@ -1362,13 +1338,10 @@ public class NetworkPolicyManagerServiceTest { // bring up wifi network with metered policy snapshots = List.of(buildWifi()); - stats = new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, 0L, 0L, 0L, 0L); + mDeps.setMockedTotalBytes(UID_A, 0L, 0L); { when(mConnManager.getAllNetworkStateSnapshots()).thenReturn(snapshots); - when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, - currentTimeMillis())).thenReturn(stats.getTotalBytes()); mPolicyListener.expect().onMeteredIfacesChanged(any()); setNetworkPolicies(new NetworkPolicy( @@ -1647,18 +1620,6 @@ public class NetworkPolicyManagerServiceTest { final NetworkPolicyManagerInternal internal = LocalServices .getService(NetworkPolicyManagerInternal.class); - // Create a place to store fake usage - final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1)); - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenAnswer(invocation -> { - final NetworkStatsHistory.Entry entry = history.getValues( - invocation.getArgument(1), invocation.getArgument(2), null); - return entry.rxBytes + entry.txBytes; - }); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); - // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); @@ -1669,9 +1630,7 @@ public class NetworkPolicyManagerServiceTest { setCurrentTimeMillis(end); // Get some data usage in place - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0); // No data plan { @@ -1786,22 +1745,11 @@ public class NetworkPolicyManagerServiceTest { true); } - private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) { - stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - rxBytes, 1, txBytes, 1, 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenReturn(stats.getTotalBytes()); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); - } - private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException { - final NetworkPolicyManagerInternal npmi = LocalServices - .getService(NetworkPolicyManagerInternal.class); - npmi.onStatsProviderWarningOrLimitReached("TEST"); + mService.notifyStatsProviderWarningOrLimitReached(); // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED. postMsgAndWaitForCompletion(); - verify(mStatsService).forceUpdate(); + verify(mStatsManager).forceUpdate(); // Wait for processing of MSG_*_INTERFACE_QUOTAS. postMsgAndWaitForCompletion(); } @@ -1814,13 +1762,12 @@ public class NetworkPolicyManagerServiceTest { public void testStatsProviderWarningAndLimitReached() throws Exception { final int CYCLE_DAY = 15; - final NetworkStats stats = new NetworkStats(0L, 1); - increaseMockedTotalBytes(stats, 2999, 2000); + mDeps.setMockedTotalBytes(UID_A, 2999, 2000); // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); - verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, + verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, Long.MAX_VALUE); // Set warning to 7KB and limit to 10KB. @@ -1830,32 +1777,32 @@ public class NetworkPolicyManagerServiceTest { postMsgAndWaitForCompletion(); // Verifies that remaining quotas are set to providers. - verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L); - reset(mStatsService); + verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L); + reset(mStatsManager); // Increase the usage and simulates that limit reached fires earlier by provider, // but actually the quota is not yet reached. Verifies that the limit reached leads to // a force update and new quotas should be set. - increaseMockedTotalBytes(stats, 1000, 999); + mDeps.increaseMockedTotalBytes(UID_A, 1000, 999); triggerOnStatsProviderWarningOrLimitReached(); - verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L); - reset(mStatsService); + verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L); + reset(mStatsManager); // Increase the usage and simulate warning reached, the new warning should be unlimited // since service will disable warning quota to stop lower layer from keep triggering // warning reached event. - increaseMockedTotalBytes(stats, 1000L, 1000); + mDeps.increaseMockedTotalBytes(UID_A, 1000L, 1000); triggerOnStatsProviderWarningOrLimitReached(); - verify(mStatsService).setStatsProviderWarningAndLimitAsync( + verify(mStatsManager).setStatsProviderWarningAndLimitAsync( TEST_IFACE, Long.MAX_VALUE, 1002L); - reset(mStatsService); + reset(mStatsManager); // Increase the usage that over the warning and limit, the new limit should set to 1 to // block the network traffic. - increaseMockedTotalBytes(stats, 1000L, 1000); + mDeps.increaseMockedTotalBytes(UID_A, 1000L, 1000); triggerOnStatsProviderWarningOrLimitReached(); - verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L); - reset(mStatsService); + verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L); + reset(mStatsManager); } private void enableRestrictedMode(boolean enable) throws Exception { @@ -2145,7 +2092,7 @@ public class NetworkPolicyManagerServiceTest { } private void verifyAdvisePersistThreshold() throws Exception { - verify(mStatsService).advisePersistThreshold(anyLong()); + verify(mStatsManager).setDefaultGlobalAlert(anyLong()); } private static class TestAbstractFuture<T> extends AbstractFuture<T> { diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 0f6dfda4bf63..13a8f69358b6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -33,12 +33,12 @@ import android.content.pm.ApplicationInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedActivityImpl; -import android.content.pm.parsing.component.ParsedInstrumentationImpl; -import android.content.pm.parsing.component.ParsedIntentInfoImpl; -import android.content.pm.parsing.component.ParsedProviderImpl; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; +import com.android.server.pm.pkg.component.ParsedProviderImpl; import android.os.Build; import android.os.Process; import android.os.UserHandle; diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java index 54ab133d760e..e137c374b4cc 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java @@ -29,8 +29,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.os.Build; import android.platform.test.annotations.Presubmit; diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java index 6b6d84af5f60..d7e3825bf9d0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java @@ -17,7 +17,7 @@ package com.android.server.pm; -import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; +import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey; import android.content.pm.Signature; import android.platform.test.annotations.Presubmit; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index 62a2b1be139d..59f2ca4f6106 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -187,7 +187,7 @@ public class PackageInstallerSessionTest { /* isFailed */ false, /* isApplied */false, /* stagedSessionErrorCode */ - PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED, /* stagedSessionErrorMessage */ "some error"); } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 9d672405603d..6c9a60ac47fb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -22,7 +22,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS; import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND; -import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; +import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey; import static android.content.res.Resources.ID_NULL; import static org.hamcrest.CoreMatchers.equalTo; @@ -43,6 +43,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.os.BaseBundle; import android.os.PersistableBundle; import android.os.Process; diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java index c2519ca0f238..b621a4408f40 100644 --- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm; +package com.android.server.pm; import static android.system.OsConstants.S_IFDIR; import static android.system.OsConstants.S_IFMT; @@ -32,10 +32,16 @@ import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.KeySet; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.PermissionInfo; +import android.content.pm.VerifierDeviceIdentity; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; @@ -63,8 +69,10 @@ import androidx.test.filters.LargeTest; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; -import com.android.frameworks.coretests.R; +import com.android.frameworks.servicestests.R; import com.android.internal.content.PackageHelper; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import dalvik.system.VMRuntime; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index c888524a6d00..d8ecf20b98c4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -15,7 +15,7 @@ */ package com.android.server.pm; -import static android.content.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS; +import static com.android.server.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS; import static com.google.common.truth.Truth.assertWithMessage; @@ -43,27 +43,6 @@ import android.content.pm.PackageManager.Property; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedActivityImpl; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedInstrumentationImpl; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedIntentInfoImpl; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionGroupImpl; -import android.content.pm.parsing.component.ParsedPermissionImpl; -import android.content.pm.parsing.component.ParsedPermissionUtils; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedProviderImpl; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedServiceImpl; -import android.content.pm.parsing.component.ParsedUsesPermission; -import android.content.pm.parsing.component.ParsedUsesPermissionImpl; -import android.content.pm.permission.CompatibilityPermissionInfo; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -85,7 +64,28 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; +import com.android.server.pm.pkg.component.ParsedPermissionImpl; +import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProviderImpl; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.component.ParsedServiceImpl; +import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; +import com.android.server.pm.pkg.parsing.ParsingPackage; import org.junit.Before; import org.junit.Rule; diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java index 28f24f2b55ee..7ff8eec70a10 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java @@ -43,8 +43,8 @@ import android.Manifest; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.SharedLibraryInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.component.ParsedUsesPermissionImpl; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import android.content.res.TypedArray; import android.os.Environment; import android.os.UserHandle; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 408d2c525f70..99edecfeed30 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -16,6 +16,7 @@ package com.android.server.pm; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundlesEqual; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; @@ -257,6 +258,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setLongLived(true) .setExtras(pb) .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.type", list("running", "jogging")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.duration", list("10m")) .build(); si.addFlags(ShortcutInfo.FLAG_PINNED); si.setBitmapPath("abc"); @@ -294,6 +299,13 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getDisabledMessageResName()); assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen", si.getStartingThemeResName()); + assertTrue(si.hasCapability("action.intent.START_EXERCISE")); + assertFalse(si.hasCapability("")); + assertFalse(si.hasCapability("random")); + assertEquals(list("running", "jogging"), si.getCapabilityParameterValues( + "action.intent.START_EXERCISE", "exercise.type")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "random")); } public void testShortcutInfoParcel_resId() { @@ -947,6 +959,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setRank(123) .setExtras(pb) .setLocusId(new LocusId("1.2.3.4.5")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.type", list("running", "jogging")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.duration", list("10m")) .build(); sorig.setTimestamp(mInjectedCurrentTimeMillis); @@ -1008,6 +1024,14 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertNull(si.getIconUri()); assertTrue(si.getLastChangedTimestamp() < now); + assertTrue(si.hasCapability("action.intent.START_EXERCISE")); + assertFalse(si.hasCapability("")); + assertFalse(si.hasCapability("random")); + assertEquals(list("running", "jogging"), si.getCapabilityParameterValues( + "action.intent.START_EXERCISE", "exercise.type")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "random")); + // Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts // to test it. si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index e4273dce7893..429445f80dbb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -315,8 +315,8 @@ public final class UserManagerTest { mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true, asHandle(currentUser)); try { - assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, - /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); + assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(), + /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); } finally { mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false, asHandle(currentUser)); @@ -335,8 +335,8 @@ public final class UserManagerTest { asHandle(currentUser)); try { synchronized (mUserRemoveLock) { - assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, - /* evenWhenDisallowed= */ true)) + assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(), + /* overrideDevicePolicy= */ true)) .isEqualTo(UserManager.REMOVE_RESULT_REMOVED); waitForUserRemovalLocked(user1.id); } @@ -352,8 +352,8 @@ public final class UserManagerTest { @MediumTest @Test public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception { - assertThat(mUserManager.removeUserOrSetEphemeral(UserHandle.USER_SYSTEM, - /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); + assertThat(mUserManager.removeUserWhenPossible(UserHandle.SYSTEM, + /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue(); } @@ -362,8 +362,8 @@ public final class UserManagerTest { @Test public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception { assertThat(hasUser(Integer.MAX_VALUE)).isFalse(); - assertThat(mUserManager.removeUserOrSetEphemeral(Integer.MAX_VALUE, - /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); + assertThat(mUserManager.removeUserWhenPossible(UserHandle.of(Integer.MAX_VALUE), + /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); } @MediumTest @@ -374,8 +374,8 @@ public final class UserManagerTest { // Switch to the user just created. switchUser(user1.id, null, /* ignoreHandle= */ true); - assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, /* evenWhenDisallowed= */ false)) - .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED); + assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(), + /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_DEFERRED); assertThat(hasUser(user1.id)).isTrue(); assertThat(getUser(user1.id).isEphemeral()).isTrue(); @@ -395,8 +395,9 @@ public final class UserManagerTest { public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception { final UserInfo user1 = createUser("User 1", /* flags= */ 0); synchronized (mUserRemoveLock) { - assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, - /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_REMOVED); + assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(), + /* overrideDevicePolicy= */ false)) + .isEqualTo(UserManager.REMOVE_RESULT_REMOVED); waitForUserRemovalLocked(user1.id); } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index 1dcb0b7eb159..7c8bbec458ee 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.content.pm.SharedLibraryInfo; -import android.content.pm.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt deleted file mode 100644 index 4059a496e8ea..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt +++ /dev/null @@ -1,147 +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.pm.parsing - -import android.Manifest -import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.content.pm.PackageParser -import android.platform.test.annotations.Postsubmit -import com.android.internal.util.ArrayUtils -import com.android.server.pm.parsing.AndroidPackageInfoFlagBehaviorTest.Companion.Param.Companion.appInfo -import com.android.server.pm.parsing.AndroidPackageInfoFlagBehaviorTest.Companion.Param.Companion.pkgInfo -import com.android.server.pm.parsing.pkg.AndroidPackage -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -/** - * Verifies that missing/adding [PackageManager] flags adds/remove the appropriate fields from the - * [PackageInfo] or [ApplicationInfo] results. - * - * This test has to be updated manually whenever the info generation behavior changes, since - * there's no single place where flag -> field is defined besides this test. - */ -@Postsubmit -@RunWith(Parameterized::class) -class AndroidPackageInfoFlagBehaviorTest : AndroidPackageParsingTestBase() { - - companion object { - - data class Param<T> constructor( - val flag: Int, - val logTag: String, - val oldPkgFunction: (pkg: PackageParser.Package, flags: Int) -> T?, - val newPkgFunction: (pkg: AndroidPackage, flags: Int) -> T?, - val fieldFunction: (T) -> List<Any?> - ) { - companion object { - fun pkgInfo(flag: Int, fieldFunction: (PackageInfo) -> List<Any?>) = Param( - flag, PackageInfo::class.java.simpleName, - ::oldPackageInfo, ::newPackageInfo, fieldFunction - ) - - fun appInfo(flag: Int, fieldFunction: (ApplicationInfo) -> List<Any?>) = Param( - flag, ApplicationInfo::class.java.simpleName, - { pkg, flags -> oldAppInfo(pkg, flags) }, - { pkg, flags -> newAppInfo(pkg, flags) }, - fieldFunction - ) - } - - override fun toString(): String { - val hex = Integer.toHexString(flag) - val fromRight = Integer.toBinaryString(flag).reversed().indexOf('1') - return "$logTag $hex | 1 shl $fromRight" - } - } - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun parameters() = arrayOf( - pkgInfo(PackageManager.GET_ACTIVITIES) { listOf(it.activities) }, - pkgInfo(PackageManager.GET_GIDS) { listOf(it.gids) }, - pkgInfo(PackageManager.GET_INSTRUMENTATION) { listOf(it.instrumentation) }, - pkgInfo(PackageManager.GET_META_DATA) { listOf(it.applicationInfo.metaData) }, - pkgInfo(PackageManager.GET_PROVIDERS) { listOf(it.providers) }, - pkgInfo(PackageManager.GET_RECEIVERS) { listOf(it.receivers) }, - pkgInfo(PackageManager.GET_SERVICES) { listOf(it.services) }, - pkgInfo(PackageManager.GET_SIGNATURES) { listOf(it.signatures) }, - pkgInfo(PackageManager.GET_SIGNING_CERTIFICATES) { listOf(it.signingInfo) }, - pkgInfo(PackageManager.GET_SHARED_LIBRARY_FILES) { - it.applicationInfo.run { listOf(sharedLibraryFiles, sharedLibraryFiles) } - }, - pkgInfo(PackageManager.GET_CONFIGURATIONS) { - listOf(it.configPreferences, it.reqFeatures, it.featureGroups) - }, - pkgInfo(PackageManager.GET_PERMISSIONS) { - listOf( - it.permissions, - // Strip compatibility permission added in T - it.requestedPermissions?.filter { x -> - x != Manifest.permission.POST_NOTIFICATIONS - }?.ifEmpty { null }?.toTypedArray(), - // Strip the flag from compatibility permission added in T - it.requestedPermissionsFlags?.filterIndexed { index, _ -> - index != ArrayUtils.indexOf(it.requestedPermissions, - Manifest.permission.POST_NOTIFICATIONS) - }?.ifEmpty { null }?.toTypedArray()) - }, - appInfo(PackageManager.GET_META_DATA) { listOf(it.metaData) }, - appInfo(PackageManager.GET_SHARED_LIBRARY_FILES) { - listOf(it.sharedLibraryFiles, it.sharedLibraryFiles) - } - ) - } - - @Parameterized.Parameter(0) - lateinit var param: Param<Any> - - @Test - fun fieldPresence() { - oldPackages.asSequence().zip(newPackages.asSequence()) - .forEach { (old, new) -> - val oldWithFlag = param.oldPkgFunction(old, param.flag) - val newWithFlag = param.newPkgFunction(new, param.flag) - val oldFieldList = oldWithFlag?.let(param.fieldFunction).orEmpty() - val newFieldList = newWithFlag?.let(param.fieldFunction).orEmpty() - - oldFieldList.zip(newFieldList).forEach { - assertWithMessage(new.packageName).that(it.second).apply { - // Assert same null-ness as old logic - if (it.first == null) { - isNull() - } else { - isNotNull() - } - } - } - } - } - - @Test - fun fieldAbsence() { - newPackages.forEach { - val newWithoutFlag = param.newPkgFunction(it, 0) - val newFieldListWithoutFlag = newWithoutFlag?.let(param.fieldFunction).orEmpty() - assertThat(newFieldListWithoutFlag.filterNotNull()).isEmpty() - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt deleted file mode 100644 index 574921cdbd05..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt +++ /dev/null @@ -1,158 +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.pm.parsing - -import android.content.pm.PackageManager -import android.platform.test.annotations.Postsubmit -import androidx.test.filters.LargeTest -import com.google.common.truth.Expect -import org.junit.Rule -import org.junit.Test - -/** - * Collects APKs from the device and verifies that the new parsing behavior outputs - * the same exposed Info object as the old parsing logic. - */ -@Postsubmit -class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { - - @get:Rule - val expect = Expect.create() - - @Test - fun applicationInfoEquality() { - val flags = PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES - val oldAppInfo = oldPackages.asSequence().map { oldAppInfo(it, flags) } - val newAppInfo = newPackages.asSequence().map { newAppInfo(it, flags) } - oldAppInfo.zip(newAppInfo).forEach { - val firstName = it.first?.packageName - val secondName = it.second?.packageName - val packageName = if (firstName == secondName) { - "$firstName" - } else { - "$firstName | $secondName" - } - expect.withMessage("${it.first?.sourceDir} $packageName") - .that(it.first?.dumpToString()) - .isEqualTo(it.second?.dumpToString()) - } - } - - @LargeTest - @Test - fun packageInfoEquality() { - val flags = PackageManager.GET_ACTIVITIES or - PackageManager.GET_CONFIGURATIONS or - PackageManager.GET_GIDS or - PackageManager.GET_INSTRUMENTATION or - PackageManager.GET_META_DATA or - PackageManager.GET_PERMISSIONS or - PackageManager.GET_PROVIDERS or - PackageManager.GET_RECEIVERS or - PackageManager.GET_SERVICES or - PackageManager.GET_SHARED_LIBRARY_FILES or - PackageManager.GET_SIGNATURES or - PackageManager.GET_SIGNING_CERTIFICATES or - PackageManager.MATCH_DIRECT_BOOT_UNAWARE or - PackageManager.MATCH_DIRECT_BOOT_AWARE - val oldPackageInfo = oldPackages.asSequence().map { oldPackageInfo(it, flags) } - val newPackageInfo = newPackages.asSequence().map { newPackageInfo(it, flags) } - - oldPackageInfo.zip(newPackageInfo).forEach { - val firstName = it.first?.packageName - val secondName = it.second?.packageName - val packageName = if (firstName == secondName) { - "$firstName" - } else { - "$firstName | $secondName" - } - - // Main components are asserted independently to separate the failures. Otherwise the - // comparison would include every component in one massive string. - - val prefix = "${it.first?.applicationInfo?.sourceDir} $packageName" - - expect.withMessage("$prefix PackageInfo") - .that(it.second?.dumpToString()) - .isEqualTo(it.first?.dumpToString()) - - expect.withMessage("$prefix ApplicationInfo") - .that(it.second?.applicationInfo?.dumpToString()) - .isEqualTo(it.first?.applicationInfo?.dumpToString()) - - val firstActivityNames = it.first?.activities?.map { it.name } ?: emptyList() - val secondActivityNames = it.second?.activities?.map { it.name } ?: emptyList() - expect.withMessage("$prefix activities") - .that(secondActivityNames) - .containsExactlyElementsIn(firstActivityNames) - .inOrder() - - if (!it.first?.activities.isNullOrEmpty() && !it.second?.activities.isNullOrEmpty()) { - it.first?.activities?.zip(it.second?.activities!!)?.forEach { - expect.withMessage("$prefix ${it.first.name}") - .that(it.second.dumpToString()) - .isEqualTo(it.first.dumpToString()) - } - } - - val firstReceiverNames = it.first?.receivers?.map { it.name } ?: emptyList() - val secondReceiverNames = it.second?.receivers?.map { it.name } ?: emptyList() - expect.withMessage("$prefix receivers") - .that(secondReceiverNames) - .containsExactlyElementsIn(firstReceiverNames) - .inOrder() - - if (!it.first?.receivers.isNullOrEmpty() && !it.second?.receivers.isNullOrEmpty()) { - it.first?.receivers?.zip(it.second?.receivers!!)?.forEach { - expect.withMessage("$prefix ${it.first.name}") - .that(it.second.dumpToString()) - .isEqualTo(it.first.dumpToString()) - } - } - - val firstProviderNames = it.first?.providers?.map { it.name } ?: emptyList() - val secondProviderNames = it.second?.providers?.map { it.name } ?: emptyList() - expect.withMessage("$prefix providers") - .that(secondProviderNames) - .containsExactlyElementsIn(firstProviderNames) - .inOrder() - - if (!it.first?.providers.isNullOrEmpty() && !it.second?.providers.isNullOrEmpty()) { - it.first?.providers?.zip(it.second?.providers!!)?.forEach { - expect.withMessage("$prefix ${it.first.name}") - .that(it.second.dumpToString()) - .isEqualTo(it.first.dumpToString()) - } - } - - val firstServiceNames = it.first?.services?.map { it.name } ?: emptyList() - val secondServiceNames = it.second?.services?.map { it.name } ?: emptyList() - expect.withMessage("$prefix services") - .that(secondServiceNames) - .containsExactlyElementsIn(firstServiceNames) - .inOrder() - - if (!it.first?.services.isNullOrEmpty() && !it.second?.services.isNullOrEmpty()) { - it.first?.services?.zip(it.second?.services!!)?.forEach { - expect.withMessage("$prefix ${it.first.name}") - .that(it.second.dumpToString()) - .isEqualTo(it.first.dumpToString()) - } - } - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt deleted file mode 100644 index 122661ea93da..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ /dev/null @@ -1,557 +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.pm.parsing - -import android.Manifest -import android.content.Context -import android.content.pm.ActivityInfo -import android.content.pm.ApplicationInfo -import android.content.pm.ConfigurationInfo -import android.content.pm.FeatureInfo -import android.content.pm.InstrumentationInfo -import android.content.pm.PackageInfo -import android.content.pm.PackageParser -import android.content.pm.PermissionInfo -import android.content.pm.ProviderInfo -import android.content.pm.ServiceInfo -import android.content.pm.parsing.ParsingPackageUtils -import android.os.Bundle -import android.os.Debug -import android.os.Environment -import android.os.Process -import android.util.SparseArray -import androidx.test.platform.app.InstrumentationRegistry -import com.android.internal.util.ArrayUtils -import com.android.server.pm.PackageManagerService -import com.android.server.pm.parsing.pkg.AndroidPackage -import com.android.server.pm.pkg.PackageStateInternal -import com.android.server.pm.pkg.PackageStateUnserialized -import com.android.server.pm.pkg.PackageUserStateImpl -import com.android.server.testutils.mockThrowOnUnmocked -import com.android.server.testutils.whenever -import org.junit.BeforeClass -import org.mockito.Mockito.anyInt -import java.io.File - -open class AndroidPackageParsingTestBase { - - companion object { - - private const val VERIFY_ALL_APKS = true - - // For auditing memory usage differences to /sdcard/AndroidPackageParsingTestBase.hprof - private const val DUMP_HPROF_TO_EXTERNAL = false - - val context: Context = InstrumentationRegistry.getInstrumentation().getContext() - protected val packageParser = PackageParser().apply { - setOnlyCoreApps(false) - setDisplayMetrics(context.resources.displayMetrics) - setCallback { false /* hasFeature */ } - } - - protected val packageParser2 = PackageParser2.forParsingFileWithDefaults() - - /** - * It would be difficult to mock all possibilities, so just use the APKs on device. - * Unfortunately, this means the device must be bootable to verify potentially - * boot-breaking behavior. - */ - private val apks = mutableListOf(File(Environment.getRootDirectory(), "framework")) - .apply { - @Suppress("ConstantConditionIf") - if (VERIFY_ALL_APKS) { - this += (PackageManagerService.SYSTEM_PARTITIONS) - .flatMap { - listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder) - } - } - } - .flatMap { - it.walkTopDown() - .filter { file -> file.name.endsWith(".apk") } - .toList() - } - .distinct() - - private val dummyUserState = - PackageUserStateImpl() - - val oldPackages = mutableListOf<PackageParser.Package>() - - val newPackages = mutableListOf<AndroidPackage>() - - @Suppress("ConstantConditionIf") - @JvmStatic - @BeforeClass - fun setUpPackages() { - var uid = Process.FIRST_APPLICATION_UID - apks.mapNotNull { - try { - packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) to - packageParser2.parsePackage(it, ParsingPackageUtils.PARSE_IS_SYSTEM_DIR, - false) - } catch (ignored: Exception) { - // It is intentional that a failure of either call here will result in failing - // both. Having null on one side would mean nothing to compare. Due to the - // nature of presubmit, this may not be caused by the change being tested, so - // it's unhelpful to consider it a failure. Actual parsing issues will be - // reported by SystemPartitionParseTest in postsubmit. - null - } - }.forEach { (old, new) -> - // Assign an arbitrary UID. This is normally done after parsing completes, inside - // PackageManagerService, but since that code isn't run here, need to mock it. This - // is equivalent to what the system would assign. - old.applicationInfo.uid = uid - new.uid = uid - uid++ - - oldPackages += old - newPackages += new.hideAsFinal() - } - - if (DUMP_HPROF_TO_EXTERNAL) { - System.gc() - Environment.getExternalStorageDirectory() - .resolve( - "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof") - .absolutePath - .run(Debug::dumpHprofData) - } - } - - fun oldAppInfo( - pkg: PackageParser.Package, - flags: Int = 0, - userId: Int = 0 - ): ApplicationInfo? { - return PackageParser.generateApplicationInfo(pkg, flags, dummyUserState, userId) - } - - fun newAppInfo( - pkg: AndroidPackage, - flags: Int = 0, - userId: Int = 0 - ): ApplicationInfo? { - return PackageInfoUtils.generateApplicationInfo(pkg, flags.toLong(), dummyUserState, - userId, mockPkgSetting(pkg)) - } - - fun newAppInfoWithoutState( - pkg: AndroidPackage, - flags: Int = 0, - userId: Int = 0 - ): ApplicationInfo? { - return PackageInfoUtils.generateApplicationInfo(pkg, flags.toLong(), dummyUserState, - userId, mockPkgSetting(pkg)) - } - - fun oldPackageInfo(pkg: PackageParser.Package, flags: Int = 0): PackageInfo? { - return PackageParser.generatePackageInfo(pkg, intArrayOf(), flags, 5, 6, emptySet(), - dummyUserState) - } - - fun newPackageInfo(pkg: AndroidPackage, flags: Int = 0): PackageInfo? { - return PackageInfoUtils.generate(pkg, intArrayOf(), flags.toLong(), 5, 6, emptySet(), - dummyUserState, 0, mockPkgSetting(pkg)) - } - - private fun mockPkgSetting(aPkg: AndroidPackage) = - mockThrowOnUnmocked<PackageStateInternal> { - whenever(pkg) { aPkg } - whenever(appId) { aPkg.uid } - whenever(transientState) { PackageStateUnserialized() } - whenever(getUserStateOrDefault(anyInt())) { dummyUserState } - whenever(categoryOverride) { ApplicationInfo.CATEGORY_UNDEFINED } - whenever(primaryCpuAbi) { null } - whenever(secondaryCpuAbi) { null } - } - } - - // The following methods dump an exact set of fields from the object to compare, because - // 1. comprehensive equals/toStrings do not exist on all of the Info objects, and - // 2. the test must only verify fields that [PackageParser.Package] can actually fill, as - // no new functionality will be added to it. - - // The following methods prepend "this." because @hide APIs can cause an IDE to auto-import - // the R.attr constant instead of referencing the field in an attempt to fix the error. - - // It's difficult to comment out a line in a triple quoted string, so this is used instead - // to ignore specific fields. A comment is required to explain why a field was ignored. - private fun Any?.ignored(comment: String): String = "IGNORED" - - protected fun ApplicationInfo.dumpToString() = """ - appComponentFactory=${this.appComponentFactory} - backupAgentName=${this.backupAgentName} - banner=${this.banner} - category=${this.category} - classLoaderName=${this.classLoaderName} - className=${this.className} - compatibleWidthLimitDp=${this.compatibleWidthLimitDp} - compileSdkVersion=${this.compileSdkVersion} - compileSdkVersionCodename=${this.compileSdkVersionCodename} - credentialProtectedDataDir=${this.credentialProtectedDataDir - .ignored("Deferred pre-R, but assigned immediately in R")} - crossProfile=${this.crossProfile.ignored("Added in R")} - dataDir=${this.dataDir.ignored("Deferred pre-R, but assigned immediately in R")} - descriptionRes=${this.descriptionRes} - deviceProtectedDataDir=${this.deviceProtectedDataDir - .ignored("Deferred pre-R, but assigned immediately in R")} - enabled=${this.enabled} - enabledSetting=${this.enabledSetting} - flags=${Integer.toBinaryString(this.flags)} - fullBackupContent=${this.fullBackupContent} - gwpAsanMode=${this.gwpAsanMode.ignored("Added in R")} - hiddenUntilInstalled=${this.hiddenUntilInstalled} - icon=${this.icon} - iconRes=${this.iconRes} - installLocation=${this.installLocation} - labelRes=${this.labelRes} - largestWidthLimitDp=${this.largestWidthLimitDp} - logo=${this.logo} - longVersionCode=${this.longVersionCode} - ${"".ignored("mHiddenApiPolicy is a private field")} - manageSpaceActivityName=${this.manageSpaceActivityName} - maxAspectRatio=${this.maxAspectRatio} - metaData=${this.metaData.dumpToString()} - minAspectRatio=${this.minAspectRatio} - minSdkVersion=${this.minSdkVersion} - name=${this.name} - nativeLibraryDir=${this.nativeLibraryDir} - nativeLibraryRootDir=${this.nativeLibraryRootDir} - nativeLibraryRootRequiresIsa=${this.nativeLibraryRootRequiresIsa} - networkSecurityConfigRes=${this.networkSecurityConfigRes} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - permission=${this.permission} - primaryCpuAbi=${this.primaryCpuAbi} - privateFlags=${Integer.toBinaryString(this.privateFlags)} - processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} - publicSourceDir=${this.publicSourceDir - .ignored("Deferred pre-R, but assigned immediately in R")} - requiresSmallestWidthDp=${this.requiresSmallestWidthDp} - resourceDirs=${this.resourceDirs?.contentToString()} - overlayPaths=${this.overlayPaths?.contentToString()} - roundIconRes=${this.roundIconRes} - scanPublicSourceDir=${this.scanPublicSourceDir - .ignored("Deferred pre-R, but assigned immediately in R")} - scanSourceDir=${this.scanSourceDir - .ignored("Deferred pre-R, but assigned immediately in R")} - seInfo=${this.seInfo} - seInfoUser=${this.seInfoUser} - secondaryCpuAbi=${this.secondaryCpuAbi} - secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} - sharedLibraryFiles=${this.sharedLibraryFiles?.contentToString()} - sharedLibraryInfos=${this.sharedLibraryInfos} - showUserIcon=${this.showUserIcon} - sourceDir=${this.sourceDir - .ignored("Deferred pre-R, but assigned immediately in R")} - splitClassLoaderNames=${this.splitClassLoaderNames?.contentToString()} - splitDependencies=${this.splitDependencies.dumpToString()} - splitNames=${this.splitNames?.contentToString()} - splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} - splitSourceDirs=${this.splitSourceDirs?.contentToString()} - storageUuid=${this.storageUuid} - targetSandboxVersion=${this.targetSandboxVersion} - targetSdkVersion=${this.targetSdkVersion} - taskAffinity=${this.taskAffinity} - theme=${this.theme} - uiOptions=${this.uiOptions} - uid=${this.uid} - versionCode=${this.versionCode} - volumeUuid=${this.volumeUuid} - zygotePreloadName=${this.zygotePreloadName} - """.trimIndent() - - protected fun FeatureInfo.dumpToString() = """ - flags=${Integer.toBinaryString(this.flags)} - name=${this.name} - reqGlEsVersion=${this.reqGlEsVersion} - version=${this.version} - """.trimIndent() - - protected fun InstrumentationInfo.dumpToString() = """ - banner=${this.banner} - credentialProtectedDataDir=${this.credentialProtectedDataDir} - dataDir=${this.dataDir} - deviceProtectedDataDir=${this.deviceProtectedDataDir} - functionalTest=${this.functionalTest} - handleProfiling=${this.handleProfiling} - icon=${this.icon} - labelRes=${this.labelRes} - logo=${this.logo} - metaData=${this.metaData} - name=${this.name} - nativeLibraryDir=${this.nativeLibraryDir} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - primaryCpuAbi=${this.primaryCpuAbi} - publicSourceDir=${this.publicSourceDir} - secondaryCpuAbi=${this.secondaryCpuAbi} - secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} - showUserIcon=${this.showUserIcon} - sourceDir=${this.sourceDir} - splitDependencies=${this.splitDependencies.dumpToString()} - splitNames=${this.splitNames?.contentToString()} - splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} - splitSourceDirs=${this.splitSourceDirs?.contentToString()} - targetPackage=${this.targetPackage} - targetProcesses=${this.targetProcesses} - """.trimIndent() - - protected fun ActivityInfo.dumpToString() = """ - banner=${this.banner} - colorMode=${this.colorMode} - configChanges=${this.configChanges} - descriptionRes=${this.descriptionRes} - directBootAware=${this.directBootAware} - documentLaunchMode=${this.documentLaunchMode - .ignored("Update for fixing b/128526493 and the testing is no longer valid")} - enabled=${this.enabled} - exported=${this.exported} - flags=${Integer.toBinaryString( - // Strip flag added in T - this.flags and (ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES.inv())) - } - icon=${this.icon} - labelRes=${this.labelRes} - launchMode=${this.launchMode} - launchToken=${this.launchToken} - lockTaskLaunchMode=${this.lockTaskLaunchMode} - logo=${this.logo} - maxRecents=${this.maxRecents} - metaData=${this.metaData.dumpToString()} - name=${this.name} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - parentActivityName=${this.parentActivityName} - permission=${this.permission} - persistableMode=${this.persistableMode.ignored("Could be dropped pre-R, fixed in R")} - privateFlags=${ - // Strip flag added in S - this.privateFlags and (ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND.inv()) - } - processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} - requestedVrComponent=${this.requestedVrComponent} - resizeMode=${this.resizeMode} - rotationAnimation=${this.rotationAnimation} - screenOrientation=${this.screenOrientation} - showUserIcon=${this.showUserIcon} - softInputMode=${this.softInputMode} - splitName=${this.splitName} - targetActivity=${this.targetActivity} - taskAffinity=${this.taskAffinity} - theme=${this.theme} - uiOptions=${this.uiOptions} - windowLayout=${this.windowLayout?.dumpToString()} - """.trimIndent() - - protected fun ActivityInfo.WindowLayout.dumpToString() = """ - gravity=${this.gravity} - height=${this.height} - heightFraction=${this.heightFraction} - minHeight=${this.minHeight} - minWidth=${this.minWidth} - width=${this.width} - widthFraction=${this.widthFraction} - """.trimIndent() - - protected fun PermissionInfo.dumpToString() = """ - backgroundPermission=${this.backgroundPermission} - banner=${this.banner} - descriptionRes=${this.descriptionRes} - flags=${Integer.toBinaryString(this.flags)} - group=${this.group} - icon=${this.icon} - labelRes=${this.labelRes} - logo=${this.logo} - metaData=${this.metaData.dumpToString()} - name=${this.name} - nonLocalizedDescription=${this.nonLocalizedDescription} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - protectionLevel=${this.protectionLevel} - requestRes=${this.requestRes} - showUserIcon=${this.showUserIcon} - """.trimIndent() - - protected fun ProviderInfo.dumpToString() = """ - applicationInfo=${this.applicationInfo.ignored("Already checked")} - authority=${this.authority} - banner=${this.banner} - descriptionRes=${this.descriptionRes} - directBootAware=${this.directBootAware} - enabled=${this.enabled} - exported=${this.exported} - flags=${Integer.toBinaryString(this.flags)} - forceUriPermissions=${this.forceUriPermissions} - grantUriPermissions=${this.grantUriPermissions} - icon=${this.icon} - initOrder=${this.initOrder} - isSyncable=${this.isSyncable} - labelRes=${this.labelRes} - logo=${this.logo} - metaData=${this.metaData.dumpToString()} - multiprocess=${this.multiprocess} - name=${this.name} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - pathPermissions=${this.pathPermissions?.joinToString { - "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}" - }} - processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} - readPermission=${this.readPermission} - showUserIcon=${this.showUserIcon} - splitName=${this.splitName} - uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()} - writePermission=${this.writePermission} - """.trimIndent() - - protected fun ServiceInfo.dumpToString() = """ - applicationInfo=${this.applicationInfo.ignored("Already checked")} - banner=${this.banner} - descriptionRes=${this.descriptionRes} - directBootAware=${this.directBootAware} - enabled=${this.enabled} - exported=${this.exported} - flags=${Integer.toBinaryString(this.flags)} - icon=${this.icon} - labelRes=${this.labelRes} - logo=${this.logo} - mForegroundServiceType"${this.mForegroundServiceType} - metaData=${this.metaData.dumpToString()} - name=${this.name} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - permission=${this.permission} - processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} - showUserIcon=${this.showUserIcon} - splitName=${this.splitName} - """.trimIndent() - - protected fun ConfigurationInfo.dumpToString() = """ - reqGlEsVersion=${this.reqGlEsVersion} - reqInputFeatures=${this.reqInputFeatures} - reqKeyboardType=${this.reqKeyboardType} - reqNavigation=${this.reqNavigation} - reqTouchScreen=${this.reqTouchScreen} - """.trimIndent() - - protected fun PackageInfo.dumpToString() = """ - activities=${this.activities?.joinToString { it.dumpToString() } - .ignored("Checked separately in test")} - applicationInfo=${this.applicationInfo.dumpToString() - .ignored("Checked separately in test")} - baseRevisionCode=${this.baseRevisionCode} - compileSdkVersion=${this.compileSdkVersion} - compileSdkVersionCodename=${this.compileSdkVersionCodename} - configPreferences=${this.configPreferences?.joinToString { it.dumpToString() }} - coreApp=${this.coreApp} - featureGroups=${this.featureGroups?.joinToString { - it.features?.joinToString { featureInfo -> featureInfo.dumpToString() }.orEmpty() - }} - firstInstallTime=${this.firstInstallTime} - gids=${gids?.contentToString()} - installLocation=${this.installLocation} - instrumentation=${instrumentation?.joinToString { it.dumpToString() }} - isApex=${this.isApex} - isStub=${this.isStub} - lastUpdateTime=${this.lastUpdateTime} - mOverlayIsStatic=${this.mOverlayIsStatic} - overlayCategory=${this.overlayCategory} - overlayPriority=${this.overlayPriority} - overlayTarget=${this.overlayTarget} - packageName=${this.packageName} - permissions=${this.permissions?.joinToString { it.dumpToString() }} - providers=${this.providers?.joinToString { it.dumpToString() } - .ignored("Checked separately in test")} - receivers=${this.receivers?.joinToString { it.dumpToString() } - .ignored("Checked separately in test")} - reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }} - requestedPermissions=${ - // Strip compatibility permission added in T - this.requestedPermissions?.filter { x -> - x != Manifest.permission.POST_NOTIFICATIONS - }?.ifEmpty { null }?.joinToString() - } - requestedPermissionsFlags=${ - // Strip the flag from compatibility permission added in T - this.requestedPermissionsFlags?.filterIndexed { index, _ -> - index != ArrayUtils.indexOf(requestedPermissions, - Manifest.permission.POST_NOTIFICATIONS) - }?.map { - // Newer flags are stripped - it and (PackageInfo.REQUESTED_PERMISSION_REQUIRED - or PackageInfo.REQUESTED_PERMISSION_GRANTED) - }?.ifEmpty { null }?.joinToString() - } - requiredAccountType=${this.requiredAccountType} - requiredForAllUsers=${this.requiredForAllUsers} - restrictedAccountType=${this.restrictedAccountType} - services=${this.services?.joinToString { it.dumpToString() } - .ignored("Checked separately in test")} - sharedUserId=${this.sharedUserId} - sharedUserLabel=${this.sharedUserLabel} - signatures=${this.signatures?.joinToString { it.toCharsString() }} - signingInfo=${this.signingInfo?.signingCertificateHistory - ?.joinToString { it.toCharsString() }.orEmpty()} - splitNames=${this.splitNames?.contentToString()} - splitRevisionCodes=${this.splitRevisionCodes?.contentToString()} - targetOverlayableName=${this.targetOverlayableName} - versionCode=${this.versionCode} - versionCodeMajor=${this.versionCodeMajor} - versionName=${this.versionName} - """.trimIndent() - - private fun Bundle?.dumpToString() = this?.keySet()?.associateWith { get(it) }?.toString() - - private fun <T> SparseArray<T>?.dumpToString(): String { - if (this == null) { - return "EMPTY" - } - - val list = mutableListOf<Pair<Int, T>>() - for (index in (0 until size())) { - list += keyAt(index) to valueAt(index) - } - return list.toString() - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageInfoUserFieldsTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageInfoUserFieldsTest.kt deleted file mode 100644 index 67b5d683de9a..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageInfoUserFieldsTest.kt +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2020 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.pm.parsing - -import android.content.pm.ApplicationInfo -import android.content.pm.PackageParser -import android.os.Environment -import android.os.UserHandle -import android.platform.test.annotations.Presubmit -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Test - -/** - * As a performance optimization, the new parsing code builds the user data directories manually - * using string concatenation. This tries to mirror the logic that [Environment] uses, but it is - * still fragile to changes and potentially different device configurations. - * - * This compares the resultant values against the old [PackageParser] outputs as well as - * [ApplicationInfo]'s own [ApplicationInfo.initForUser]. - */ -@Presubmit -class PackageInfoUserFieldsTest : AndroidPackageParsingTestBase() { - - @Test - fun userEnvironmentValues() { - // Specifically use a large user ID to test assumptions about single character IDs - val userId = 110 - - oldPackages.zip(newPackages) - .map { (old, new) -> - (old to oldAppInfo(pkg = old, userId = userId)!!) to - (new to newAppInfo(pkg = new, userId = userId)!!) - } - .forEach { (oldPair, newPair) -> - val (oldPkg, oldInfo) = oldPair - val (newPkg, newInfo) = newPair - - val oldValuesActual = extractActual(oldInfo) - val newValuesActual = extractActual(newInfo) - val oldValuesExpected: Values - val newValuesExpected: Values - - val packageName = oldPkg.packageName - if (packageName == "android") { - val systemDataDir = Environment.getDataSystemDirectory().absolutePath - oldValuesExpected = Values( - uid = UserHandle.getUid(userId, - UserHandle.getAppId(oldPkg.applicationInfo.uid)), - userDe = null, - userCe = null, - dataDir = systemDataDir - ) - newValuesExpected = Values( - uid = UserHandle.getUid(userId, UserHandle.getAppId(newPkg.uid)), - userDe = null, - userCe = null, - dataDir = systemDataDir - ) - } else { - oldValuesExpected = extractExpected(oldInfo, oldInfo.uid, userId) - newValuesExpected = extractExpected(newInfo, newPkg.uid, userId) - } - - // Calls the internal ApplicationInfo logic to compare against. This must be - // done after saving the original values, since this will overwrite them. - oldInfo.initForUser(userId) - newInfo.initForUser(userId) - - val oldInitValues = extractActual(oldInfo) - val newInitValues = extractActual(newInfo) - - // The optimization is also done for the no state API that isn't used by the - // system. This API is still exposed publicly, so for this test we should - // verify it. - val newNoStateValues = extractActual( - newAppInfoWithoutState(newPkg, 0, userId)!!) - - assertAllEquals(packageName, - oldValuesActual, oldValuesExpected, oldInitValues, - newValuesActual, newValuesExpected, newInitValues, newNoStateValues) - } - } - - private fun assertAllEquals(packageName: String, vararg values: Values) { - // Local function to avoid accidentally calling wrong type - fun assertAllEquals(message: String, vararg values: Any?) { - values.forEachIndexed { index, value -> - if (index == 0) return@forEachIndexed - assertWithMessage("$message $index").that(values[0]).isEqualTo(value) - } - } - - assertAllEquals("$packageName mismatched uid", values.map { it.uid }) - assertAllEquals("$packageName mismatched userDe", values.map { it.userDe }) - assertAllEquals("$packageName mismatched userCe", values.map { it.userCe }) - assertAllEquals("$packageName mismatched dataDir", values.map { it.dataDir }) - } - - private fun extractActual(appInfo: ApplicationInfo) = Values( - uid = appInfo.uid, - userDe = appInfo.deviceProtectedDataDir, - userCe = appInfo.credentialProtectedDataDir, - dataDir = appInfo.dataDir - ) - - private fun extractExpected(appInfo: ApplicationInfo, appIdUid: Int, userId: Int): Values { - val userDe = Environment.getDataUserDePackageDirectory(appInfo.volumeUuid, userId, - appInfo.packageName).absolutePath - val userCe = Environment.getDataUserCePackageDirectory(appInfo.volumeUuid, userId, - appInfo.packageName).absolutePath - val dataDir = if (appInfo.isDefaultToDeviceProtectedStorage) { - appInfo.deviceProtectedDataDir - } else { - appInfo.credentialProtectedDataDir - } - - return Values( - uid = UserHandle.getUid(userId, UserHandle.getAppId(appIdUid)), - userDe = userDe, - userCe = userCe, - dataDir = dataDir - ) - } - - data class Values( - val uid: Int, - val userDe: String?, - val userCe: String?, - val dataDir: String? - ) -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index c99034201d69..004d7bc2707c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -18,7 +18,6 @@ package com.android.server.pm.parsing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.apex.ApexInfo; @@ -27,16 +26,9 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.content.pm.PermissionInfo; import android.content.pm.SigningDetails; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Build; @@ -55,6 +47,14 @@ import com.android.internal.util.ArrayUtils; import com.android.server.pm.PackageManagerException; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.component.ParsedActivityUtils; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.google.common.truth.Expect; @@ -105,20 +105,19 @@ public class PackageParserLegacyCoreTest { private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename, boolean isPlatformReleased, int expectedMinSdk) { - final String[] outError = new String[1]; - final int result = PackageParser.computeMinSdkVersion( + final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); + final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeMinSdkVersion( minSdkVersion, minSdkCodename, PLATFORM_VERSION, isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE, - outError); - - assertEquals("Error msg: " + outError[0], expectedMinSdk, result); + input); if (expectedMinSdk == -1) { - assertNotNull(outError[0]); + assertTrue(result.isError()); } else { - assertNull(outError[0]); + assertTrue(result.isSuccess()); + assertEquals(expectedMinSdk, (int) result.getResult()); } } @@ -201,19 +200,18 @@ public class PackageParserLegacyCoreTest { private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename, boolean isPlatformReleased, int expectedTargetSdk) { - final String[] outError = new String[1]; - final int result = PackageParser.computeTargetSdkVersion( + final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); + final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeTargetSdkVersion( targetSdkVersion, targetSdkCodename, isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE, - outError); - - assertEquals(result, expectedTargetSdk); + input); if (expectedTargetSdk == -1) { - assertNotNull(outError[0]); + assertTrue(result.isError()); } else { - assertNull(outError[0]); + assertTrue(result.isSuccess()); + assertEquals(expectedTargetSdk, (int) result.getResult()); } } @@ -306,34 +304,34 @@ public class PackageParserLegacyCoreTest { // Not set in either configChanges or recreateOnConfigChanges. int configChanges = 0x0000; // 00000000. int recreateOnConfigChanges = 0x0000; // 00000000. - int finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + int finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0003, finalConfigChanges); // Should be 00000011. // Not set in configChanges, but set in recreateOnConfigChanges. configChanges = 0x0000; // 00000000. recreateOnConfigChanges = 0x0003; // 00000011. - finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0000, finalConfigChanges); // Should be 00000000. // Set in configChanges. configChanges = 0x0003; // 00000011. recreateOnConfigChanges = 0X0000; // 00000000. - finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0003, finalConfigChanges); // Should be 00000011. recreateOnConfigChanges = 0x0003; // 00000011. - finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0003, finalConfigChanges); // Should still be 00000011. // Other bit set in configChanges. configChanges = 0x0080; // 10000000, orientation. recreateOnConfigChanges = 0x0000; // 00000000. - finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0083, finalConfigChanges); // Should be 10000011. } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt index f53042183af8..bb094ba897e6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.parsing import android.annotation.RawRes import android.content.Context -import android.content.pm.parsing.ParsingPackage -import android.content.pm.parsing.ParsingPackageUtils +import com.android.server.pm.pkg.parsing.ParsingPackage +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import android.content.pm.parsing.result.ParseResult import android.platform.test.annotations.Presubmit import androidx.test.InstrumentationRegistry diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt index ffa19575718b..1f57b6c9f95f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.parsing import android.content.pm.PackageManager -import android.content.pm.parsing.ParsingPackageUtils +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import android.platform.test.annotations.Postsubmit import com.android.server.pm.PackageManagerException import com.android.server.pm.PackageManagerService diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java index 5bcd0f6bb029..b28446b337a6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java @@ -23,7 +23,7 @@ import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACH import static com.google.common.truth.Truth.assertThat; -import android.content.pm.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; import android.os.Build; import android.platform.test.annotations.Presubmit; diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index 761cea79df28..90b19a450f48 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -160,12 +160,12 @@ public final class DeviceStateProviderImplTest { } @Test - public void create_stateWithCancelStickyRequestFlag() { + public void create_stateWithCancelOverrideRequestFlag() { String configString = "<device-state-config>\n" + " <device-state>\n" + " <identifier>1</identifier>\n" + " <flags>\n" - + " <flag>FLAG_CANCEL_STICKY_REQUESTS</flag>\n" + + " <flag>FLAG_CANCEL_OVERRIDE_REQUESTS</flag>\n" + " </flags>\n" + " <conditions/>\n" + " </device-state>\n" @@ -183,7 +183,7 @@ public final class DeviceStateProviderImplTest { verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); final DeviceState[] expectedStates = new DeviceState[]{ - new DeviceState(1, "", DeviceState.FLAG_CANCEL_STICKY_REQUESTS), + new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS), new DeviceState(2, "", 0 /* flags */) }; assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index e47a07c9930a..c832a3ed49b6 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -73,7 +73,7 @@ import android.test.mock.MockContentResolver; import android.view.Display; import android.view.DisplayInfo; -import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; import com.android.internal.app.IBatteryStats; import com.android.internal.util.test.FakeSettingsProvider; @@ -138,7 +138,6 @@ public class PowerManagerServiceTest { private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; private PowerManagerService mService; - private PowerSaveState mPowerSaveState; private DisplayPowerRequest mDisplayPowerRequest; private ContextWrapper mContextSpy; private BatteryReceiver mBatteryReceiver; @@ -147,7 +146,7 @@ public class PowerManagerServiceTest { private OffsettableClock mClock; private TestLooper mTestLooper; - private class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> { + private static class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> { private final IntentFilter mFilter; IntentFilterMatcher(IntentFilter filter) { @@ -173,13 +172,13 @@ public class PowerManagerServiceTest { MockitoAnnotations.initMocks(this); FakeSettingsProvider.clearSettingsProvider(); - mPowerSaveState = new PowerSaveState.Builder() + PowerSaveState powerSaveState = new PowerSaveState.Builder() .setBatterySaverEnabled(BATTERY_SAVER_ENABLED) .setBrightnessFactor(BRIGHTNESS_FACTOR) .build(); when(mBatterySaverPolicyMock.getBatterySaverPolicy( eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS))) - .thenReturn(mPowerSaveState); + .thenReturn(powerSaveState); when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false); when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false); when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean())) @@ -195,7 +194,7 @@ public class PowerManagerServiceTest { addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock); addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock); - mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); + mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResourcesSpy = spy(mContextSpy.getResources()); when(mContextSpy.getResources()).thenReturn(mResourcesSpy); @@ -304,8 +303,8 @@ public class PowerManagerServiceTest { LocalServices.addService(clazz, mock); } - private void startSystem() throws Exception { - mService.systemReady(null); + private void startSystem() { + mService.systemReady(); // Grab the BatteryReceiver ArgumentCaptor<BatteryReceiver> batCaptor = ArgumentCaptor.forClass(BatteryReceiver.class); @@ -403,9 +402,9 @@ public class PowerManagerServiceTest { } @Test - public void testGetDesiredScreenPolicy_WithVR() throws Exception { + public void testGetDesiredScreenPolicy_WithVR() { createService(); - mService.systemReady(null); + mService.systemReady(); // Brighten up the screen mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null, null); @@ -436,13 +435,13 @@ public class PowerManagerServiceTest { } @Test - public void testWakefulnessAwake_InitialValue() throws Exception { + public void testWakefulnessAwake_InitialValue() { createService(); assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); } @Test - public void testWakefulnessSleep_NoDozeSleepFlag() throws Exception { + public void testWakefulnessSleep_NoDozeSleepFlag() { createService(); // Start with AWAKE state startSystem(); @@ -455,7 +454,7 @@ public class PowerManagerServiceTest { } @Test - public void testWakefulnessAwake_AcquireCausesWakeup() throws Exception { + public void testWakefulnessAwake_AcquireCausesWakeup() { createService(); startSystem(); forceSleep(); @@ -487,7 +486,7 @@ public class PowerManagerServiceTest { } @Test - public void testWakefulnessAwake_IPowerManagerWakeUp() throws Exception { + public void testWakefulnessAwake_IPowerManagerWakeUp() { createService(); startSystem(); forceSleep(); @@ -501,9 +500,7 @@ public class PowerManagerServiceTest { * or docked. */ @Test - public void testWakefulnessAwake_ShouldWakeUpWhenPluggedIn() throws Exception { - boolean powerState; - + public void testWakefulnessAwake_ShouldWakeUpWhenPluggedIn() { createService(); startSystem(); forceSleep(); @@ -579,7 +576,7 @@ public class PowerManagerServiceTest { } @Test - public void testWakefulnessDoze_goToSleep() throws Exception { + public void testWakefulnessDoze_goToSleep() { createService(); // Start with AWAKE state startSystem(); @@ -595,7 +592,7 @@ public class PowerManagerServiceTest { public void testWasDeviceIdleFor_true() { int interval = 1000; createService(); - mService.systemReady(null); + mService.systemReady(); mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); mService.onUserActivity(); advanceTime(interval + 1 /* just a little more */); @@ -606,7 +603,7 @@ public class PowerManagerServiceTest { public void testWasDeviceIdleFor_false() { int interval = 1000; createService(); - mService.systemReady(null); + mService.systemReady(); mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); mService.onUserActivity(); assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse(); @@ -615,7 +612,7 @@ public class PowerManagerServiceTest { @Test public void testForceSuspend_putsDeviceToSleep() { createService(); - mService.systemReady(null); + mService.systemReady(); mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); // Verify that we start awake @@ -636,7 +633,7 @@ public class PowerManagerServiceTest { } @Test - public void testForceSuspend_pakeLocksDisabled() { + public void testForceSuspend_wakeLocksDisabled() { final String tag = "TestWakelockTag_098213"; final int flags = PowerManager.PARTIAL_WAKE_LOCK; final String pkg = mContextSpy.getOpPackageName(); @@ -661,7 +658,7 @@ public class PowerManagerServiceTest { // // TEST STARTS HERE // - mService.systemReady(null); + mService.systemReady(); mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); // Verify that we start awake @@ -686,7 +683,7 @@ public class PowerManagerServiceTest { } @Test - public void testForceSuspend_forceSuspendFailurePropagated() throws Exception { + public void testForceSuspend_forceSuspendFailurePropagated() { createService(); startSystem(); when(mNativeWrapperMock.nativeForceSuspend()).thenReturn(false); @@ -694,7 +691,7 @@ public class PowerManagerServiceTest { } @Test - public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() throws Exception { + public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() { final String suspendBlockerName = "PowerManagerService.Display"; final String tag = "acq_causes_wakeup"; final String packageName = "pkg.name"; @@ -741,7 +738,7 @@ public class PowerManagerServiceTest { } @Test - public void testSuspendBlockerHeldDuringBoot() throws Exception { + public void testSuspendBlockerHeldDuringBoot() { final String suspendBlockerName = "PowerManagerService.Booting"; final boolean[] isAcquired = new boolean[1]; @@ -760,7 +757,7 @@ public class PowerManagerServiceTest { createService(); assertTrue(isAcquired[0]); - mService.systemReady(null); + mService.systemReady(); assertTrue(isAcquired[0]); mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); @@ -768,7 +765,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_hideWarningIfStayOnIsEnabledAndPluggedIn() throws Exception { + public void testInattentiveSleep_hideWarningIfStayOnIsEnabledAndPluggedIn() { setMinimumScreenOffTimeoutConfig(5); setAttentiveWarningDuration(120); setAttentiveTimeout(100); @@ -788,7 +785,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_hideWarningIfInattentiveSleepIsDisabled() throws Exception { + public void testInattentiveSleep_hideWarningIfInattentiveSleepIsDisabled() { setMinimumScreenOffTimeoutConfig(5); setAttentiveWarningDuration(120); setAttentiveTimeout(100); @@ -807,7 +804,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_userActivityDismissesWarning() throws Exception { + public void testInattentiveSleep_userActivityDismissesWarning() { final DisplayInfo info = new DisplayInfo(); info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); @@ -833,7 +830,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_warningHiddenAfterWakingUp() throws Exception { + public void testInattentiveSleep_warningHiddenAfterWakingUp() { setMinimumScreenOffTimeoutConfig(5); setAttentiveWarningDuration(70); setAttentiveTimeout(100); @@ -851,7 +848,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() throws Exception { + public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() { setAttentiveTimeout(-1); createService(); startSystem(); @@ -859,7 +856,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_goesToSleepAfterTimeout() throws Exception { + public void testInattentiveSleep_goesToSleepAfterTimeout() { setMinimumScreenOffTimeoutConfig(5); setAttentiveTimeout(5); createService(); @@ -871,7 +868,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_goesToSleepWithWakeLock() throws Exception { + public void testInattentiveSleep_goesToSleepWithWakeLock() { final String pkg = mContextSpy.getOpPackageName(); final Binder token = new Binder(); final String tag = "testInattentiveSleep_goesToSleepWithWakeLock"; @@ -893,8 +890,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_wakeLockOnAfterRelease_inattentiveSleepTimeoutNotAffected() - throws Exception { + public void testInattentiveSleep_wakeLockOnAfterRelease_inattentiveSleepTimeoutNotAffected() { final DisplayInfo info = new DisplayInfo(); info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); @@ -922,8 +918,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_userActivityNoChangeLights_inattentiveSleepTimeoutNotAffected() - throws Exception { + public void testInattentiveSleep_userActivityNoChangeLights_inattentiveSleepTimeoutNotAffected() { final DisplayInfo info = new DisplayInfo(); info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); @@ -945,8 +940,7 @@ public class PowerManagerServiceTest { } @Test - public void testInattentiveSleep_userActivity_inattentiveSleepTimeoutExtended() - throws Exception { + public void testInattentiveSleep_userActivity_inattentiveSleepTimeoutExtended() { final DisplayInfo info = new DisplayInfo(); info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); @@ -965,7 +959,7 @@ public class PowerManagerServiceTest { } @Test - public void testWakeLock_affectsProperDisplayGroup() throws Exception { + public void testWakeLock_affectsProperDisplayGroup() { final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = new AtomicReference<>(); @@ -1005,7 +999,7 @@ public class PowerManagerServiceTest { } @Test - public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() throws Exception { + public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() { final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = new AtomicReference<>(); @@ -1045,7 +1039,7 @@ public class PowerManagerServiceTest { } @Test - public void testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups() throws Exception { + public void testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups() { final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1; final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = @@ -1086,7 +1080,7 @@ public class PowerManagerServiceTest { } @Test - public void testBoot_ShouldBeAwake() throws Exception { + public void testBoot_ShouldBeAwake() { createService(); startSystem(); @@ -1095,7 +1089,7 @@ public class PowerManagerServiceTest { } @Test - public void testBoot_DesiredScreenPolicyShouldBeBright() throws Exception { + public void testBoot_DesiredScreenPolicyShouldBeBright() { createService(); startSystem(); @@ -1104,7 +1098,7 @@ public class PowerManagerServiceTest { } @Test - public void testQuiescentBoot_ShouldBeAsleep() throws Exception { + public void testQuiescentBoot_ShouldBeAsleep() { when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1"); createService(); startSystem(); @@ -1115,7 +1109,7 @@ public class PowerManagerServiceTest { } @Test - public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() throws Exception { + public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() { when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1"); createService(); startSystem(); @@ -1124,7 +1118,7 @@ public class PowerManagerServiceTest { } @Test - public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() throws Exception { + public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() { when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1"); createService(); startSystem(); @@ -1134,11 +1128,10 @@ public class PowerManagerServiceTest { } @Test - public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() - throws Exception { + public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() { when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1"); createService(); - mService.systemReady(null); + mService.systemReady(); mService.getBinderServiceInstance().wakeUp(mClock.now(), PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name"); @@ -1150,7 +1143,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplayAvailable_available() throws Exception { + public void testIsAmbientDisplayAvailable_available() { createService(); when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true); @@ -1158,7 +1151,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplayAvailable_unavailable() throws Exception { + public void testIsAmbientDisplayAvailable_unavailable() { createService(); when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false); @@ -1166,14 +1159,14 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressed_default_notSuppressed() throws Exception { + public void testIsAmbientDisplaySuppressed_default_notSuppressed() { createService(); assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse(); } @Test - public void testIsAmbientDisplaySuppressed_suppressed() throws Exception { + public void testIsAmbientDisplaySuppressed_suppressed() { createService(); mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); @@ -1181,7 +1174,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressed_notSuppressed() throws Exception { + public void testIsAmbientDisplaySuppressed_notSuppressed() { createService(); mService.getBinderServiceInstance().suppressAmbientDisplay("test", false); @@ -1189,7 +1182,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() throws Exception { + public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() { createService(); mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false); mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true); @@ -1198,7 +1191,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() throws Exception { + public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() { createService(); mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false); mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false); @@ -1207,7 +1200,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() throws Exception { + public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() { createService(); assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test")) @@ -1215,7 +1208,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForToken_suppressed() throws Exception { + public void testIsAmbientDisplaySuppressedForToken_suppressed() { createService(); mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); @@ -1224,7 +1217,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForToken_notSuppressed() throws Exception { + public void testIsAmbientDisplaySuppressedForToken_notSuppressed() { createService(); mService.getBinderServiceInstance().suppressAmbientDisplay("test", false); @@ -1233,8 +1226,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed() - throws Exception { + public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed() { createService(); mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true); mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true); @@ -1246,8 +1238,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed() - throws Exception { + public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed() { createService(); mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true); mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false); @@ -1259,8 +1250,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable() - throws Exception { + public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable() { createService(); when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false); @@ -1270,8 +1260,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForTokenByApp_default() - throws Exception { + public void testIsAmbientDisplaySuppressedForTokenByApp_default() { createService(); BinderService service = mService.getBinderServiceInstance(); @@ -1280,8 +1269,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp() - throws Exception { + public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp() { createService(); BinderService service = mService.getBinderServiceInstance(); service.suppressAmbientDisplay("test", true); @@ -1294,8 +1282,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp() - throws Exception { + public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp() { createService(); BinderService service = mService.getBinderServiceInstance(); service.suppressAmbientDisplay("test", false); @@ -1308,8 +1295,7 @@ public class PowerManagerServiceTest { } @Test - public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp() - throws Exception { + public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp() { createService(); BinderService service = mService.getBinderServiceInstance(); service.suppressAmbientDisplay("test1", true); @@ -1358,7 +1344,7 @@ public class PowerManagerServiceTest { @Test public void testSetPowerBoost_redirectsCallToNativeWrapper() { createService(); - mService.systemReady(null); + mService.systemReady(); mService.getBinderServiceInstance().setPowerBoost(Boost.INTERACTION, 1234); @@ -1368,7 +1354,7 @@ public class PowerManagerServiceTest { @Test public void testSetPowerMode_redirectsCallToNativeWrapper() { createService(); - mService.systemReady(null); + mService.systemReady(); // Enabled launch boost in BatterySaverController to allow setting launch mode. when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(false); @@ -1384,7 +1370,7 @@ public class PowerManagerServiceTest { @Test public void testSetPowerMode_withLaunchBoostDisabledAndModeLaunch_ignoresCallToEnable() { createService(); - mService.systemReady(null); + mService.systemReady(); // Disables launch boost in BatterySaverController. when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true); @@ -1400,7 +1386,7 @@ public class PowerManagerServiceTest { @Test public void testSetPowerModeChecked_returnsNativeCallResult() { createService(); - mService.systemReady(null); + mService.systemReady(); // Disables launch boost in BatterySaverController. when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true); @@ -1419,7 +1405,7 @@ public class PowerManagerServiceTest { } @Test - public void testMultiDisplay_wakefulnessUpdates() throws Exception { + public void testMultiDisplay_wakefulnessUpdates() { final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = new AtomicReference<>(); @@ -1448,7 +1434,7 @@ public class PowerManagerServiceTest { } @Test - public void testMultiDisplay_addDisplayGroup_preservesWakefulness() throws Exception { + public void testMultiDisplay_addDisplayGroup_preservesWakefulness() { final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = new AtomicReference<>(); @@ -1472,7 +1458,7 @@ public class PowerManagerServiceTest { } @Test - public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() throws Exception { + public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() { final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = new AtomicReference<>(); @@ -1502,7 +1488,7 @@ public class PowerManagerServiceTest { @Test public void testGetFullPowerSavePolicy_returnsStateMachineResult() { createService(); - mService.systemReady(null); + mService.systemReady(); BatterySaverPolicyConfig mockReturnConfig = new BatterySaverPolicyConfig.Builder().build(); when(mBatterySaverStateMachineMock.getFullBatterySaverPolicy()) .thenReturn(mockReturnConfig); @@ -1517,7 +1503,7 @@ public class PowerManagerServiceTest { @Test public void testSetFullPowerSavePolicy_callsStateMachine() { createService(); - mService.systemReady(null); + mService.systemReady(); BatterySaverPolicyConfig mockSetPolicyConfig = new BatterySaverPolicyConfig.Builder().build(); when(mBatterySaverStateMachineMock.setFullBatterySaverPolicy(any())).thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 26b34fdd4e04..304fe5a1c9c3 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -30,6 +30,7 @@ import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.State; import android.hardware.power.stats.StateResidency; import android.hardware.power.stats.StateResidencyResult; +import android.os.Looper; import androidx.test.InstrumentationRegistry; @@ -145,12 +146,12 @@ public class PowerStatsServiceTest { } @Override - PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String meterCacheFilename, + PowerStatsLogger createPowerStatsLogger(Context context, Looper looper, + File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, + mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath, meterFilename, meterCacheFilename, modelFilename, modelCacheFilename, residencyFilename, residencyCacheFilename, diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java index 88f368e403a6..06726b031a36 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java @@ -22,16 +22,20 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; +import static android.app.usage.UsageStatsManager.standbyBucketToString; import android.os.FileUtils; import android.test.AndroidTestCase; import java.io.File; +import java.util.Map; public class AppIdleHistoryTests extends AndroidTestCase { @@ -65,7 +69,7 @@ public class AppIdleHistoryTests extends AndroidTestCase { // Screen On time file should be written right away assertTrue(aih.getScreenOnTimeFile().exists()); - aih.writeAppIdleTimes(USER_ID); + aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 2000); // stats file should be written now assertTrue(new File(new File(mStorageDir, "users/" + USER_ID), AppIdleHistory.APP_IDLE_FILENAME).exists()); @@ -128,7 +132,7 @@ public class AppIdleHistoryTests extends AndroidTestCase { // Check persistence aih.writeAppIdleDurations(); - aih.writeAppIdleTimes(USER_ID); + aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 3000); aih = new AppIdleHistory(mStorageDir, 4000); assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE); @@ -165,7 +169,7 @@ public class AppIdleHistoryTests extends AndroidTestCase { aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000)); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 4000, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_TIMEOUT); - aih.writeAppIdleTimes(USER_ID); + aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 4000); aih = new AppIdleHistory(mStorageDir, 5000); assertEquals(REASON_MAIN_TIMEOUT, aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000)); @@ -180,11 +184,63 @@ public class AppIdleHistoryTests extends AndroidTestCase { aih.reportUsage(null, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_MOVE_TO_FOREGROUND, 2000, 0); // Persist data - aih.writeAppIdleTimes(USER_ID); + aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 2000); // Recover data from disk aih = new AppIdleHistory(mStorageDir, 5000); // Verify data is intact assertEquals(REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND, aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000)); } + + public void testBucketExpiryTimes() throws Exception { + AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000 /* elapsedRealtime */); + aih.reportUsage(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_SUB_USAGE_SLICE_PINNED, + 2000 /* elapsedRealtime */, 6000 /* expiryRealtime */); + assertEquals(5000 /* expectedExpiryTimeMs */, aih.getBucketExpiryTimeMs(PACKAGE_1, USER_ID, + STANDBY_BUCKET_WORKING_SET, 2000 /* elapsedRealtime */)); + aih.reportUsage(PACKAGE_2, USER_ID, STANDBY_BUCKET_FREQUENT, + REASON_SUB_USAGE_NOTIFICATION_SEEN, + 2000 /* elapsedRealtime */, 3000 /* expiryRealtime */); + assertEquals(2000 /* expectedExpiryTimeMs */, aih.getBucketExpiryTimeMs(PACKAGE_2, USER_ID, + STANDBY_BUCKET_FREQUENT, 2000 /* elapsedRealtime */)); + aih.writeAppIdleTimes(USER_ID, 4000 /* elapsedRealtime */); + + // Persist data + aih = new AppIdleHistory(mStorageDir, 5000 /* elapsedRealtime */); + final Map<Integer, Long> expectedExpiryTimes1 = Map.of( + STANDBY_BUCKET_ACTIVE, 0L, + STANDBY_BUCKET_WORKING_SET, 5000L, + STANDBY_BUCKET_FREQUENT, 0L, + STANDBY_BUCKET_RARE, 0L, + STANDBY_BUCKET_RESTRICTED, 0L + ); + // For PACKAGE_1, only WORKING_SET bucket should have an expiry time. + verifyBucketExpiryTimes(aih, PACKAGE_1, USER_ID, 5000 /* elapsedRealtime */, + expectedExpiryTimes1); + final Map<Integer, Long> expectedExpiryTimes2 = Map.of( + STANDBY_BUCKET_ACTIVE, 0L, + STANDBY_BUCKET_WORKING_SET, 0L, + STANDBY_BUCKET_FREQUENT, 0L, + STANDBY_BUCKET_RARE, 0L, + STANDBY_BUCKET_RESTRICTED, 0L + ); + // For PACKAGE_2, there shouldn't be any expiry time since the one set earlier would have + // elapsed by the time the data was persisted to disk + verifyBucketExpiryTimes(aih, PACKAGE_2, USER_ID, 5000 /* elapsedRealtime */, + expectedExpiryTimes2); + } + + private void verifyBucketExpiryTimes(AppIdleHistory aih, String packageName, int userId, + long elapsedRealtimeMs, Map<Integer, Long> expectedExpiryTimesMs) throws Exception { + for (Map.Entry<Integer, Long> entry : expectedExpiryTimesMs.entrySet()) { + final int bucket = entry.getKey(); + final long expectedExpiryTimeMs = entry.getValue(); + final long actualExpiryTimeMs = aih.getBucketExpiryTimeMs(packageName, userId, bucket, + elapsedRealtimeMs); + assertEquals("Unexpected expiry time for pkg=" + packageName + ", userId=" + userId + + ", bucket=" + standbyBucketToString(bucket), + expectedExpiryTimeMs, actualExpiryTimeMs); + } + } }
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 949ee01d6872..18d3f3d0e805 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -63,7 +63,6 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -828,7 +827,6 @@ public class AppStandbyControllerTests { } @Test - @FlakyTest(bugId = 185169504) public void testNotificationEvent() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); @@ -842,6 +840,22 @@ public class AppStandbyControllerTests { } @Test + public void testNotificationEvent_changePromotedBucket() throws Exception { + mController.forceIdleState(PACKAGE_1, USER_ID, true); + reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); + assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + + // TODO: Avoid hardcoding these string constants. + mInjector.mSettingsBuilder.setInt("notification_seen_promoted_bucket", + STANDBY_BUCKET_FREQUENT); + mInjector.mPropertiesChangedListener.onPropertiesChanged( + mInjector.getDeviceConfigProperties()); + mController.forceIdleState(PACKAGE_1, USER_ID, true); + reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); + assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); + } + + @Test @FlakyTest(bugId = 185169504) public void testSlicePinnedEvent() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java index 6369dbc6b171..81677101c139 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -133,13 +133,14 @@ public class VibrationScalerTest { mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH)); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); - // Unexpected vibration intensity will be treated as SCALE_NONE. + // Vibration setting being bypassed will use default setting and not scale. assertEquals(IExternalVibratorService.SCALE_NONE, mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH)); } @Test public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() { + setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_MEDIUM); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH); PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK, /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); @@ -158,35 +159,33 @@ public class VibrationScalerTest { setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION); - // Unexpected intensity setting will be mapped to STRONG. - assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + // Vibration setting being bypassed will use default setting. + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); } @Test public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() { + setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH); VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale( - effect, USAGE_NOTIFICATION)); + PrebakedSegment scaled = + getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION)); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_MEDIUM); - scaled = getFirstSegment(mVibrationScaler.scale( - effect, USAGE_NOTIFICATION)); + scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION)); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW); - scaled = getFirstSegment(mVibrationScaler.scale( - effect, USAGE_NOTIFICATION)); + scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION)); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); - scaled = getFirstSegment(mVibrationScaler.scale( - effect, USAGE_NOTIFICATION)); - // Unexpected intensity setting will be mapped to STRONG. - assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION)); + // Vibration setting being bypassed will use default setting. + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); } @Test diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index ff59d0f22c3c..2c22419d1372 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -330,6 +330,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -363,6 +365,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -376,6 +380,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -389,6 +395,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -402,6 +410,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -419,6 +429,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -522,9 +534,17 @@ public class VibrationSettingsTest { } private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) { + assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0); + } + + private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage, + @VibrationAttributes.Flag int flags) { assertNull(errorMessageForUsage(usage), mVibrationSettings.shouldIgnoreVibration(UID, - VibrationAttributes.createForUsage(usage))); + new VibrationAttributes.Builder() + .setUsage(usage) + .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) + .build())); } private String errorMessageForUsage(int usage) { diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index b0bdaf084b1a..ab86e295e28a 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -1207,6 +1207,33 @@ public class VibratorManagerServiceTest { assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale); } + @Test + public void onExternalVibration_withBypassMuteAudioFlag_ignoresUserSettings() { + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + AudioAttributes audioAttrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM) + .build(); + AudioAttributes flaggedAudioAttrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM) + .setFlags(AudioAttributes.FLAG_BYPASS_MUTE) + .build(); + createSystemReadyService(); + + int scale = mExternalVibratorService.onExternalVibrationStart( + new ExternalVibration(UID, PACKAGE_NAME, audioAttrs, + mock(IExternalVibrationController.class))); + assertEquals(IExternalVibratorService.SCALE_MUTE, scale); + + createSystemReadyService(); + scale = mExternalVibratorService.onExternalVibrationStart( + new ExternalVibration(UID, PACKAGE_NAME, flaggedAudioAttrs, + mock(IExternalVibrationController.class))); + assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale); + } + private VibrationEffectSegment expectedPrebaked(int effectId) { return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); } diff --git a/services/tests/servicestests/src/com/android/server/wm/OWNERS b/services/tests/servicestests/src/com/android/server/wm/OWNERS new file mode 100644 index 000000000000..361760d7f945 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index f21991defbec..a12bc3b4c59f 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -16,14 +16,20 @@ package com.android.server; +import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE; import static android.app.UiModeManager.MODE_NIGHT_AUTO; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; import static android.app.UiModeManager.MODE_NIGHT_NO; import static android.app.UiModeManager.MODE_NIGHT_YES; import static android.app.UiModeManager.PROJECTION_TYPE_ALL; import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE; import static android.app.UiModeManager.PROJECTION_TYPE_NONE; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @@ -194,7 +200,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Ignore // b/152719290 - Fails on stage-aosp-master @Test - public void setNightMoveActivated_overridesFunctionCorrectly() throws RemoteException { + public void setNightModeActivated_overridesFunctionCorrectly() throws RemoteException { // set up when(mPowerManager.isInteractive()).thenReturn(false); mService.setNightMode(MODE_NIGHT_NO); @@ -225,6 +231,29 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void setNightModeActivated_true_withCustomModeBedtime_shouldOverrideNightModeCorrectly() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + assertFalse(mUiManagerService.getConfiguration().isNightModeActive()); + + mService.setNightModeActivated(true); + + assertThat(mUiManagerService.getConfiguration().isNightModeActive()).isTrue(); + } + + @Test + public void setNightModeActivated_false_withCustomModeBedtime_shouldOverrideNightModeCorrectly() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + assertFalse(mUiManagerService.getConfiguration().isNightModeActive()); + + mService.setNightModeActivated(true); + mService.setNightModeActivated(false); + + assertThat(mUiManagerService.getConfiguration().isNightModeActive()).isFalse(); + } + + @Test public void setAutoMode_screenOffRegistered() throws RemoteException { try { mService.setNightMode(MODE_NIGHT_NO); @@ -247,7 +276,44 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test - public void setNightModeActivated_fromNoToYesAndBAck() throws RemoteException { + public void setNightModeCustomType_bedtime_shouldNotActivateNightMode() throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_NO); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void setNightModeCustomType_noPermission_shouldThrow() throws RemoteException { + when(mContext.checkCallingOrSelfPermission(eq(MODIFY_DAY_NIGHT_MODE))) + .thenReturn(PackageManager.PERMISSION_DENIED); + + assertThrows(SecurityException.class, + () -> mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME)); + } + + @Test + public void setNightModeCustomType_bedtime_shouldHaveNoScreenOffRegistered() + throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_NO); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + ArgumentCaptor<IntentFilter> intentFiltersCaptor = ArgumentCaptor.forClass( + IntentFilter.class); + verify(mContext, atLeastOnce()).registerReceiver(any(BroadcastReceiver.class), + intentFiltersCaptor.capture()); + + List<IntentFilter> intentFilters = intentFiltersCaptor.getAllValues(); + for (IntentFilter intentFilter : intentFilters) { + assertThat(intentFilter.hasAction(Intent.ACTION_SCREEN_OFF)).isFalse(); + } + } + + @Test + public void setNightModeActivated_fromNoToYesAndBack() throws RemoteException { mService.setNightMode(MODE_NIGHT_NO); mService.setNightModeActivated(true); assertTrue(isNightModeActivated()); @@ -256,7 +322,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test - public void setNightModeActivated_permissiontoChangeOtherUsers() throws RemoteException { + public void setNightModeActivated_permissionToChangeOtherUsers() throws RemoteException { SystemService.TargetUser user = mock(SystemService.TargetUser.class); doReturn(9).when(user).getUserIdentifier(); mUiManagerService.onUserSwitching(user, user); @@ -267,6 +333,89 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOnAndBedtime_shouldActivate() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOffAndBedtime_shouldDeactivate() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOnAndSchedule_shouldNotActivate() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, true /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeSchedule_withParamOnAndBedtime_shouldNotActivate() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_CUSTOM); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeSchedule_withParamOnAndBedtime_thenCustomTypeBedtime_shouldActivate() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_CUSTOM); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOnAndBedtime_thenCustomTypeSchedule_shouldKeepNightModeActivate() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_CUSTOM); + LocalTime now = LocalTime.now(); + mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOnAndBedtime_thenCustomTypeScheduleAndScreenOff_shouldDeactivateNightMode() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_CUSTOM); + LocalTime now = LocalTime.now(); + mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test public void autoNightModeSwitch_batterySaverOn() throws RemoteException { mService.setNightMode(MODE_NIGHT_NO); when(mTwilightState.isNight()).thenReturn(false); @@ -283,6 +432,191 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void nightModeCustomBedtime_batterySaverOn_notInBedtime_shouldActivateNightMode() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(true).build()); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeCustomBedtime_batterySaverOn_afterBedtime_shouldKeepNightModeActivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(true).build()); + + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeBedtime_duringBedtime_batterySaverOnThenOff_shouldKeepNightModeActivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(true).build()); + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(false).build()); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_batterySaverOnThenOff_finallyAfterBedtime_shouldDeactivateNightMode() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(true).build()); + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(false).build()); + + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_changeModeToNo_shouldDeactivateNightMode() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_NO); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_changeModeToNoAndThenExitBedtime_shouldKeepNightModeDeactivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + mService.setNightMode(MODE_NIGHT_NO); + + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_changeModeToYes_shouldKeepNightModeActivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_YES); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_changeModeToYesAndThenExitBedtime_shouldKeepNightModeActivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_YES); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeNo_duringBedtime_shouldKeepNightModeDeactivated() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_NO); + + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeNo_thenChangeToCustomTypeBedtimeAndActivate_shouldActivateNightMode() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_NO); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeYes_thenChangeToCustomTypeBedtime_shouldDeactivateNightMode() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_YES); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeYes_thenChangeToCustomTypeBedtimeAndActivate_shouldActivateNightMode() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_YES); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeAuto_thenChangeToCustomTypeBedtime_notInBedtime_shouldDeactivateNightMode() + throws RemoteException { + // set mode to auto + mService.setNightMode(MODE_NIGHT_AUTO); + mService.setNightModeActivated(true); + // now it is night time + doReturn(true).when(mTwilightState).isNight(); + mTwilightListener.onTwilightStateChanged(mTwilightState); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeAuto_thenChangeToCustomTypeBedtime_duringBedtime_shouldActivateNightMode() + throws RemoteException { + // set mode to auto + mService.setNightMode(MODE_NIGHT_AUTO); + mService.setNightModeActivated(true); + // now it is night time + doReturn(true).when(mTwilightState).isNight(); + mTwilightListener.onTwilightStateChanged(mTwilightState); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test public void setAutoMode_clearCache() throws RemoteException { try { mService.setNightMode(MODE_NIGHT_AUTO); @@ -327,6 +661,62 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void getNightModeCustomType_nightModeNo_shouldReturnUnknown() throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_NO); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN); + } + + @Test + public void getNightModeCustomType_nightModeYes_shouldReturnUnknown() throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_YES); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN); + } + + @Test + public void getNightModeCustomType_nightModeAuto_shouldReturnUnknown() throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_AUTO); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN); + } + + @Test + public void getNightModeCustomType_nightModeCustom_shouldReturnSchedule() + throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_CUSTOM); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE); + } + + @Test + public void getNightModeCustomType_nightModeCustomBedtime_shouldReturnBedtime() + throws RemoteException { + try { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + } + + @Test + public void getNightModeCustomType_permissionNotGranted_shouldThrow() + throws RemoteException { + when(mContext.checkCallingOrSelfPermission(eq(MODIFY_DAY_NIGHT_MODE))) + .thenReturn(PackageManager.PERMISSION_DENIED); + + assertThrows(SecurityException.class, () -> mService.getNightModeCustomType()); + } + + @Test public void isNightModeActive_nightModeYes() throws RemoteException { try { mService.setNightMode(MODE_NIGHT_YES); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index a834e2b6cc5a..12cd834d1d66 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -394,7 +394,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { "disabledMessage", 0, "disabledMessageResName", null, null, 0, null, 0, 0, 0, "iconResName", "bitmapPath", null, 0, - null, null, null); + null, null, null, null); return si; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 11777ef7c1e0..a192bf87cce4 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -182,7 +182,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; -import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; @@ -2702,6 +2701,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // should trigger a broadcast mBinderService.setNotificationsEnabledForPackage(PKG, 0, true); + Thread.sleep(500); + waitForIdle(); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); @@ -2729,6 +2730,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // should trigger a broadcast mBinderService.setNotificationsEnabledForPackage(PKG, 0, true); + Thread.sleep(500); + waitForIdle(); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); @@ -7174,6 +7177,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testAreNotificationsEnabledForPackage_viaInternalService() throws Exception { + assertEquals(mInternalService.areNotificationsEnabledForPackage( + mContext.getPackageName(), mUid), + mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid)); + verify(mPermissionHelper, never()).hasPermission(anyInt()); + } + + @Test public void testAreBubblesAllowedForPackage_crossUser() throws Exception { try { mBinderService.getBubblePreferenceForPackage(mContext.getPackageName(), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index 0c8fe35484f7..f6400b65bc4d 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -536,6 +536,12 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { } @Test + public void testAreNotificationsEnabledForPackage_viaInternalService() { + mInternalService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid); + verify(mPermissionHelper).hasPermission(mUid); + } + + @Test public void testGetPackageImportance() throws Exception { when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); assertThat(mBinderService.getPackageImportance(mContext.getPackageName())) @@ -595,6 +601,8 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED); mService.mAppOpsCallback.opChanged(0, mUid, PKG); + Thread.sleep(500); + waitForIdle(); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); @@ -610,6 +618,8 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED); mService.mAppOpsCallback.opChanged(0, mUid, PKG); + Thread.sleep(500); + waitForIdle(); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index d49cf670f471..736fbd9d50ef 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -46,6 +46,7 @@ import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID; import static com.google.common.truth.Truth.assertThat; @@ -542,6 +543,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setInvalidMessageSent(PKG_P, UID_P); mHelper.setValidMessageSent(PKG_P, UID_P); mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true); + mHelper.setValidBubbleSent(PKG_P, UID_P); mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE); @@ -561,6 +563,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertFalse(mHelper.hasSentInvalidMsg(PKG_N_MR1, UID_N_MR1)); assertTrue(mHelper.hasSentValidMsg(PKG_P, UID_P)); assertTrue(mHelper.didUserEverDemoteInvalidMsgApp(PKG_P, UID_P)); + assertTrue(mHelper.hasSentValidBubble(PKG_P, UID_P)); assertEquals(channel1, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false)); compareChannels(channel2, @@ -3174,6 +3177,19 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testIsGroupBlocked_appCannotCreateAsBlocked() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + group.setBlocked(true); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true); + assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId())); + + NotificationChannelGroup group3 = group.clone(); + group3.setBlocked(false); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group3, true); + assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId())); + } + + @Test public void testIsGroup_appCannotResetBlock() throws Exception { NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true); @@ -4235,6 +4251,52 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testTooManyGroups() { + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + try { + NotificationChannelGroup group = new NotificationChannelGroup( + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT), + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + fail("Allowed to create too many notification channel groups"); + } catch (IllegalStateException e) { + // great + } + } + + @Test + public void testTooManyGroups_xml() throws Exception { + String extraGroup = "EXTRA"; + String extraGroup1 = "EXTRA1"; + + // create first... many... directly so we don't need a big xml blob in this test + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + + final String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + + "<channelGroup id=\"" + extraGroup + "\" name=\"hi\"/>" + + "<channelGroup id=\"" + extraGroup1 + "\" name=\"hi2\"/>" + + "</package>" + + "</ranking>"; + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertNull(mHelper.getNotificationChannelGroup(extraGroup, PKG_O, UID_O)); + assertNull(mHelper.getNotificationChannelGroup(extraGroup1, PKG_O, UID_O)); + } + + @Test public void testRestoreMultiUser() throws Exception { String pkg = "restore_pkg"; String channelId = "channelId"; @@ -4706,7 +4768,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testGetConversations_noDisabledGroups() { NotificationChannelGroup group = new NotificationChannelGroup("a", "a"); group.setBlocked(true); - mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, false); NotificationChannel parent = new NotificationChannel("parent", "p", 1); mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); @@ -4927,6 +4989,18 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testValidBubbleSent() { + // create package preferences + mHelper.canShowBadge(PKG_P, UID_P); + // false by default + assertFalse(mHelper.hasSentValidBubble(PKG_P, UID_P)); + + // set something valid was sent + mHelper.setValidBubbleSent(PKG_P, UID_P); + assertTrue(mHelper.hasSentValidBubble(PKG_P, UID_P)); + } + + @Test public void testPullPackageChannelPreferencesStats() { String channelId = "parent"; String name = "messages"; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java index c77a474e032c..f135d16ea3b8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -40,7 +40,10 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class VibratorHelperTest extends UiServiceTestCase { + // OFF/ON vibration pattern private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 }; + // (amplitude, frequency, duration) triples list + private static final float[] PWLE_PATTERN = new float[] { 1, 120, 100 }; @Mock private Vibrator mVibrator; @@ -58,12 +61,16 @@ public class VibratorHelperTest extends UiServiceTestCase { public void createWaveformVibration_insistent_createsRepeatingVibration() { assertRepeatingVibration( VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true)); + assertRepeatingVibration( + VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ true)); } @Test public void createWaveformVibration_nonInsistent_createsSingleShotVibration() { assertSingleVibration( VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false)); + assertSingleVibration( + VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ false)); } @Test @@ -71,6 +78,11 @@ public class VibratorHelperTest extends UiServiceTestCase { assertNull(VibratorHelper.createWaveformVibration(null, false)); assertNull(VibratorHelper.createWaveformVibration(new long[0], false)); assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false)); + + assertNull(VibratorHelper.createPwleWaveformVibration(null, false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[0], false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0 }, false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0, 0, 0 }, false)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 8a057f7b7065..0dcf7992e02b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -103,6 +103,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; @@ -136,6 +137,7 @@ import android.view.IWindowManager; import android.view.IWindowSession; import android.view.InsetsSource; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; @@ -152,6 +154,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; @@ -3065,6 +3068,50 @@ public class ActivityRecordTests extends WindowTestsBase { assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame()); } + @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD}) + @Test + public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest() + throws RemoteException { + final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); + final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); + + mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow( + mImeWindow, null, null); + mImeWindow.getControllableInsetProvider().setServerVisible(true); + + // Simulate app2 is closing and let app1 is visible to be IME targets. + makeWindowVisibleAndDrawn(app1, mImeWindow); + mDisplayContent.setImeLayeringTarget(app1); + mDisplayContent.updateImeInputAndControlTarget(app1); + app2.mActivityRecord.commitVisibility(false, false); + + // app1 requests IME visible. + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_IME, true); + app1.setRequestedVisibilities(requestedVisibilities); + mDisplayContent.getInsetsStateController().onInsetsModified(app1); + + // Verify app1's IME insets is visible and app2's IME insets frozen flag set. + assertTrue(app1.getInsetsState().peekSource(ITYPE_IME).isVisible()); + assertTrue(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Simulate switching to app2 to make it visible to be IME targets. + makeWindowVisibleAndDrawn(app2); + spyOn(app2); + spyOn(app2.mClient); + ArgumentCaptor<InsetsState> insetsStateCaptor = ArgumentCaptor.forClass(InsetsState.class); + doReturn(true).when(app2).isReadyToDispatchInsetsState(); + mDisplayContent.setImeLayeringTarget(app2); + mDisplayContent.updateImeInputAndControlTarget(app2); + + // Verify after unfreezing app2's IME insets state, we won't dispatch visible IME insets + // to client if the app didn't request IME visible. + assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); + verify(app2.mClient, atLeastOnce()).insetsChanged(insetsStateCaptor.capture(), anyBoolean(), + anyBoolean()); + assertFalse(insetsStateCaptor.getAllValues().get(0).peekSource(ITYPE_IME).isVisible()); + } + @Test public void testInClosingAnimation_doNotHideSurface() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index e2f0658f3da3..fdc898207e27 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -31,7 +31,6 @@ import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -51,7 +50,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -457,7 +455,7 @@ public class ActivityStarterTests extends WindowTestsBase { final ActivityRecord splitSecondReusableActivity = activities.second; final ActivityRecord splitSecondTopActivity = new ActivityBuilder(mAtm).setCreateTask(true) .setParentTask(splitSecondReusableActivity.getRootTask()).build(); - assertTrue(splitSecondTopActivity.inSplitScreenSecondaryWindowingMode()); + assertTrue(splitSecondTopActivity.inMultiWindowMode()); // Let primary stack has focus. splitPrimaryFocusActivity.moveFocusableActivityToTop("testSplitScreenTaskToFront"); @@ -476,13 +474,16 @@ public class ActivityStarterTests extends WindowTestsBase { final TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm); // The fullscreen windowing mode activity will be moved to split-secondary by // TestSplitOrganizer when a split-primary task appears. - final ActivityRecord splitSecondActivity = - new ActivityBuilder(mAtm).setCreateTask(true).build(); final ActivityRecord splitPrimaryActivity = new TaskBuilder(mSupervisor) .setParentTaskFragment(splitOrg.mPrimary) .setCreateActivity(true) .build() .getTopMostActivity(); + final ActivityRecord splitSecondActivity = new TaskBuilder(mSupervisor) + .setParentTaskFragment(splitOrg.mSecondary) + .setCreateActivity(true) + .build() + .getTopMostActivity(); splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true; assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask()); @@ -1090,7 +1091,7 @@ public class ActivityStarterTests extends WindowTestsBase { starter.setActivityOptions(options.toBundle()) .setReason("testWindowingModeOptionsLaunchAdjacent") .setOutActivity(outActivity).execute(); - assertThat(outActivity[0].inSplitScreenSecondaryWindowingMode()).isTrue(); + assertThat(outActivity[0].inMultiWindowMode()).isTrue(); } @Test @@ -1108,50 +1109,6 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test - public void testStartActivityInner_allSplitScreenPrimaryActivitiesVisible() { - // Given - final ActivityStarter starter = prepareStarter(0, false); - - starter.setReason("testAllSplitScreenPrimaryActivitiesAreResumed"); - - final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); - targetRecord.setFocusable(false); - targetRecord.setVisibility(false); - final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).build(); - - final Task stack = spy( - mRootWindowContainer.getDefaultTaskDisplayArea() - .createRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - /* onTop */true)); - - stack.addChild(targetRecord); - - doReturn(stack).when(mRootWindowContainer).getLaunchRootTask(any(), any(), any(), any(), - anyBoolean(), any(), anyInt(), anyInt(), anyInt()); - - starter.mStartActivity = new ActivityBuilder(mAtm).build(); - - // When - starter.startActivityInner( - /* r */targetRecord, - /* sourceRecord */ sourceRecord, - /* voiceSession */null, - /* voiceInteractor */ null, - /* startFlags */ 0, - /* doResume */true, - /* options */null, - /* inTask */null, - /* inTaskFragment */ null, - /* restrictedBgActivity */false, - /* intentGrants */null); - - // Then - verify(stack).ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); - verify(targetRecord).makeVisibleIfNeeded(null, true); - assertTrue(targetRecord.mVisibleRequested); - } - - @Test public void testStartActivityInner_inTaskFragment() { final ActivityStarter starter = prepareStarter(0, false); final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index 525888df7c78..22e411ea1500 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; @@ -30,7 +31,6 @@ import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION; import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; -import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST; @@ -49,12 +49,14 @@ import static org.testng.Assert.assertThrows; import static java.util.stream.Collectors.toList; +import android.app.ActivityOptions; import android.content.res.Resources; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; +import android.window.WindowContainerToken; import com.google.android.collect.Lists; @@ -81,6 +83,8 @@ import java.util.stream.Collectors; */ @Presubmit public class DisplayAreaPolicyBuilderTest { + private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID = + "android.test.launchTaskDisplayAreaFeatureId"; @Rule public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule(); @@ -104,6 +108,7 @@ public class DisplayAreaPolicyBuilderTest { mImeContainer = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "ImeContainer"); mDisplayContent = mock(DisplayContent.class); doReturn(true).when(mDisplayContent).isTrusted(); + doReturn(DEFAULT_DISPLAY).when(mDisplayContent).getDisplayId(); mDisplayContent.isDefaultDisplay = true; mDefaultTaskDisplayArea = new TaskDisplayArea(mDisplayContent, mWms, "Tasks", FEATURE_DEFAULT_TASK_CONTAINER); @@ -197,25 +202,6 @@ public class DisplayAreaPolicyBuilderTest { } @Test - public void testBuilder_defaultPolicy_hasOneHandedBackgroundFeature() { - final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( - resourcesWithProvider("")); - final DisplayAreaPolicyBuilder.Result defaultPolicy = - (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent, - mRoot, mImeContainer); - if (mDisplayContent.isDefaultDisplay) { - final List<Feature> features = defaultPolicy.getFeatures(); - boolean hasOneHandedBackgroundFeature = false; - for (Feature feature : features) { - hasOneHandedBackgroundFeature |= - feature.getId() == FEATURE_ONE_HANDED_BACKGROUND_PANEL; - } - - assertThat(hasOneHandedBackgroundFeature).isTrue(); - } - } - - @Test public void testBuilder_defaultPolicy_hasWindowedMagnificationFeature() { final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( resourcesWithProvider("")); @@ -731,6 +717,208 @@ public class DisplayAreaPolicyBuilderTest { .build(); } + @Test + public void testGetTaskDisplayArea_DefaultFunction_NullOptions_ReturnsDefaultTda() { + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot) + .setTaskDisplayAreas(mTaskDisplayAreaList); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1) + .setImeContainer(mImeContainer) + .setTaskDisplayAreas(Lists.newArrayList(mTda1)); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2) + .setTaskDisplayAreas(Lists.newArrayList(mTda2)); + final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() + .setRootHierarchy(hierarchy0) + .addDisplayAreaGroupHierarchy(hierarchy1) + .addDisplayAreaGroupHierarchy(hierarchy2) + .build(mWms); + + final TaskDisplayArea tda = policy.getTaskDisplayArea(null /* options */); + + assertThat(tda).isEqualTo(mDefaultTaskDisplayArea); + assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea()); + } + + @Test + public void testGetTaskDisplayArea_DefaultFunction_NotContainsLunchedTda_ReturnsDefaultTda() { + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot) + .setTaskDisplayAreas(mTaskDisplayAreaList); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1) + .setImeContainer(mImeContainer) + .setTaskDisplayAreas(Lists.newArrayList(mTda1)); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2) + .setTaskDisplayAreas(Lists.newArrayList(mTda2)); + final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() + .setRootHierarchy(hierarchy0) + .addDisplayAreaGroupHierarchy(hierarchy1) + .addDisplayAreaGroupHierarchy(hierarchy2) + .build(mWms); + + final TaskDisplayArea tda = policy.getTaskDisplayArea(new Bundle()); + + assertThat(tda).isEqualTo(mDefaultTaskDisplayArea); + assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea()); + } + + @Test + public void testGetTaskDisplayArea_DefaultFunction_InvalidTdaToken_ReturnsDefaultTda() { + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot) + .setTaskDisplayAreas(mTaskDisplayAreaList); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1) + .setImeContainer(mImeContainer) + .setTaskDisplayAreas(Lists.newArrayList(mTda1)); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2) + .setTaskDisplayAreas(Lists.newArrayList(mTda2)); + final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() + .setRootHierarchy(hierarchy0) + .addDisplayAreaGroupHierarchy(hierarchy1) + .addDisplayAreaGroupHierarchy(hierarchy2) + .build(mWms); + final ActivityOptions options = ActivityOptions.makeBasic(); + final WindowContainerToken fakeToken = mRoot.mRemoteToken.toWindowContainerToken(); + options.setLaunchTaskDisplayArea(fakeToken); + + final TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle()); + + assertThat(tda).isEqualTo(mDefaultTaskDisplayArea); + assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetTaskDisplayArea_DefaultFunction_TdaOnDifferentDisplay_ThrowException() { + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot) + .setTaskDisplayAreas(mTaskDisplayAreaList); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1) + .setImeContainer(mImeContainer) + .setTaskDisplayAreas(Lists.newArrayList(mTda1)); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2) + .setTaskDisplayAreas(Lists.newArrayList(mTda2)); + final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() + .setRootHierarchy(hierarchy0) + .addDisplayAreaGroupHierarchy(hierarchy1) + .addDisplayAreaGroupHierarchy(hierarchy2) + .build(mWms); + final TaskDisplayArea tdaOnSecondaryDisplay = mock(TaskDisplayArea.class); + doReturn(DEFAULT_DISPLAY + 1).when(tdaOnSecondaryDisplay).getDisplayId(); + doReturn(tdaOnSecondaryDisplay).when(tdaOnSecondaryDisplay).asTaskDisplayArea(); + tdaOnSecondaryDisplay.mRemoteToken = new WindowContainer.RemoteToken(tdaOnSecondaryDisplay); + + final WindowContainerToken tdaToken = tdaOnSecondaryDisplay.mRemoteToken + .toWindowContainerToken(); + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchTaskDisplayArea(tdaToken); + final TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle()); + + assertThat(tda).isEqualTo(mDefaultTaskDisplayArea); + assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea()); + } + + @Test + public void testGetTaskDisplayArea_DefaultFunction_ContainsTdaToken_ReturnsTda() { + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot) + .setTaskDisplayAreas(mTaskDisplayAreaList); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1) + .setImeContainer(mImeContainer) + .setTaskDisplayAreas(Lists.newArrayList(mTda1)); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2) + .setTaskDisplayAreas(Lists.newArrayList(mTda2)); + final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() + .setRootHierarchy(hierarchy0) + .addDisplayAreaGroupHierarchy(hierarchy1) + .addDisplayAreaGroupHierarchy(hierarchy2) + .build(mWms); + final ActivityOptions options = ActivityOptions.makeBasic(); + + final WindowContainerToken defaultTdaToken = mDefaultTaskDisplayArea.mRemoteToken + .toWindowContainerToken(); + options.setLaunchTaskDisplayArea(defaultTdaToken); + TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle()); + + assertThat(tda).isEqualTo(mDefaultTaskDisplayArea); + assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea()); + + final WindowContainerToken tda1Token = mTda1.mRemoteToken.toWindowContainerToken(); + options.setLaunchTaskDisplayArea(tda1Token); + tda = policy.getTaskDisplayArea(options.toBundle()); + + assertThat(tda).isEqualTo(mTda1); + + final WindowContainerToken tda2Token = mTda2.mRemoteToken.toWindowContainerToken(); + options.setLaunchTaskDisplayArea(tda2Token); + tda = policy.getTaskDisplayArea(options.toBundle()); + + assertThat(tda).isEqualTo(mTda2); + } + + @Test + public void testBuilder_getTaskDisplayArea_setSelectTaskDisplayAreaFunc() { + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot) + .setTaskDisplayAreas(mTaskDisplayAreaList); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1) + .setImeContainer(mImeContainer) + .setTaskDisplayAreas(Lists.newArrayList(mTda1)); + final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 = + new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2) + .setTaskDisplayAreas(Lists.newArrayList(mTda2)); + final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() + .setRootHierarchy(hierarchy0) + .setSelectTaskDisplayAreaFunc((options) -> { + if (options == null) { + return mDefaultTaskDisplayArea; + } + final int tdaFeatureId = + options.getInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID); + if (tdaFeatureId == mTda1.mFeatureId) { + return mTda1; + } + if (tdaFeatureId == mTda2.mFeatureId) { + return mTda2; + } + return mDefaultTaskDisplayArea; + }) + .addDisplayAreaGroupHierarchy(hierarchy1) + .addDisplayAreaGroupHierarchy(hierarchy2) + .build(mWms); + + TaskDisplayArea tda = policy.getTaskDisplayArea(null /* options */); + + assertThat(tda).isEqualTo(mDefaultTaskDisplayArea); + assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea()); + + final Bundle options = new Bundle(); + options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, -1); + tda = policy.getTaskDisplayArea(options); + assertThat(tda).isEqualTo(mDefaultTaskDisplayArea); + + options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mDefaultTaskDisplayArea.mFeatureId); + tda = policy.getTaskDisplayArea(options); + assertThat(tda).isEqualTo(mDefaultTaskDisplayArea); + + options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mTda1.mFeatureId); + tda = policy.getTaskDisplayArea(options); + assertThat(tda).isEqualTo(mTda1); + + options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mTda2.mFeatureId); + tda = policy.getTaskDisplayArea(options); + assertThat(tda).isEqualTo(mTda2); + } + private static Resources resourcesWithProvider(String provider) { Resources mock = mock(Resources.class); when(mock.getString( diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 2ef59f6ac5c9..2f78b588f305 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1718,6 +1718,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testShellTransitRotation() { DisplayContent dc = createNewDisplay(); + dc.setLastHasContent(); final TestTransitionPlayer testPlayer = registerTestTransitionPlayer(); final DisplayRotation dr = dc.getDisplayRotation(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 69700052ce74..2b131e1fb7c5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -322,4 +322,26 @@ public class DisplayPolicyTests extends WindowTestsBase { assertFalse(navBarSource.getFrame().isEmpty()); assertTrue(imeSource.getFrame().contains(navBarSource.getFrame())); } + + @UseTestDisplay(addWindows = { W_NAVIGATION_BAR }) + @Test + public void testInsetsGivenContentFrame() { + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + displayInfo.rotation = ROTATION_0; + + WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs; + displayPolicy.addWindowLw(mNavBarWindow, attrs); + mNavBarWindow.setRequestedSize(attrs.width, attrs.height); + mNavBarWindow.getControllableInsetProvider().setServerVisible(true); + + mNavBarWindow.mGivenContentInsets.set(0, 10, 0, 0); + + displayPolicy.layoutWindowLw(mNavBarWindow, null, mDisplayContent.mDisplayFrames); + final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); + final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR); + assertEquals(attrs.height - 10, navBarSource.getFrame().height()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java new file mode 100644 index 000000000000..6e11d8cf23e1 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerHelperTests.java @@ -0,0 +1,147 @@ +/* + * 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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; + +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.os.UserHandle; +import android.util.ArraySet; +import android.view.Display; +import android.window.DisplayWindowPolicyController; + +import androidx.annotation.NonNull; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +/** + * Tests for the {@link DisplayWindowPolicyControllerHelper} class. + * + * Build/Install/Run: + * atest WmTests:DisplayWindowPolicyControllerHelperTests + */ +@RunWith(WindowTestRunner.class) +public class DisplayWindowPolicyControllerHelperTests extends WindowTestsBase { + private static final int TEST_USER_0_ID = 0; + private static final int TEST_USER_1_ID = 10; + + private TestDisplayWindowPolicyController mDwpc = new TestDisplayWindowPolicyController(); + private DisplayContent mSecondaryDisplay; + + @Before + public void setUp() { + doReturn(mDwpc).when(mWm.mDisplayManagerInternal) + .getDisplayWindowPolicyController(anyInt()); + mSecondaryDisplay = createNewDisplay(); + assertNotEquals(Display.DEFAULT_DISPLAY, mSecondaryDisplay.getDisplayId()); + assertTrue(mSecondaryDisplay.mDwpcHelper.hasController()); + } + + @Test + public void testOnRunningActivityChanged() { + final ActivityRecord activity1 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_0_ID); + verifyTopActivityAndRunningUid(activity1, + true /* expectedUid0 */, false /* expectedUid1 */); + final ActivityRecord activity2 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_1_ID); + verifyTopActivityAndRunningUid(activity2, + true /* expectedUid0 */, true /* expectedUid1 */); + final ActivityRecord activity3 = launchActivityOnDisplay(mSecondaryDisplay, TEST_USER_0_ID); + verifyTopActivityAndRunningUid(activity3, + true /* expectedUid0 */, true /* expectedUid1 */); + + activity3.finishing = true; + verifyTopActivityAndRunningUid(activity2, + true /* expectedUid0 */, true /* expectedUid1 */); + + activity2.finishing = true; + verifyTopActivityAndRunningUid(activity1, + true /* expectedUid0 */, false /* expectedUid1 */); + + activity1.finishing = true; + verifyTopActivityAndRunningUid(null /* expectedTopActivity */, + false /* expectedUid0 */, false /* expectedUid1 */); + } + + private void verifyTopActivityAndRunningUid(ActivityRecord expectedTopActivity, + boolean expectedUid0, boolean expectedUid1) { + mSecondaryDisplay.onRunningActivityChanged(); + int uidAmount = (expectedUid0 && expectedUid1) ? 2 : (expectedUid0 || expectedUid1) ? 1 : 0; + assertEquals(expectedTopActivity == null ? null : + expectedTopActivity.info.getComponentName(), mDwpc.mTopActivity); + assertEquals(expectedTopActivity == null ? UserHandle.USER_NULL : + expectedTopActivity.info.applicationInfo.uid, mDwpc.mTopActivityUid); + assertEquals(uidAmount, mDwpc.mRunningUids.size()); + assertTrue(mDwpc.mRunningUids.contains(TEST_USER_0_ID) == expectedUid0); + assertTrue(mDwpc.mRunningUids.contains(TEST_USER_1_ID) == expectedUid1); + + } + + private ActivityRecord launchActivityOnDisplay(DisplayContent display, int uid) { + final Task task = new TaskBuilder(mSupervisor) + .setDisplay(display) + .setUserId(uid) + .build(); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(task) + .setUid(uid) + .setOnTop(true) + .build(); + return activity; + } + + private class TestDisplayWindowPolicyController extends DisplayWindowPolicyController { + + ComponentName mTopActivity = null; + int mTopActivityUid = UserHandle.USER_NULL; + ArraySet<Integer> mRunningUids = new ArraySet<>(); + + @Override + public boolean canContainActivities(@NonNull List<ActivityInfo> activities) { + return false; + } + + @Override + public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags, + int systemWindowFlags) { + return false; + } + + @Override + public void onTopActivityChanged(ComponentName topActivity, int uid) { + super.onTopActivityChanged(topActivity, uid); + mTopActivity = topActivity; + mTopActivityUid = uid; + } + + @Override + public void onRunningAppsChanged(ArraySet<Integer> runningUids) { + super.onRunningAppsChanged(runningUids); + mRunningUids.clear(); + mRunningUids.addAll(runningUids); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java index 9e4cd161c478..365e749df79f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java @@ -51,6 +51,7 @@ import androidx.test.filters.SmallTest; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; import org.junit.Before; import org.junit.Test; @@ -447,6 +448,21 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { assertEquals(456, config.densityDpi); } + @Test + public void testDisplayRotationSettingsAppliedOnCreation() { + // Create new displays with different rotation settings + final SettingsEntry settingsEntry1 = new SettingsEntry(); + settingsEntry1.mIgnoreOrientationRequest = false; + final DisplayContent dcDontIgnoreOrientation = createMockSimulatedDisplay(settingsEntry1); + final SettingsEntry settingsEntry2 = new SettingsEntry(); + settingsEntry2.mIgnoreOrientationRequest = true; + final DisplayContent dcIgnoreOrientation = createMockSimulatedDisplay(settingsEntry2); + + // Verify that newly created displays are created with correct rotation settings + assertFalse(dcDontIgnoreOrientation.getIgnoreOrientationRequest()); + assertTrue(dcIgnoreOrientation.getIgnoreOrientationRequest()); + } + public final class TestSettingsProvider implements DisplayWindowSettings.SettingsProvider { Map<DisplayInfo, SettingsEntry> mOverrideSettingsCache = new HashMap<>(); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index ea5bf52af905..d3282b97a6b8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; @@ -82,19 +81,6 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_dockedTaskVisible() { - addWindow(TYPE_STATUS_BAR, "statusBar"); - addWindow(TYPE_NAVIGATION_BAR, "navBar"); - - final WindowState win = createWindow(null, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, - ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); - final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); - - // The app must not control any system bars. - assertNull(controls); - } - - @Test public void testControlsForDispatch_multiWindowTaskVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index b4c449a94a40..632a59d3484d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -117,8 +117,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS, mFinishedCallback); - // Remove the app window so that the animation target can not be created - activity.removeImmediately(); + // The activity doesn't contain window so the animation target cannot be created. mController.startAnimation(); // Verify that the finish callback to reparent the leash is called diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index bb49cd25ac6b..3cb0bed32493 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -240,7 +241,7 @@ public class RootTaskTests extends WindowTestsBase { final WindowConfiguration windowConfiguration = task.getResolvedOverrideConfiguration().windowConfiguration; spyOn(windowConfiguration); - doReturn(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) + doReturn(WINDOWING_MODE_MULTI_WINDOW) .when(windowConfiguration).getWindowingMode(); // Prevent adjust task dimensions @@ -323,72 +324,12 @@ public class RootTaskTests extends WindowTestsBase { } @Test - public void testPrimarySplitScreenMoveToBack() { - TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); - // We're testing an edge case here where we have primary + fullscreen rather than secondary. - organizer.setMoveToSecondaryOnEnter(false); - - // Create primary splitscreen root task. - final Task primarySplitScreen = new TaskBuilder(mAtm.mTaskSupervisor) - .setParentTaskFragment(organizer.mPrimary) - .setOnTop(true) - .build(); - - // Assert windowing mode. - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, primarySplitScreen.getWindowingMode()); - - // Move primary to back. - primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", - null /* task */); - - // Assert that root task is at the bottom. - assertEquals(0, getTaskIndexOf(mDefaultTaskDisplayArea, primarySplitScreen)); - - // Ensure no longer in splitscreen. - assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); - - // Ensure that the override mode is restored to undefined - assertEquals(WINDOWING_MODE_UNDEFINED, - primarySplitScreen.getRequestedOverrideWindowingMode()); - } - - @Test - public void testMoveToPrimarySplitScreenThenMoveToBack() { - TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); - // This time, start with a fullscreen activity root task. - final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - primarySplitScreen.reparent(organizer.mPrimary, POSITION_TOP, - false /*moveParents*/, "test"); - - // Assert windowing mode. - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, primarySplitScreen.getWindowingMode()); - - // Move primary to back. - primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", - null /* task */); - - // Assert that root task is at the bottom. - assertEquals(primarySplitScreen, organizer.mSecondary.getChildAt(0)); - - // Ensure that the override mode is restored to what it was (fullscreen) - assertEquals(WINDOWING_MODE_UNDEFINED, - primarySplitScreen.getRequestedOverrideWindowingMode()); - } - - @Test public void testSplitScreenMoveToBack() { TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); - // Explicitly reparent task to primary split root to enter split mode, in which implies - // primary on top and secondary containing the home task below another root task. - final Task primaryTask = mDefaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task secondaryTask = mDefaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task primaryTask = organizer.createTaskToPrimary(true /* onTop */); + final Task secondaryTask = organizer.createTaskToSecondary(true /* onTop */); final Task homeRoot = mDefaultTaskDisplayArea.getRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); - primaryTask.reparent(organizer.mPrimary, POSITION_TOP); mDefaultTaskDisplayArea.positionChildAt(POSITION_TOP, organizer.mPrimary, false /* includingParents */); @@ -397,21 +338,26 @@ public class RootTaskTests extends WindowTestsBase { // Assert that the primaryTask is now below home in its parent but primary is left alone. assertEquals(0, organizer.mPrimary.getChildCount()); - assertEquals(primaryTask, organizer.mSecondary.getChildAt(0)); + // Assert that root task is at the bottom. + assertEquals(0, getTaskIndexOf(mDefaultTaskDisplayArea, primaryTask)); assertEquals(1, organizer.mPrimary.compareTo(organizer.mSecondary)); assertEquals(1, homeRoot.compareTo(primaryTask)); assertEquals(homeRoot.getParent(), primaryTask.getParent()); // Make sure windowing modes are correct - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, organizer.mPrimary.getWindowingMode()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, primaryTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, organizer.mPrimary.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, secondaryTask.getWindowingMode()); + // Ensure no longer in splitscreen. + assertEquals(WINDOWING_MODE_FULLSCREEN, primaryTask.getWindowingMode()); + // Ensure that the override mode is restored to undefined + assertEquals(WINDOWING_MODE_UNDEFINED, primaryTask.getRequestedOverrideWindowingMode()); // Move secondary to back via parent (should be equivalent) organizer.mSecondary.moveToBack("test", secondaryTask); - // Assert that it is now in back but still in secondary split + // Assert that it is now in back and left in secondary split + assertEquals(0, organizer.mSecondary.getChildCount()); assertEquals(1, homeRoot.compareTo(primaryTask)); - assertEquals(secondaryTask, organizer.mSecondary.getChildAt(0)); assertEquals(1, primaryTask.compareTo(secondaryTask)); assertEquals(homeRoot.getParent(), secondaryTask.getParent()); } @@ -423,7 +369,7 @@ public class RootTaskTests extends WindowTestsBase { .setTask(rootHomeTask) .build(); final Task secondaryRootTask = mAtm.mTaskOrganizerController.createRootTask( - rootHomeTask.getDisplayContent(), WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + rootHomeTask.getDisplayContent(), WINDOWING_MODE_MULTI_WINDOW, null); rootHomeTask.reparent(secondaryRootTask, POSITION_TOP); assertEquals(secondaryRootTask, rootHomeTask.getParent()); @@ -581,6 +527,7 @@ public class RootTaskTests extends WindowTestsBase { assertTrue(pinnedRootTask.shouldBeVisible(null /* starting */)); } + // TODO(b/199236198): check this is unnecessary or need to migrate after remove legacy split. @Test public void testShouldBeVisible_SplitScreen() { // task not supporting split should be fullscreen for this test. @@ -700,30 +647,23 @@ public class RootTaskTests extends WindowTestsBase { @Test public void testGetVisibility_MultiLevel() { - final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, true /* onTop */); + TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); final Task splitPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); - // Creating as two-level tasks so home task can be reparented to split-secondary root task. + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); final Task splitSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */, - true /* twoLevelTask */); + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); - doReturn(false).when(homeRootTask).isTranslucent(any()); doReturn(false).when(splitPrimary).isTranslucent(any()); doReturn(false).when(splitSecondary).isTranslucent(any()); - // Re-parent home to split secondary. - homeRootTask.reparent(splitSecondary, POSITION_TOP); - // Current tasks should be visible. + // Re-parent tasks to split. + organizer.putTaskToPrimary(splitPrimary, true /* onTop */); + organizer.putTaskToSecondary(splitSecondary, true /* onTop */); + // Reparented tasks should be visible. assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */)); assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */)); - // Home task should still be visible even though it is a child of another visible task. - assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, - homeRootTask.getVisibility(null /* starting */)); - // Add fullscreen translucent task that partially occludes split tasks final Task translucentRootTask = createStandardRootTaskForVisibilityTest( @@ -736,19 +676,12 @@ public class RootTaskTests extends WindowTestsBase { splitPrimary.getVisibility(null /* starting */)); assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitSecondary.getVisibility(null /* starting */)); - // Home task should be visible behind translucent since its parent is visible behind - // translucent. - assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - homeRootTask.getVisibility(null /* starting */)); - // Hide split-secondary - splitSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); + organizer.mSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); // Home split secondary and home task should be invisible. assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, - homeRootTask.getVisibility(null /* starting */)); } @Test @@ -1094,36 +1027,6 @@ public class RootTaskTests extends WindowTestsBase { assertEquals(pinnedRootTask, getRootTaskAbove(alwaysOnTopRootTask2)); } - @Test - public void testSplitScreenMoveToFront() { - final Task splitScreenPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task splitScreenSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - - doReturn(false).when(splitScreenPrimary).isTranslucent(any()); - doReturn(false).when(splitScreenSecondary).isTranslucent(any()); - doReturn(false).when(assistantRootTask).isTranslucent(any()); - - assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); - - splitScreenSecondary.moveToFront("testSplitScreenMoveToFront"); - - if (isAssistantOnTop()) { - assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); - } else { - assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertFalse(assistantRootTask.shouldBeVisible(null /* starting */)); - } - } - private Task createStandardRootTaskForVisibilityTest(int windowingMode, boolean translucent) { final Task rootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 645d804b3cfc..c722b0a70c0a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -16,7 +16,7 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; @@ -937,8 +937,8 @@ public class SizeCompatTests extends WindowTestsBase { mTask.reparent(organizer.mPrimary, POSITION_TOP, false /*moveParents*/, "test"); organizer.mPrimary.setBounds(0, 0, 1000, 1400); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mTask.getWindowingMode()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, activity.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode()); // Resizable activity is sandboxed due to config being enabled. assertActivityMaxBoundsSandboxed(activity); @@ -1828,8 +1828,8 @@ public class SizeCompatTests extends WindowTestsBase { mTask.reparent(organizer.mPrimary, POSITION_TOP, false /*moveParents*/, "test"); organizer.mPrimary.setBounds(0, 0, 1000, 1400); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mTask.getWindowingMode()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mActivity.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); // Non-resizable activity in size compat mode assertScaled(); @@ -1868,8 +1868,8 @@ public class SizeCompatTests extends WindowTestsBase { mTask.reparent(organizer.mPrimary, POSITION_TOP, false /*moveParents*/, "test"); organizer.mPrimary.setBounds(0, 0, 1000, 1400); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mTask.getWindowingMode()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mActivity.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); // Non-resizable activity in size compat mode assertScaled(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 9ad8c5b081f7..0debdfa3bd1a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -21,14 +21,17 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.content.Intent; @@ -471,6 +474,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setOrganizer(mOrganizer) + .setFragmentToken(mFragmentToken) .build(); // Mock the task to invisible @@ -485,4 +489,38 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Verifies that event was not sent verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); } + + /** + * Tests that a task fragment info changed event is still sent if the task is invisible only + * when the info changed event is because of the last activity in a task finishing. + */ + @Test + public void testLastPendingTaskFragmentInfoChangedEventOfInvisibleTaskSent() { + // Create a TaskFragment with an activity, all within a parent task + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setOrganizer(mOrganizer) + .setFragmentToken(mFragmentToken) + .setCreateParentTask() + .createActivityCount(1) + .build(); + final Task parentTask = taskFragment.getTask(); + final ActivityRecord activity = taskFragment.getTopNonFinishingActivity(); + assertTrue(parentTask.shouldBeVisible(null)); + + // Dispatch pending info changed event from creating the activity + mController.registerOrganizer(mIOrganizer); + taskFragment.mTaskFragmentAppearedSent = true; + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + + // Finish the activity and verify that the task is invisible + activity.finishing = true; + assertFalse(parentTask.shouldBeVisible(null)); + + // Verify the info changed callback still occurred despite the task being invisible + reset(mOrganizer); + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + verify(mOrganizer).onTaskFragmentInfoChanged(any()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index 3065e7dab296..8b0716c699ad 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -30,6 +30,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.ArgumentMatchers.any; +import android.annotation.Nullable; import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; @@ -38,6 +39,8 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; +import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; + class TestDisplayContent extends DisplayContent { public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300; @@ -81,6 +84,7 @@ class TestDisplayContent extends DisplayContent { protected final ActivityTaskManagerService mService; private boolean mSystemDecorations = false; private int mStatusBarHeight = 0; + private SettingsEntry mOverrideSettings; Builder(ActivityTaskManagerService service, int width, int height) { mService = service; @@ -104,6 +108,10 @@ class TestDisplayContent extends DisplayContent { private String generateUniqueId() { return "TEST_DISPLAY_CONTENT_" + System.currentTimeMillis(); } + Builder setOverrideSettings(@Nullable SettingsEntry overrideSettings) { + mOverrideSettings = overrideSettings; + return this; + } Builder setSystemDecorations(boolean yes) { mSystemDecorations = yes; return this; @@ -151,6 +159,11 @@ class TestDisplayContent extends DisplayContent { TestDisplayContent build() { SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock); + if (mOverrideSettings != null) { + mService.mWindowManager.mDisplayWindowSettingsProvider + .updateOverrideSettings(mInfo, mOverrideSettings); + } + final int displayId = SystemServicesTestRule.sNextDisplayId++; final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, mInfo, DEFAULT_DISPLAY_ADJUSTMENTS); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index ed3888c7aedd..141588a87585 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -488,6 +488,7 @@ public class TransitionTests extends WindowTestsBase { final TestTransitionPlayer player = registerTestTransitionPlayer(); mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1); + mDisplayContent.setLastHasContent(); mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */, null /* displayChange */); final FadeRotationAnimationController fadeController = @@ -536,6 +537,7 @@ public class TransitionTests extends WindowTestsBase { null /* remoteTransition */, null /* displayChange */); mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1); final int anyChanges = 1; + mDisplayContent.setLastHasContent(); mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */); transition.setKnownConfigChanges(mDisplayContent, anyChanges); final FadeRotationAnimationController fadeController = @@ -550,9 +552,11 @@ public class TransitionTests extends WindowTestsBase { assertTrue(app.getTask().inTransition()); final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class); + final SurfaceControl leash = statusBar.mToken.getAnimationLeash(); + doReturn(true).when(leash).isValid(); player.onTransactionReady(startTransaction); // The leash should be unrotated. - verify(startTransaction).setMatrix(eq(statusBar.mToken.getAnimationLeash()), any(), any()); + verify(startTransaction).setMatrix(eq(leash), any(), any()); // The redrawn window will be faded in when the transition finishes. And because this test // only use one non-activity window, the fade rotation controller should also be cleared. diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index bec53d71b312..8b14e981f046 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; @@ -77,6 +78,7 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.util.ArrayList; @@ -271,6 +273,22 @@ public class WindowContainerTests extends WindowTestsBase { } @Test + public void testRemoveImmediatelyClearsLeash() { + final AnimationAdapter animAdapter = mock(AnimationAdapter.class); + final WindowToken token = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent); + final SurfaceControl.Transaction t = token.getPendingTransaction(); + token.startAnimation(t, animAdapter, false /* hidden */, + SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION); + final ArgumentCaptor<SurfaceControl> leashCaptor = + ArgumentCaptor.forClass(SurfaceControl.class); + verify(animAdapter).startAnimation(leashCaptor.capture(), eq(t), anyInt(), any()); + assertTrue(token.mSurfaceAnimator.hasLeash()); + token.removeImmediately(); + assertFalse(token.mSurfaceAnimator.hasLeash()); + verify(t).remove(eq(leashCaptor.getValue())); + } + + @Test public void testAddChildByIndex() { final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); final TestWindowContainer root = builder.setLayer(0).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 34038c57eafc..62c1067ec707 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -17,14 +17,10 @@ package com.android.server.wm; import static android.app.AppOpsManager.OP_NONE; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -66,15 +62,14 @@ import static org.mockito.Mockito.mock; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IApplicationThread; -import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.os.Build; @@ -106,6 +101,7 @@ import android.window.TransitionRequestInfo; import com.android.internal.policy.AttributeCache; import com.android.internal.util.ArrayUtils; +import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; import org.junit.After; import org.junit.Before; @@ -720,18 +716,21 @@ class WindowTestsBase extends SystemServiceTestsBase { /** Creates a {@link DisplayContent} and adds it to the system. */ private DisplayContent createNewDisplayWithImeSupport(@DisplayImePolicy int imePolicy) { - return createNewDisplay(mDisplayInfo, imePolicy); + return createNewDisplay(mDisplayInfo, imePolicy, /* overrideSettings */ null); } /** Creates a {@link DisplayContent} that supports IME and adds it to the system. */ DisplayContent createNewDisplay(DisplayInfo info) { - return createNewDisplay(info, DISPLAY_IME_POLICY_LOCAL); + return createNewDisplay(info, DISPLAY_IME_POLICY_LOCAL, /* overrideSettings */ null); } /** Creates a {@link DisplayContent} and adds it to the system. */ - private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy) { + private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy, + @Nullable SettingsEntry overrideSettings) { final DisplayContent display = - new TestDisplayContent.Builder(mAtm, info).build(); + new TestDisplayContent.Builder(mAtm, info) + .setOverrideSettings(overrideSettings) + .build(); final DisplayContent dc = display.mDisplayContent; // this display can show IME. dc.mWmService.mDisplayWindowSettings.setDisplayImePolicy(dc, imePolicy); @@ -749,7 +748,7 @@ class WindowTestsBase extends SystemServiceTestsBase { DisplayInfo displayInfo = new DisplayInfo(); displayInfo.copyFrom(mDisplayInfo); displayInfo.state = displayState; - return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_LOCAL); + return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_LOCAL, /* overrideSettings */ null); } /** Creates a {@link TestWindowState} */ @@ -761,11 +760,15 @@ class WindowTestsBase extends SystemServiceTestsBase { /** Creates a {@link DisplayContent} as parts of simulate display info for test. */ DisplayContent createMockSimulatedDisplay() { + return createMockSimulatedDisplay(/* overrideSettings */ null); + } + + DisplayContent createMockSimulatedDisplay(@Nullable SettingsEntry overrideSettings) { DisplayInfo displayInfo = new DisplayInfo(); displayInfo.copyFrom(mDisplayInfo); displayInfo.type = Display.TYPE_VIRTUAL; displayInfo.ownerUid = SYSTEM_UID; - return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_FALLBACK_DISPLAY); + return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_FALLBACK_DISPLAY, overrideSettings); } IDisplayWindowInsetsController createDisplayWindowInsetsController() { @@ -1507,64 +1510,55 @@ class WindowTestsBase extends SystemServiceTestsBase { static class TestSplitOrganizer extends WindowOrganizerTests.StubOrganizer { final ActivityTaskManagerService mService; + final TaskDisplayArea mDefaultTDA; Task mPrimary; Task mSecondary; - boolean mInSplit = false; - // moves everything to secondary. Most tests expect this since sysui usually does it. - boolean mMoveToSecondaryOnEnter = true; int mDisplayId; - private static final int[] CONTROLLED_ACTIVITY_TYPES = { - ACTIVITY_TYPE_STANDARD, - ACTIVITY_TYPE_HOME, - ACTIVITY_TYPE_RECENTS, - ACTIVITY_TYPE_UNDEFINED - }; - private static final int[] CONTROLLED_WINDOWING_MODES = { - WINDOWING_MODE_FULLSCREEN, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - WINDOWING_MODE_UNDEFINED - }; + TestSplitOrganizer(ActivityTaskManagerService service, DisplayContent display) { mService = service; + mDefaultTDA = display.getDefaultTaskDisplayArea(); mDisplayId = display.mDisplayId; mService.mTaskOrganizerController.registerTaskOrganizer(this); mPrimary = mService.mTaskOrganizerController.createRootTask( - display, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + display, WINDOWING_MODE_MULTI_WINDOW, null); mSecondary = mService.mTaskOrganizerController.createRootTask( - display, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);; + display, WINDOWING_MODE_MULTI_WINDOW, null); + + mPrimary.setAdjacentTaskFragment(mSecondary, true); + display.getDefaultTaskDisplayArea().setLaunchAdjacentFlagRootTask(mSecondary); + + final Rect primaryBounds = new Rect(); + final Rect secondaryBounds = new Rect(); + display.getBounds().splitVertically(primaryBounds, secondaryBounds); + mPrimary.setBounds(primaryBounds); + mSecondary.setBounds(secondaryBounds); } + TestSplitOrganizer(ActivityTaskManagerService service) { this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay()); } - public void setMoveToSecondaryOnEnter(boolean move) { - mMoveToSecondaryOnEnter = move; + + public Task createTaskToPrimary(boolean onTop) { + final Task primaryTask = mDefaultTDA.createRootTask( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, onTop); + putTaskToPrimary(primaryTask, onTop); + return primaryTask; + } + + public Task createTaskToSecondary(boolean onTop) { + final Task secondaryTask = mDefaultTDA.createRootTask( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, onTop); + putTaskToSecondary(secondaryTask, onTop); + return secondaryTask; } - @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { - if (mInSplit) { - return; - } - if (info.topActivityType == ACTIVITY_TYPE_UNDEFINED) { - // Not populated - return; - } - if (info.configuration.windowConfiguration.getWindowingMode() - != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - return; - } - mInSplit = true; - if (!mMoveToSecondaryOnEnter) { - return; - } - DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId); - dc.getDefaultTaskDisplayArea().setLaunchRootTask( - mSecondary, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES); - dc.forAllRootTasks(rootTask -> { - if (!WindowConfiguration.isSplitScreenWindowingMode(rootTask.getWindowingMode())) { - rootTask.reparent(mSecondary, POSITION_BOTTOM); - } - }); + public void putTaskToPrimary(Task task, boolean onTop) { + task.reparent(mPrimary, onTop ? POSITION_TOP : POSITION_BOTTOM); + } + + public void putTaskToSecondary(Task task, boolean onTop) { + task.reparent(mSecondary, onTop ? POSITION_TOP : POSITION_BOTTOM); } } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 0aa62c53e269..1c72eb8db708 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -257,10 +257,11 @@ public final class UsbAlsaManager { // look for MIDI devices boolean hasMidi = parser.hasMIDIInterface(); - int midiNumInputs = parser.calculateNumMidiInputs(); - int midiNumOutputs = parser.calculateNumMidiOutputs(); + int midiNumInputs = parser.calculateNumLegacyMidiInputs(); + int midiNumOutputs = parser.calculateNumLegacyMidiOutputs(); if (DEBUG) { Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature); + Slog.d(TAG, "midiNumInputs: " + midiNumInputs + " midiNumOutputs:" + midiNumOutputs); } if (hasMidi && mHasMidiFeature) { int device = 0; diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index f33001c9241e..9ac270f17fc4 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.os.Bundle; @@ -46,6 +47,8 @@ import com.android.server.usb.descriptors.UsbInterfaceDescriptor; import com.android.server.usb.descriptors.report.TextReportCanvas; import com.android.server.usb.descriptors.tree.UsbDescriptorsTree; +import libcore.io.IoUtils; + import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -90,6 +93,13 @@ public class UsbHostManager { private ConnectionRecord mLastConnect; private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>(); + /** + * List of connected MIDI devices + */ + private final HashMap<String, UsbUniversalMidiDevice> + mMidiDevices = new HashMap<String, UsbUniversalMidiDevice>(); + private final boolean mHasMidiFeature; + /* * ConnectionRecord * Stores connection/disconnection data. @@ -245,6 +255,7 @@ public class UsbHostManager { setUsbDeviceConnectionHandler(ComponentName.unflattenFromString( deviceConnectionHandler)); } + mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); } public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) { @@ -413,6 +424,18 @@ public class UsbHostManager { mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser); + if (mHasMidiFeature) { + if (parser.containsUniversalMidiDeviceEndpoint()) { + UsbUniversalMidiDevice midiDevice = UsbUniversalMidiDevice.create(mContext, + newDevice, parser); + if (midiDevice != null) { + mMidiDevices.put(deviceAddress, midiDevice); + } else { + Slog.e(TAG, "Universal Midi Device is null."); + } + } + } + // Tracking addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, parser.getRawDescriptors()); @@ -446,6 +469,14 @@ public class UsbHostManager { Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName()); mUsbAlsaManager.usbDeviceRemoved(deviceAddress); mPermissionManager.usbDeviceRemoved(device); + + // MIDI + UsbUniversalMidiDevice midiDevice = mMidiDevices.remove(deviceAddress); + if (midiDevice != null) { + Slog.i(TAG, "USB Universal MIDI Device Removed: " + deviceAddress); + IoUtils.closeQuietly(midiDevice); + } + getCurrentUserSettings().usbDeviceRemoved(device); ConnectionRecord current = mConnected.get(deviceAddress); // Tracking diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java index b61e93b36e58..275f21755141 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -303,7 +303,8 @@ public final class UsbMidiDevice implements Closeable { } mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs, - null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); + null, null, properties, MidiDeviceInfo.TYPE_USB, + MidiDeviceInfo.PROTOCOL_UNKNOWN, mCallback); if (mServer == null) { return false; } diff --git a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java new file mode 100644 index 000000000000..db0c80f189d3 --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java @@ -0,0 +1,469 @@ +/* + * 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.usb; + +import android.content.Context; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbManager; +import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceServer; +import android.media.midi.MidiDeviceStatus; +import android.media.midi.MidiManager; +import android.media.midi.MidiReceiver; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.midi.MidiEventScheduler; +import com.android.internal.midi.MidiEventScheduler.MidiEvent; +import com.android.server.usb.descriptors.UsbDescriptorParser; +import com.android.server.usb.descriptors.UsbEndpointDescriptor; +import com.android.server.usb.descriptors.UsbInterfaceDescriptor; +import com.android.server.usb.descriptors.UsbMidiBlockParser; + +import libcore.io.IoUtils; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; + +/** + * A MIDI device that opens device connections to MIDI 2.0 endpoints. + */ +public final class UsbUniversalMidiDevice implements Closeable { + private static final String TAG = "UsbUniversalMidiDevice"; + private static final boolean DEBUG = false; + + private Context mContext; + private UsbDevice mUsbDevice; + private UsbDescriptorParser mParser; + private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces; + + // USB outputs are MIDI inputs + private final InputReceiverProxy[] mMidiInputPortReceivers; + private final int mNumInputs; + private final int mNumOutputs; + + private MidiDeviceServer mServer; + + // event schedulers for each input port of the physical device + private MidiEventScheduler[] mEventSchedulers; + + private ArrayList<UsbDeviceConnection> mUsbDeviceConnections; + private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints; + private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints; + + private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser(); + private int mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS; + + private final Object mLock = new Object(); + private boolean mIsOpen; + + private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { + + @Override + public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { + MidiDeviceInfo deviceInfo = status.getDeviceInfo(); + int numInputPorts = deviceInfo.getInputPortCount(); + int numOutputPorts = deviceInfo.getOutputPortCount(); + boolean hasOpenPorts = false; + + for (int i = 0; i < numInputPorts; i++) { + if (status.isInputPortOpen(i)) { + hasOpenPorts = true; + break; + } + } + + if (!hasOpenPorts) { + for (int i = 0; i < numOutputPorts; i++) { + if (status.getOutputPortOpenCount(i) > 0) { + hasOpenPorts = true; + break; + } + } + } + + synchronized (mLock) { + if (hasOpenPorts && !mIsOpen) { + openLocked(); + } else if (!hasOpenPorts && mIsOpen) { + closeLocked(); + } + } + } + + @Override + public void onClose() { + } + }; + + // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist + // until the device has active clients + private static final class InputReceiverProxy extends MidiReceiver { + private MidiReceiver mReceiver; + + @Override + public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { + MidiReceiver receiver = mReceiver; + if (receiver != null) { + receiver.send(msg, offset, count, timestamp); + } + } + + public void setReceiver(MidiReceiver receiver) { + mReceiver = receiver; + } + + @Override + public void onFlush() throws IOException { + MidiReceiver receiver = mReceiver; + if (receiver != null) { + receiver.flush(); + } + } + } + + /** + * Creates an UsbUniversalMidiDevice based on the input parameters. Read/Write streams + * will be created individually as some devices don't have the same number of + * inputs and outputs. + */ + public static UsbUniversalMidiDevice create(Context context, UsbDevice usbDevice, + UsbDescriptorParser parser) { + UsbUniversalMidiDevice midiDevice = new UsbUniversalMidiDevice(usbDevice, parser); + if (!midiDevice.register(context)) { + IoUtils.closeQuietly(midiDevice); + Log.e(TAG, "createDeviceServer failed"); + return null; + } + return midiDevice; + } + + private UsbUniversalMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser) { + mUsbDevice = usbDevice; + mParser = parser; + + mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors(); + + int numInputs = 0; + int numOutputs = 0; + + for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) { + UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex); + for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints(); + endpointIndex++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(endpointIndex); + // 0 is output, 1 << 7 is input. + if (endpoint.getDirection() == 0) { + numOutputs++; + } else { + numInputs++; + } + } + } + + mNumInputs = numInputs; + mNumOutputs = numOutputs; + + Log.d(TAG, "Created UsbUniversalMidiDevice with " + numInputs + " inputs and " + + numOutputs + " outputs"); + + // Create MIDI port receivers based on the number of output ports. The + // output of USB is the input of MIDI. + mMidiInputPortReceivers = new InputReceiverProxy[numOutputs]; + for (int port = 0; port < numOutputs; port++) { + mMidiInputPortReceivers[port] = new InputReceiverProxy(); + } + } + + private int calculateDefaultMidiProtocol() { + UsbManager manager = mContext.getSystemService(UsbManager.class); + + for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) { + UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex); + boolean doesInterfaceContainInput = false; + boolean doesInterfaceContainOutput = false; + for (int endpointIndex = 0; (endpointIndex < interfaceDescriptor.getNumEndpoints()) + && !(doesInterfaceContainInput && doesInterfaceContainOutput); + endpointIndex++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(endpointIndex); + // 0 is output, 1 << 7 is input. + if (endpoint.getDirection() == 0) { + doesInterfaceContainOutput = true; + } else { + doesInterfaceContainInput = true; + } + } + + // Intentionally open the device connection to query the default MIDI type for + // a connection with both the input and output set. + if (doesInterfaceContainInput + && doesInterfaceContainOutput) { + UsbDeviceConnection connection = manager.openDevice(mUsbDevice); + if (!connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true)) { + Log.d(TAG, "Can't claim control interface"); + continue; + } + int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection, + interfaceDescriptor.getInterfaceNumber(), + interfaceDescriptor.getAlternateSetting()); + + connection.close(); + return defaultMidiProtocol; + } + } + + Log.d(TAG, "Cannot find interface with both input and output endpoints"); + return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS; + } + + private boolean openLocked() { + UsbManager manager = mContext.getSystemService(UsbManager.class); + + mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size()); + mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size()); + mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size()); + + for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) { + ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>(); + ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>(); + UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex); + for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints(); + endpointIndex++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(endpointIndex); + // 0 is output, 1 << 7 is input. + if (endpoint.getDirection() == 0) { + outputEndpoints.add(endpoint.toAndroid(mParser)); + } else { + inputEndpoints.add(endpoint.toAndroid(mParser)); + } + } + if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) { + UsbDeviceConnection connection = manager.openDevice(mUsbDevice); + connection.setInterface(interfaceDescriptor.toAndroid(mParser)); + connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true); + mUsbDeviceConnections.add(connection); + mInputUsbEndpoints.add(inputEndpoints); + mOutputUsbEndpoints.add(outputEndpoints); + } + } + + mEventSchedulers = new MidiEventScheduler[mNumOutputs]; + + for (int i = 0; i < mNumOutputs; i++) { + MidiEventScheduler scheduler = new MidiEventScheduler(); + mEventSchedulers[i] = scheduler; + mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver()); + } + + final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); + + // Create input thread for each input port of the physical device + int portNumber = 0; + for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size(); + connectionIndex++) { + for (int endpointIndex = 0; + endpointIndex < mInputUsbEndpoints.get(connectionIndex).size(); + endpointIndex++) { + final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex); + final UsbEndpoint epF = mInputUsbEndpoints.get(connectionIndex).get(endpointIndex); + final int portF = portNumber; + + new Thread("UsbUniversalMidiDevice input thread " + portF) { + @Override + public void run() { + byte[] inputBuffer = new byte[epF.getMaxPacketSize()]; + try { + while (true) { + // Record time of event immediately after waking. + long timestamp = System.nanoTime(); + synchronized (mLock) { + if (!mIsOpen) break; + + int nRead = connectionF.bulkTransfer(epF, inputBuffer, + inputBuffer.length, 0); + + // For USB, each 32 bit word of a UMP is + // sent with the least significant byte first. + swapEndiannessPerWord(inputBuffer, inputBuffer.length); + + if (nRead > 0) { + if (DEBUG) { + logByteArray("Input ", inputBuffer, 0, + nRead); + } + outputReceivers[portF].send(inputBuffer, 0, nRead, + timestamp); + } + } + } + } catch (IOException e) { + Log.d(TAG, "reader thread exiting"); + } + Log.d(TAG, "input thread exit"); + } + }.start(); + + portNumber++; + } + } + + // Create output thread for each output port of the physical device + portNumber = 0; + for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size(); + connectionIndex++) { + for (int endpointIndex = 0; + endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size(); + endpointIndex++) { + final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex); + final UsbEndpoint epF = + mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex); + final int portF = portNumber; + final MidiEventScheduler eventSchedulerF = mEventSchedulers[portF]; + + new Thread("UsbUniversalMidiDevice output thread " + portF) { + @Override + public void run() { + while (true) { + MidiEvent event; + try { + event = (MidiEvent) eventSchedulerF.waitNextEvent(); + } catch (InterruptedException e) { + // try again + continue; + } + if (event == null) { + break; + } + + // For USB, each 32 bit word of a UMP is + // sent with the least significant byte first. + swapEndiannessPerWord(event.data, event.count); + + if (DEBUG) { + logByteArray("Output ", event.data, 0, + event.count); + } + connectionF.bulkTransfer(epF, event.data, event.count, 0); + eventSchedulerF.addEventToPool(event); + } + Log.d(TAG, "output thread exit"); + } + }.start(); + + portNumber++; + } + } + + mIsOpen = true; + return true; + } + + private boolean register(Context context) { + mContext = context; + MidiManager midiManager = context.getSystemService(MidiManager.class); + if (midiManager == null) { + Log.e(TAG, "No MidiManager in UsbUniversalMidiDevice.create()"); + return false; + } + + mDefaultMidiProtocol = calculateDefaultMidiProtocol(); + + Bundle properties = new Bundle(); + String manufacturer = mUsbDevice.getManufacturerName(); + String product = mUsbDevice.getProductName(); + String version = mUsbDevice.getVersion(); + String name; + if (manufacturer == null || manufacturer.isEmpty()) { + name = product; + } else if (product == null || product.isEmpty()) { + name = manufacturer; + } else { + name = manufacturer + " " + product + " MIDI 2.0"; + } + properties.putString(MidiDeviceInfo.PROPERTY_NAME, name); + properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer); + properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product); + properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version); + properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, + mUsbDevice.getSerialNumber()); + properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, mUsbDevice); + + mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs, + null, null, properties, MidiDeviceInfo.TYPE_USB, mDefaultMidiProtocol, mCallback); + if (mServer == null) { + return false; + } + + return true; + } + + @Override + public void close() throws IOException { + synchronized (mLock) { + if (mIsOpen) { + closeLocked(); + } + } + + if (mServer != null) { + IoUtils.closeQuietly(mServer); + } + } + + private void closeLocked() { + for (int i = 0; i < mEventSchedulers.length; i++) { + mMidiInputPortReceivers[i].setReceiver(null); + mEventSchedulers[i].close(); + } + for (UsbDeviceConnection connection : mUsbDeviceConnections) { + connection.close(); + } + mUsbDeviceConnections = null; + mInputUsbEndpoints = null; + mOutputUsbEndpoints = null; + + mIsOpen = false; + } + + private void swapEndiannessPerWord(byte[] array, int size) { + for (int i = 0; i + 3 < size; i += 4) { + byte tmp = array[i]; + array[i] = array[i + 3]; + array[i + 3] = tmp; + tmp = array[i + 1]; + array[i + 1] = array[i + 2]; + array[i + 2] = tmp; + } + } + + private static void logByteArray(String prefix, byte[] value, int offset, int count) { + StringBuilder builder = new StringBuilder(prefix); + for (int i = offset; i < offset + count; i++) { + builder.append(String.format("0x%02X", value[i])); + if (i != value.length - 1) { + builder.append(", "); + } + } + Log.d(TAG, builder.toString()); + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java index 409e605c3c2f..bfcf62147f75 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java @@ -38,8 +38,8 @@ public class UsbACAudioControlEndpoint extends UsbACEndpoint { static final byte ATTRIBSMASK_SYNC = 0x0C; static final byte ATTRIBMASK_TRANS = 0x03; - public UsbACAudioControlEndpoint(int length, byte type, int subclass) { - super(length, type, subclass); + public UsbACAudioControlEndpoint(int length, byte type, int subclass, byte subtype) { + super(length, type, subclass, subtype); } public byte getAddress() { diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java index e63bb74abdf7..ae9ca0d827f5 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java @@ -24,8 +24,8 @@ public class UsbACAudioStreamEndpoint extends UsbACEndpoint { private static final String TAG = "UsbACAudioStreamEndpoint"; //TODO data fields... - public UsbACAudioStreamEndpoint(int length, byte type, int subclass) { - super(length, type, subclass); + public UsbACAudioStreamEndpoint(int length, byte type, int subclass, byte subtype) { + super(length, type, subclass, subtype); } @Override diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java index ff7f3934086c..b7f9ac334954 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java @@ -25,13 +25,17 @@ import android.util.Log; abstract class UsbACEndpoint extends UsbDescriptor { private static final String TAG = "UsbACEndpoint"; + public static final byte MS_GENERAL = 1; + public static final byte MS_GENERAL_2_0 = 2; + protected final int mSubclass; // from the mSubclass member of the "enclosing" // Interface Descriptor, not the stream. - protected byte mSubtype; // 2:1 HEADER descriptor subtype + protected final byte mSubtype; // 2:1 HEADER descriptor subtype - UsbACEndpoint(int length, byte type, int subclass) { + UsbACEndpoint(int length, byte type, int subclass, byte subtype) { super(length, type); mSubclass = subclass; + mSubtype = subtype; } public int getSubclass() { @@ -44,33 +48,39 @@ abstract class UsbACEndpoint extends UsbDescriptor { @Override public int parseRawDescriptors(ByteStream stream) { - mSubtype = stream.getByte(); return mLength; } public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, - int length, byte type) { + int length, byte type, byte subType) { UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); int subClass = interfaceDesc.getUsbSubclass(); - // TODO shouldn't this switch on subtype? switch (subClass) { case AUDIO_AUDIOCONTROL: if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "---> AUDIO_AUDIOCONTROL"); } - return new UsbACAudioControlEndpoint(length, type, subClass); + return new UsbACAudioControlEndpoint(length, type, subClass, subType); case AUDIO_AUDIOSTREAMING: if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "---> AUDIO_AUDIOSTREAMING"); } - return new UsbACAudioStreamEndpoint(length, type, subClass); + return new UsbACAudioStreamEndpoint(length, type, subClass, subType); case AUDIO_MIDISTREAMING: if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "---> AUDIO_MIDISTREAMING"); } - return new UsbACMidiEndpoint(length, type, subClass); + switch (subType) { + case MS_GENERAL: + return new UsbACMidi10Endpoint(length, type, subClass, subType); + case MS_GENERAL_2_0: + return new UsbACMidi20Endpoint(length, type, subClass, subType); + default: + Log.w(TAG, "Unknown Midi Endpoint id:0x" + Integer.toHexString(subType)); + return null; + } default: Log.w(TAG, "Unknown Audio Class Endpoint id:0x" + Integer.toHexString(subClass)); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java index 42ee88922edd..49b9d7b30d29 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java @@ -22,14 +22,14 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * An audio class-specific Midi Endpoint. * see midi10.pdf section 6.2.2 */ -public final class UsbACMidiEndpoint extends UsbACEndpoint { - private static final String TAG = "UsbACMidiEndpoint"; +public final class UsbACMidi10Endpoint extends UsbACEndpoint { + private static final String TAG = "UsbACMidi10Endpoint"; private byte mNumJacks; - private byte[] mJackIds; + private byte[] mJackIds = new byte[0]; - public UsbACMidiEndpoint(int length, byte type, int subclass) { - super(length, type, subclass); + public UsbACMidi10Endpoint(int length, byte type, int subclass, byte subtype) { + super(length, type, subclass, subtype); } public byte getNumJacks() { @@ -45,9 +45,11 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint { super.parseRawDescriptors(stream); mNumJacks = stream.getByte(); - mJackIds = new byte[mNumJacks]; - for (int jack = 0; jack < mNumJacks; jack++) { - mJackIds[jack] = stream.getByte(); + if (mNumJacks > 0) { + mJackIds = new byte[mNumJacks]; + for (int jack = 0; jack < mNumJacks; jack++) { + mJackIds[jack] = stream.getByte(); + } } return mLength; } @@ -56,10 +58,10 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint { public void report(ReportCanvas canvas) { super.report(canvas); - canvas.writeHeader(3, "AC Midi Endpoint: " + ReportCanvas.getHexString(getType()) + canvas.writeHeader(3, "ACMidi10Endpoint: " + ReportCanvas.getHexString(getType()) + " Length: " + getLength()); canvas.openList(); canvas.writeListItem("" + getNumJacks() + " Jacks."); canvas.closeList(); } -}
\ No newline at end of file +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java new file mode 100644 index 000000000000..1024a5bf55a6 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usb.descriptors; + +import com.android.server.usb.descriptors.report.ReportCanvas; + +/** + * @hide + * An audio class-specific Midi Endpoint. + * see midi10.pdf section 6.2.2 + */ +public final class UsbACMidi20Endpoint extends UsbACEndpoint { + private static final String TAG = "UsbACMidi20Endpoint"; + + private byte mNumGroupTerminals; + private byte[] mBlockIds = new byte[0]; + + public UsbACMidi20Endpoint(int length, byte type, int subclass, byte subtype) { + super(length, type, subclass, subtype); + } + + public byte getNumGroupTerminals() { + return mNumGroupTerminals; + } + + public byte[] getBlockIds() { + return mBlockIds; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + super.parseRawDescriptors(stream); + + mNumGroupTerminals = stream.getByte(); + if (mNumGroupTerminals > 0) { + mBlockIds = new byte[mNumGroupTerminals]; + for (int block = 0; block < mNumGroupTerminals; block++) { + mBlockIds[block] = stream.getByte(); + } + } + return mLength; + } + + @Override + public void report(ReportCanvas canvas) { + super.report(canvas); + + canvas.writeHeader(3, "AC Midi20 Endpoint: " + ReportCanvas.getHexString(getType()) + + " Length: " + getLength()); + canvas.openList(); + canvas.writeListItem("" + getNumGroupTerminals() + " Group Terminals."); + canvas.closeList(); + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index 7250a071835d..3412a6f80cc7 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -30,6 +30,9 @@ public final class UsbDescriptorParser { private final String mDeviceAddr; + private static final int MS_MIDI_1_0 = 0x0100; + private static final int MS_MIDI_2_0 = 0x0200; + // Descriptor Objects private static final int DESCRIPTORS_ALLOC_SIZE = 128; private final ArrayList<UsbDescriptor> mDescriptors; @@ -215,6 +218,7 @@ public final class UsbDescriptorParser { Log.w(TAG, " Unparsed Class-specific"); break; } + mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor); } break; @@ -222,17 +226,25 @@ public final class UsbDescriptorParser { if (mCurInterfaceDescriptor != null) { int subClass = mCurInterfaceDescriptor.getUsbClass(); switch (subClass) { - case UsbDescriptor.CLASSID_AUDIO: - descriptor = UsbACEndpoint.allocDescriptor(this, length, type); + case UsbDescriptor.CLASSID_AUDIO: { + Byte subType = stream.getByte(); + if (DEBUG) { + Log.d(TAG, "UsbDescriptor.CLASSID_AUDIO type:0x" + + Integer.toHexString(type)); + } + descriptor = UsbACEndpoint.allocDescriptor(this, length, type, + subType); + } break; case UsbDescriptor.CLASSID_VIDEO: { - Byte subtype = stream.getByte(); + Byte subType = stream.getByte(); if (DEBUG) { Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO type:0x" + Integer.toHexString(type)); } - descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, subtype); + descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, + subType); } break; @@ -644,8 +656,8 @@ public final class UsbDescriptorParser { for (UsbDescriptor descriptor : descriptors) { // enusure that this isn't an unrecognized interface descriptor if (descriptor instanceof UsbInterfaceDescriptor) { - UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor; - if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; + if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { return true; } } else { @@ -656,17 +668,90 @@ public final class UsbDescriptorParser { return false; } - private int calculateNumMidiPorts(boolean isOutput) { + /** + * @hide + */ + public boolean containsUniversalMidiDeviceEndpoint() { + ArrayList<UsbInterfaceDescriptor> interfaceDescriptors = + findUniversalMidiInterfaceDescriptors(); + int outputCount = 0; + int inputCount = 0; + for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size(); + interfaceIndex++) { + UsbInterfaceDescriptor interfaceDescriptor = interfaceDescriptors.get(interfaceIndex); + for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints(); + endpointIndex++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(endpointIndex); + // 0 is output, 1 << 7 is input. + if (endpoint.getDirection() == 0) { + outputCount++; + } else { + inputCount++; + } + } + } + return (outputCount > 0) || (inputCount > 0); + } + + /** + * @hide + */ + public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() { int count = 0; ArrayList<UsbDescriptor> descriptors = getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); + ArrayList<UsbInterfaceDescriptor> universalMidiInterfaces = + new ArrayList<UsbInterfaceDescriptor>(); + for (UsbDescriptor descriptor : descriptors) { // ensure that this isn't an unrecognized interface descriptor if (descriptor instanceof UsbInterfaceDescriptor) { - UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor; - if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { - for (int i = 0; i < interfaceDescr.getNumEndpoints(); i++) { - UsbEndpointDescriptor endpoint = interfaceDescr.getEndpointDescriptor(i); + UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; + if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + UsbDescriptor classSpecificDescriptor = + interfaceDescriptor.getClassSpecificInterfaceDescriptor(); + if (classSpecificDescriptor != null) { + if (classSpecificDescriptor instanceof UsbMSMidiHeader) { + UsbMSMidiHeader midiHeader = + (UsbMSMidiHeader) classSpecificDescriptor; + if (midiHeader.getMidiStreamingClass() == MS_MIDI_2_0) { + universalMidiInterfaces.add(interfaceDescriptor); + } + } + } + } + } else { + Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + return universalMidiInterfaces; + } + + private int calculateNumLegacyMidiPorts(boolean isOutput) { + int count = 0; + ArrayList<UsbDescriptor> descriptors = + getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); + for (UsbDescriptor descriptor : descriptors) { + // ensure that this isn't an unrecognized interface descriptor + if (descriptor instanceof UsbInterfaceDescriptor) { + UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; + if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + UsbDescriptor classSpecificDescriptor = + interfaceDescriptor.getClassSpecificInterfaceDescriptor(); + if (classSpecificDescriptor != null) { + if (classSpecificDescriptor instanceof UsbMSMidiHeader) { + UsbMSMidiHeader midiHeader = + (UsbMSMidiHeader) classSpecificDescriptor; + if (midiHeader.getMidiStreamingClass() != MS_MIDI_1_0) { + continue; + } + } + } + for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(i); // 0 is output, 1 << 7 is input. if ((endpoint.getDirection() == 0) == isOutput) { count++; @@ -684,15 +769,15 @@ public final class UsbDescriptorParser { /** * @hide */ - public int calculateNumMidiInputs() { - return calculateNumMidiPorts(false /*isOutput*/); + public int calculateNumLegacyMidiInputs() { + return calculateNumLegacyMidiPorts(false /*isOutput*/); } /** * @hide */ - public int calculateNumMidiOutputs() { - return calculateNumMidiPorts(true /*isOutput*/); + public int calculateNumLegacyMidiOutputs() { + return calculateNumLegacyMidiPorts(true /*isOutput*/); } /** diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java index 4d0cfea98630..ab07ce7fdb7a 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -112,7 +112,10 @@ public class UsbEndpointDescriptor extends UsbDescriptor { return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION; } - /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) { + /** + * Returns a UsbEndpoint that this UsbEndpointDescriptor is describing. + */ + public UsbEndpoint toAndroid(UsbDescriptorParser parser) { if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid() type:" + Integer.toHexString(mAttributes & MASK_ATTRIBS_TRANSTYPE) diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java index 64dbd971cc67..ca4613b17873 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java @@ -42,6 +42,8 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors = new ArrayList<UsbEndpointDescriptor>(); + private UsbDescriptor mClassSpecificInterfaceDescriptor; + UsbInterfaceDescriptor(int length, byte type) { super(length, type); mHierarchyLevel = 3; @@ -105,7 +107,18 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { mEndpointDescriptors.add(endpoint); } - UsbInterface toAndroid(UsbDescriptorParser parser) { + public void setClassSpecificInterfaceDescriptor(UsbDescriptor descriptor) { + mClassSpecificInterfaceDescriptor = descriptor; + } + + public UsbDescriptor getClassSpecificInterfaceDescriptor() { + return mClassSpecificInterfaceDescriptor; + } + + /** + * Returns a UsbInterface that this UsbInterfaceDescriptor is describing. + */ + public UsbInterface toAndroid(UsbDescriptorParser parser) { if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid() class:" + Integer.toHexString(mUsbClass) + " subclass:" + Integer.toHexString(mUsbSubclass) diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java index d0ca6db87d38..76535612f54d 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java @@ -25,13 +25,19 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbMSMidiHeader extends UsbACInterface { private static final String TAG = "UsbMSMidiHeader"; + private int mMidiStreamingClass; // MSC Specification Release (BCD). + public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } + public int getMidiStreamingClass() { + return mMidiStreamingClass; + } + @Override public int parseRawDescriptors(ByteStream stream) { - // TODO - read data memebers + mMidiStreamingClass = stream.unpackUsbShort(); stream.advance(mLength - stream.getReadCount()); return mLength; } @@ -42,6 +48,7 @@ public final class UsbMSMidiHeader extends UsbACInterface { canvas.writeHeader(3, "MS Midi Header: " + ReportCanvas.getHexString(getType()) + " SubType: " + ReportCanvas.getHexString(getSubclass()) - + " Length: " + getLength()); + + " Length: " + getLength() + + " MidiStreamingClass :" + getMidiStreamingClass()); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java new file mode 100644 index 000000000000..37bd0f8f6f7b --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usb.descriptors; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDeviceConnection; +import android.util.Log; + +import java.util.ArrayList; + +/** + * @hide + * A class to parse Block Descriptors + * see midi20.pdf section 5.4 + */ +public class UsbMidiBlockParser { + private static final String TAG = "UsbMidiBlockParser"; + + // Block header size + public static final int MIDI_BLOCK_HEADER_SIZE = 5; + public static final int MIDI_BLOCK_SIZE = 13; + public static final int REQ_GET_DESCRIPTOR = 0x06; + public static final int CS_GR_TRM_BLOCK = 0x26; // Class-specific GR_TRM_BLK + public static final int GR_TRM_BLOCK_HEADER = 0x01; // Group block header + public static final int REQ_TIMEOUT_MS = 2000; // 2 second timeout + public static final int DEFAULT_MIDI_TYPE = 1; // Default MIDI type + + protected int mHeaderLength; // 0:1 Size of header descriptor + protected int mHeaderDescriptorType; // 1:1 Descriptor Type + protected int mHeaderDescriptorSubtype; // 2:1 Descriptor Subtype + protected int mTotalLength; // 3:2 Total Length of header and blocks + + static class GroupTerminalBlock { + protected int mLength; // 0:1 Size of descriptor + protected int mDescriptorType; // 1:1 Descriptor Type + protected int mDescriptorSubtype; // 2:1 Descriptor Subtype + protected int mGroupBlockId; // 3:1 Id of Group Terminal Block + protected int mGroupTerminalBlockType; // 4:1 bi-directional, IN, or OUT + protected int mGroupTerminal; // 5:1 Group Terminal Number + protected int mNumGroupTerminals; // 6:1 Number of Group Terminals + protected int mBlockItem; // 7:1 ID of STRING descriptor of Block item + protected int mMidiProtocol; // 8:1 MIDI protocol + protected int mMaxInputBandwidth; // 9:2 Max Input Bandwidth + protected int mMaxOutputBandwidth; // 11:2 Max Output Bandwidth + + public int parseRawDescriptors(ByteStream stream) { + mLength = stream.getUnsignedByte(); + mDescriptorType = stream.getUnsignedByte(); + mDescriptorSubtype = stream.getUnsignedByte(); + mGroupBlockId = stream.getUnsignedByte(); + mGroupTerminalBlockType = stream.getUnsignedByte(); + mGroupTerminal = stream.getUnsignedByte(); + mNumGroupTerminals = stream.getUnsignedByte(); + mBlockItem = stream.getUnsignedByte(); + mMidiProtocol = stream.getUnsignedByte(); + mMaxInputBandwidth = stream.unpackUsbShort(); + mMaxOutputBandwidth = stream.unpackUsbShort(); + return mLength; + } + } + + private ArrayList<GroupTerminalBlock> mGroupTerminalBlocks = + new ArrayList<GroupTerminalBlock>(); + + public UsbMidiBlockParser() { + } + + /** + * Parses a raw ByteStream into a block terminal descriptor. + * The header is parsed before each block is parsed. + * @param stream ByteStream to parse + * @return The total length that has been parsed. + */ + public int parseRawDescriptors(ByteStream stream) { + mHeaderLength = stream.getUnsignedByte(); + mHeaderDescriptorType = stream.getUnsignedByte(); + mHeaderDescriptorSubtype = stream.getUnsignedByte(); + mTotalLength = stream.unpackUsbShort(); + + while (stream.available() >= MIDI_BLOCK_SIZE) { + GroupTerminalBlock block = new GroupTerminalBlock(); + block.parseRawDescriptors(stream); + mGroupTerminalBlocks.add(block); + } + + return mTotalLength; + } + + /** + * Calculates the MIDI type through querying the device twice, once for the size + * of the block descriptor and once for the block descriptor. This descriptor is + * then parsed to return the MIDI type. + * See the MIDI 2.0 USB doc for more info. + * @param connection UsbDeviceConnection to send the request + * @param interfaceNumber The interface number to query + * @param alternateInterfaceNumber The alternate interface of the interface + * @return The MIDI type as an int. + */ + public int calculateMidiType(UsbDeviceConnection connection, int interfaceNumber, + int alternateInterfaceNumber) { + byte[] byteArray = new byte[MIDI_BLOCK_HEADER_SIZE]; + try { + // This first request is simply to get the full size of the descriptor. + // This info is stored in the last two bytes of the header. + int rdo = connection.controlTransfer( + UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD + | UsbConstants.USB_CLASS_AUDIO, + REQ_GET_DESCRIPTOR, + (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber, + interfaceNumber, + byteArray, + MIDI_BLOCK_HEADER_SIZE, + REQ_TIMEOUT_MS); + if (rdo > 0) { + if (byteArray[1] != CS_GR_TRM_BLOCK) { + Log.e(TAG, "Incorrect descriptor type: " + byteArray[1]); + return DEFAULT_MIDI_TYPE; + } + if (byteArray[2] != GR_TRM_BLOCK_HEADER) { + Log.e(TAG, "Incorrect descriptor subtype: " + byteArray[2]); + return DEFAULT_MIDI_TYPE; + } + int newSize = (((int) byteArray[3]) & (0xff)) + + ((((int) byteArray[4]) & (0xff)) << 8); + if (newSize <= 0) { + Log.e(TAG, "Parsed a non-positive block terminal size: " + newSize); + return DEFAULT_MIDI_TYPE; + } + byteArray = new byte[newSize]; + rdo = connection.controlTransfer( + UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD + | UsbConstants.USB_CLASS_AUDIO, + REQ_GET_DESCRIPTOR, + (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber, + interfaceNumber, + byteArray, + newSize, + REQ_TIMEOUT_MS); + if (rdo > 0) { + ByteStream stream = new ByteStream(byteArray); + parseRawDescriptors(stream); + if (mGroupTerminalBlocks.isEmpty()) { + Log.e(TAG, "Group Terminal Blocks failed parsing: " + DEFAULT_MIDI_TYPE); + return DEFAULT_MIDI_TYPE; + } else { + Log.d(TAG, "MIDI protocol: " + mGroupTerminalBlocks.get(0).mMidiProtocol); + return mGroupTerminalBlocks.get(0).mMidiProtocol; + } + } else { + Log.e(TAG, "second transfer failed: " + rdo); + } + } else { + Log.e(TAG, "first transfer failed: " + rdo); + } + } catch (Exception e) { + Log.e(TAG, "Can not communicate with USB device", e); + } + return DEFAULT_MIDI_TYPE; + } +} diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index a75f79caeee0..b9936ce2e1b2 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -342,6 +342,8 @@ interface ITelecomService { void cleanupStuckCalls(); + int cleanupOrphanPhoneAccounts(); + void resetCarMode(); void setTestDefaultCallRedirectionApp(String packageName); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 808df50466a3..21967f4f4687 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2486,6 +2486,10 @@ public class CarrierConfigManager { * * Note: If {@code *} is specified for the original code, any ImsReasonInfo with the matching * {@code MESSAGE} will be remapped to {@code NEW_CODE}. + * If {@code *} is specified for the message, any ImsReasonInfo with the matching + * {@code ORIGINAL_CODE} will be remapped to {@code NEW_CODE}. + * The wildcard for {@code ORIGINAL_CODE} takes precedence to the wildcard for {@code MESSAGE}. + * A mapping with both wildcards has no effect. * * Example: "501|call completion elsewhere|1014" * When the {@link ImsReasonInfo#getCode()} is {@link ImsReasonInfo#CODE_USER_TERMINATED} and @@ -2935,19 +2939,6 @@ public class CarrierConfigManager { "signal_strength_nr_nsa_use_lte_as_primary_bool"; /** - * String array of TCP buffer sizes per network type. - * The entries should be of the following form, with values in bytes: - * "network_name:read_min,read_default,read_max,write_min,write_default,write_max". - * For NR (5G), the following network names should be used: - * - NR_NSA: NR NSA, sub-6 frequencies - * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies - * - NR_SA: NR SA, sub-6 frequencies - * - NR_SA_MMWAVE: NR SA, mmwave frequencies - * @hide - */ - public static final String KEY_TCP_BUFFERS_STRING_ARRAY = "tcp_buffers_string_array"; - - /** * String array of default bandwidth values per network type. * The entries should be of form: "network_name:downlink,uplink", with values in Kbps. * For NR (5G), the following network names should be used: @@ -7426,7 +7417,7 @@ public class CarrierConfigManager { /** * A priority list of ePDG addresses to be used. Possible values are {@link * #EPDG_ADDRESS_STATIC}, {@link #EPDG_ADDRESS_PLMN}, {@link #EPDG_ADDRESS_PCO}, {@link - * #EPDG_ADDRESS_CELLULAR_LOC} + * #EPDG_ADDRESS_CELLULAR_LOC}, {@link #EPDG_ADDRESS_VISITED_COUNTRY} */ public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = KEY_PREFIX + "epdg_address_priority_int_array"; @@ -7605,7 +7596,8 @@ public class CarrierConfigManager { EPDG_ADDRESS_STATIC, EPDG_ADDRESS_PLMN, EPDG_ADDRESS_PCO, - EPDG_ADDRESS_CELLULAR_LOC + EPDG_ADDRESS_CELLULAR_LOC, + EPDG_ADDRESS_VISITED_COUNTRY }) public @interface EpdgAddressType {} @@ -7619,6 +7611,8 @@ public class CarrierConfigManager { public static final int EPDG_ADDRESS_PCO = 2; /** Use cellular location to chose epdg server */ public static final int EPDG_ADDRESS_CELLULAR_LOC = 3; + /* Use Visited Country FQDN rule*/ + public static final int EPDG_ADDRESS_VISITED_COUNTRY = 4; /** @hide */ @IntDef({ID_TYPE_FQDN, ID_TYPE_RFC822_ADDR, ID_TYPE_KEY_ID}) @@ -8570,28 +8564,6 @@ public class CarrierConfigManager { "iDEN:14,14", "LTE:30000,15000", "HSPA+:13000,3400", "GSM:24,24", "TD_SCDMA:115,115", "LTE_CA:30000,15000", "NR_NSA:47000,18000", "NR_NSA_MMWAVE:145000,60000", "NR_SA:145000,60000", "NR_SA_MMWAVE:145000,60000"}); - sDefaults.putStringArray(KEY_TCP_BUFFERS_STRING_ARRAY, new String[]{ - "GPRS:4092,8760,48000,4096,8760,48000", "EDGE:4093,26280,70800,4096,16384,70800", - "UMTS:58254,349525,1048576,58254,349525,1048576", - "CDMA:4094,87380,262144,4096,16384,262144", - "1xRTT:16384,32768,131072,4096,16384,102400", - "EvDo_0:4094,87380,262144,4096,16384,262144", - "EvDo_A:4094,87380,262144,4096,16384,262144", - "HSDPA:61167,367002,1101005,8738,52429,262114", - "HSUPA:40778,244668,734003,16777,100663,301990", - "HSPA:40778,244668,734003,16777,100663,301990", - "EvDo_B:4094,87380,262144,4096,16384,262144", - "eHRPD:131072,262144,1048576,4096,16384,524288", - "iDEN:4094,87380,262144,4096,16384,262144", - "LTE:524288,1048576,2097152,262144,524288,1048576", - "HSPA+:122334,734003,2202010,32040,192239,576717", - "GSM:4092,8760,48000,4096,8760,48000", - "TD_SCDMA:58254,349525,1048576,58254,349525,1048576", - "LTE_CA:4096,6291456,12582912,4096,1048576,2097152", - "NR_NSA:2097152,6291456,16777216,512000,2097152,8388608", - "NR_NSA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608", - "NR_SA:2097152,6291456,16777216,512000,2097152,8388608", - "NR_SA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608"}); sDefaults.putBoolean(KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPLINK_BOOL, false); sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, "rssi"); sDefaults.putBoolean(KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL, false); @@ -8678,10 +8650,10 @@ public class CarrierConfigManager { /* Default value is 2 seconds. */ sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_5G_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 2000); sDefaults.putBoolean(KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL, true); - sDefaults.putInt(KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG, 60000); - sDefaults.putInt( + sDefaults.putLong(KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG, 60000L); + sDefaults.putLong( KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG, - 120000); + 120000L); sDefaults.putAll(ImsServiceEntitlement.getDefaults()); sDefaults.putAll(Gps.getDefaults()); sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY, diff --git a/telephony/java/android/telephony/PcoData.java b/telephony/java/android/telephony/PcoData.java index bcfbcf8bf5fa..39e4f2f799d8 100644 --- a/telephony/java/android/telephony/PcoData.java +++ b/telephony/java/android/telephony/PcoData.java @@ -19,6 +19,9 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; +import java.util.Arrays; +import java.util.Objects; + /** * Contains Carrier-specific (and opaque) Protocol configuration Option * Data. In general this is only passed on to carrier-specific applications @@ -84,4 +87,22 @@ public class PcoData implements Parcelable { return "PcoData(" + cid + ", " + bearerProto + ", " + pcoId + ", contents[" + contents.length + "])"; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PcoData pcoData = (PcoData) o; + return cid == pcoData.cid + && pcoId == pcoData.pcoId + && Objects.equals(bearerProto, pcoData.bearerProto) + && Arrays.equals(contents, pcoData.contents); + } + + @Override + public int hashCode() { + int result = Objects.hash(cid, bearerProto, pcoId); + result = 31 * result + Arrays.hashCode(contents); + return result; + } } diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 54fb65cea617..cbd03c7f653a 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -2548,6 +2548,11 @@ public final class SmsManager { */ public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; + /** + * A RIL error occurred during the SMS send. + */ + public static final int RESULT_RIL_GENERIC_ERROR = 124; + // SMS receiving results sent as a "result" extra in {@link Intents.SMS_REJECTED_ACTION} /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f5505e6390ff..536517c12313 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -142,6 +142,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; @@ -6807,6 +6808,24 @@ public class TelephonyManager { } /** + * Get the first active portIndex from the corresponding physical slot index. + * @param physicalSlotIndex physical slot index + * @return first active port index or INVALID_PORT_INDEX if no port is active + */ + private int getFirstActivePortIndex(int physicalSlotIndex) { + UiccSlotInfo[] slotInfos = getUiccSlotsInfo(); + if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length + && slotInfos[physicalSlotIndex] != null) { + Optional<UiccPortInfo> result = slotInfos[physicalSlotIndex].getPorts().stream() + .filter(portInfo -> portInfo.isActive()).findFirst(); + if (result.isPresent()) { + return result.get().getPortIndex(); + } + } + return INVALID_PORT_INDEX; + } + + /** * Opens a logical channel to the ICC card. * * Input parameters equivalent to TS 27.007 AT+CCHO command. @@ -6852,7 +6871,8 @@ public class TelephonyManager { * @param p2 P2 parameter (described in ISO 7816-4). * @return an IccOpenLogicalChannelResponse object. * @hide - * @deprecated instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)} + * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP), + * instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)} */ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) @@ -6866,6 +6886,7 @@ public class TelephonyManager { if (telephony != null) { IccLogicalChannelRequest request = new IccLogicalChannelRequest(); request.slotIndex = slotIndex; + request.portIndex = getFirstActivePortIndex(slotIndex); request.aid = aid; request.p2 = p2; request.callingPackage = getOpPackageName(); @@ -7021,7 +7042,8 @@ public class TelephonyManager { * iccOpenLogicalChannel. * @return true if the channel was closed successfully. * @hide - * @deprecated instead use {@link #iccCloseLogicalChannelByPort(int, int, int)} + * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP), + * instead use {@link #iccCloseLogicalChannelByPort(int, int, int)} */ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) @@ -7033,6 +7055,7 @@ public class TelephonyManager { if (telephony != null) { IccLogicalChannelRequest request = new IccLogicalChannelRequest(); request.slotIndex = slotIndex; + request.portIndex = getFirstActivePortIndex(slotIndex); request.channel = channel; return telephony.iccCloseLogicalChannel(request); } @@ -7153,7 +7176,8 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at the end, or null if * there is an issue connecting to the Telephony service. * @hide - * @deprecated instead use + * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP), + * instead use * {@link #iccTransmitApduLogicalChannelByPort(int, int, int, int, int, int, int, int, String)} */ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @@ -7166,8 +7190,9 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.iccTransmitApduLogicalChannelByPort(slotIndex, DEFAULT_PORT_INDEX, - channel, cla, instruction, p1, p2, p3, data); + return telephony.iccTransmitApduLogicalChannelByPort(slotIndex, + getFirstActivePortIndex(slotIndex), channel, cla, instruction, + p1, p2, p3, data); } } catch (RemoteException ex) { } catch (NullPointerException ex) { @@ -7305,7 +7330,8 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide - * @deprecated instead use + * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP), + * instead use * {@link #iccTransmitApduBasicChannelByPort(int, int, int, int, int, int, int, String)} */ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @@ -7318,8 +7344,9 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.iccTransmitApduBasicChannelByPort(slotIndex, DEFAULT_PORT_INDEX, - getOpPackageName(), cla, instruction, p1, p2, p3, data); + return telephony.iccTransmitApduBasicChannelByPort(slotIndex, + getFirstActivePortIndex(slotIndex), getOpPackageName(), + cla, instruction, p1, p2, p3, data); } } catch (RemoteException ex) { } catch (NullPointerException ex) { @@ -12588,12 +12615,15 @@ public class TelephonyManager { if (carriers == null || !SubscriptionManager.isValidPhoneId(slotIndex)) { return -1; } - // Execute the method setCarrierRestrictionRules with an empty excluded list and - // indicating priority for the allowed list. + // Execute the method setCarrierRestrictionRules with an empty excluded list. + // If the allowed list is empty, it means that all carriers are allowed (default allowed), + // otherwise it means that only specified carriers are allowed (default not allowed). CarrierRestrictionRules carrierRestrictionRules = CarrierRestrictionRules.newBuilder() .setAllowedCarriers(carriers) .setDefaultCarrierRestriction( - CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED) + carriers.isEmpty() + ? CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED + : CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED) .build(); int result = setCarrierRestrictionRules(carrierRestrictionRules); @@ -15108,6 +15138,15 @@ public class TelephonyManager { public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; /** + * Indicates the call waiting status could not be set or queried because the Fixed Dialing + * Numbers (FDN) feature is enabled. + * + * @hide + */ + @SystemApi + public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; + + /** * @hide */ @IntDef(prefix = { "CALL_WAITING_STATUS_" }, value = { @@ -15115,6 +15154,7 @@ public class TelephonyManager { CALL_WAITING_STATUS_DISABLED, CALL_WAITING_STATUS_UNKNOWN_ERROR, CALL_WAITING_STATUS_NOT_SUPPORTED, + CALL_WAITING_STATUS_FDN_CHECK_FAILURE, }) @Retention(RetentionPolicy.SOURCE) public @interface CallWaitingStatus { @@ -15135,6 +15175,7 @@ public class TelephonyManager { * <li>{@link #CALL_WAITING_STATUS_DISABLED}}</li> * <li>{@link #CALL_WAITING_STATUS_UNKNOWN_ERROR}}</li> * <li>{@link #CALL_WAITING_STATUS_NOT_SUPPORTED}}</li> + * <li>{@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE}}</li> * </ul> * @hide */ @@ -15184,7 +15225,8 @@ public class TelephonyManager { * {@link #CALL_WAITING_STATUS_ENABLED} or * {@link #CALL_WAITING_STATUS_DISABLED} if the operation succeeded and * {@link #CALL_WAITING_STATUS_NOT_SUPPORTED} or - * {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} if it failed. + * {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} or + * {@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE} if it failed. * @hide */ @SystemApi diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java index 464389b84945..30ca1627953f 100644 --- a/telephony/java/android/telephony/UiccCardInfo.java +++ b/telephony/java/android/telephony/UiccCardInfo.java @@ -257,8 +257,6 @@ public final class UiccCardInfo implements Parcelable { + mCardId + ", mEid=" + mEid - + ", mIccId=" - + SubscriptionInfo.givePrintableIccid(getIccId()) + ", mPhysicalSlotIndex=" + mPhysicalSlotIndex + ", mIsRemovable=" diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java index 2b1c8c863eb6..17f34db4e44a 100644 --- a/telephony/java/android/telephony/UiccSlotInfo.java +++ b/telephony/java/android/telephony/UiccSlotInfo.java @@ -271,16 +271,13 @@ public class UiccSlotInfo implements Parcelable { @NonNull @Override public String toString() { - return "UiccSlotInfo (mIsActive=" - + mIsActive + return "UiccSlotInfo (" + ", mIsEuicc=" + mIsEuicc + ", mCardId=" + mCardId + ", cardState=" + mCardStateInfo - + ", phoneId=" - + mLogicalSlotIdx + ", mIsExtendedApduSupported=" + mIsExtendedApduSupported + ", mIsRemovable=" diff --git a/telephony/java/android/telephony/UiccSlotMapping.java b/telephony/java/android/telephony/UiccSlotMapping.java index 87e7acdc792d..08de7fdc7029 100644 --- a/telephony/java/android/telephony/UiccSlotMapping.java +++ b/telephony/java/android/telephony/UiccSlotMapping.java @@ -94,7 +94,6 @@ public final class UiccSlotMapping implements Parcelable { * @param physicalSlotIndex is unique index referring to a physical SIM slot. * @param logicalSlotIndex is unique index referring to a logical SIM slot. * - * @hide */ public UiccSlotMapping(int portIndex, int physicalSlotIndex, int logicalSlotIndex) { this.mPortIndex = portIndex; diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 4ff59b568657..acbd64b57773 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -118,11 +118,9 @@ public class ApnSetting implements Parcelable { public static final int TYPE_VSIM = 1 << 12; // TODO: Refer to ApnTypes.VSIM /** APN type for BIP. */ public static final int TYPE_BIP = 1 << 13; // TODO: Refer to ApnTypes.BIP - /** - * APN type for ENTERPRISE. - * @hide - */ - public static final int TYPE_ENTERPRISE = TYPE_BIP << 1; + /** APN type for ENTERPRISE. */ + public static final int TYPE_ENTERPRISE = 1 << 14; //TODO: In future should be referenced from + // hardware.interfaces.radio.data.ApnTypes /** @hide */ @IntDef(flag = true, prefix = {"TYPE_"}, value = { @@ -355,6 +353,7 @@ public class ApnSetting implements Parcelable { * modem components or carriers. Non-system apps should use the integer variants instead. * @hide */ + @SystemApi public static final String TYPE_ENTERPRISE_STRING = "enterprise"; diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 051d6c5d5ec0..1ff6ec1779cd 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -253,8 +253,10 @@ public class DataServiceCallback { return "RESULT_ERROR_BUSY"; case RESULT_ERROR_ILLEGAL_STATE: return "RESULT_ERROR_ILLEGAL_STATE"; + case RESULT_ERROR_TEMPORARILY_UNAVAILABLE: + return "RESULT_ERROR_TEMPORARILY_UNAVAILABLE"; default: - return "Missing case for result code=" + resultCode; + return "Unknown(" + resultCode + ")"; } } diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 2e5402c53d73..a49a61b592ba 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -805,6 +805,13 @@ public class EuiccManager { */ public static final int ERROR_OPERATION_BUSY = 10016; + /** + * Failure due to target port is not supported. + * @see #switchToSubscription(int, int, PendingIntent) + */ + public static final int ERROR_INVALID_PORT = 10017; + + private final Context mContext; private int mCardId; @@ -1120,6 +1127,15 @@ public class EuiccManager { * intent to prompt the user to accept the download. The caller should also be authorized to * manage the subscription to be enabled. * + * <p> From Android T, devices might support MEP(Multiple Enabled Profile), the subscription + * can be installed on different port from the eUICC. Calling apps with carrier privilege + * (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently active subscriptions + * can use {@link #switchToSubscription(int, int, PendingIntent)} to specify which port to + * enable the subscription. Otherwise, use this API to enable the subscription on the eUICC + * and the platform will internally resolve a port. If there is no available port, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned in the callback + * intent to prompt the user to disable an already-active subscription. + * * @param subscriptionId the ID of the subscription to enable. May be * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the * current profile without activating another profile to replace it. If it's a disable @@ -1127,12 +1143,7 @@ public class EuiccManager { * permission, or the calling app must be authorized to manage the active subscription on * the target eUICC. * @param callbackIntent a PendingIntent to launch when the operation completes. - * - * @deprecated From T, callers should use - * {@link #switchToSubscription(int, int, PendingIntent)} instead to specify a port - * index on the card to switch to. */ - @Deprecated @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) { if (!isEnabled()) { @@ -1150,20 +1161,19 @@ public class EuiccManager { /** * Switch to (enable) the given subscription. * - * <p>Requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, - * or the calling app must be authorized to manage both the currently-active subscription and - * the subscription to be enabled according to the subscription metadata. Without the former, - * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback - * intent to prompt the user to accept the download. + * <p> Requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, + * or the caller must be having both the carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}) over any currently active subscriptions + * and the subscription to be enabled according to the subscription metadata. + * Without the former permissions, an SecurityException is thrown. * - * <p>On a multi-active SIM device, requires the - * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app - * only if the targeted eUICC does not currently have an active subscription or the calling app - * is authorized to manage the active subscription on the target eUICC, and the calling app is - * authorized to manage any active subscription on any SIM. Without it, an - * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback - * intent to prompt the user to accept the download. The caller should also be authorized to - * manage the subscription to be enabled. + * <p> If the caller is passing invalid port index, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR} with detailed error code + * {@link #ERROR_INVALID_PORT} will be returned. + * + * <p> Depending on the target port and permission check, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned to the callback + * intent to prompt the user to authorize before the switch. * * @param subscriptionId the ID of the subscription to enable. May be * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index 6569de626702..d65286f26447 100755 --- a/telephony/java/android/telephony/ims/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -1212,50 +1212,56 @@ public class ImsCallSession { */ @Override public void callSessionInitiating(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionInitiating( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionInitiating(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionProgressing(ImsStreamMediaProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionProgressing( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionProgressing(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionInitiated(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStarted( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionStarted(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionTerminated(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionTerminated( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionTerminated(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1263,51 +1269,56 @@ public class ImsCallSession { */ @Override public void callSessionHeld(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHeld( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHeld(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionHoldFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionHoldReceived(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldReceived( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHoldReceived(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionResumed(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumed( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionResumed(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionResumeFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumeFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionResumeReceived(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionResumeReceived(ImsCallSession.this, profile), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionResumeReceived(ImsCallSession.this, profile); + } + }, mListenerExecutor); } /** @@ -1330,8 +1341,8 @@ public class ImsCallSession { */ @Override public void callSessionMergeComplete(IImsCallSession newSession) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> { + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { if (newSession != null) { // New session created after conference mListener.callSessionMergeComplete(new ImsCallSession(newSession)); @@ -1339,8 +1350,8 @@ public class ImsCallSession { // Session already exists. Hence no need to pass mListener.callSessionMergeComplete(null); } - }, mListenerExecutor); - } + } + }, mListenerExecutor); } /** @@ -1350,11 +1361,11 @@ public class ImsCallSession { */ @Override public void callSessionMergeFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1362,29 +1373,29 @@ public class ImsCallSession { */ @Override public void callSessionUpdated(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionUpdated(ImsCallSession.this, profile), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionUpdated(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionUpdateReceived(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionUpdateReceived(ImsCallSession.this, profile), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionUpdateReceived(ImsCallSession.this, profile); + } + }, mListenerExecutor); } /** @@ -1393,30 +1404,33 @@ public class ImsCallSession { @Override public void callSessionConferenceExtended(IImsCallSession newSession, ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionConferenceExtended(ImsCallSession.this, - new ImsCallSession(newSession), profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionConferenceExtended(ImsCallSession.this, + new ImsCallSession(newSession), profile); + } + }, mListenerExecutor); } @Override public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionConferenceExtendFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionConferenceExtendFailed( + ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionConferenceExtendReceived(IImsCallSession newSession, ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionConferenceExtendReceived(ImsCallSession.this, - new ImsCallSession(newSession), profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionConferenceExtendReceived(ImsCallSession.this, + new ImsCallSession(newSession), profile); + } + }, mListenerExecutor); } /** @@ -1425,38 +1439,41 @@ public class ImsCallSession { */ @Override public void callSessionInviteParticipantsRequestDelivered() { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionInviteParticipantsRequestDelivered( - ImsCallSession.this), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionInviteParticipantsRequestDelivered( + ImsCallSession.this); + } + }, mListenerExecutor); } @Override public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this, - reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this, + reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionRemoveParticipantsRequestDelivered() { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRemoveParticipantsRequestDelivered( - ImsCallSession.this), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this); + } + }, mListenerExecutor); } @Override public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this, - reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this, + reasonInfo); + } + }, mListenerExecutor); } /** @@ -1464,11 +1481,11 @@ public class ImsCallSession { */ @Override public void callSessionConferenceStateUpdated(ImsConferenceState state) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state); + } + }, mListenerExecutor); } /** @@ -1476,11 +1493,12 @@ public class ImsCallSession { */ @Override public void callSessionUssdMessageReceived(int mode, String ussdMessage) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, - ussdMessage), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, + ussdMessage); + } + }, mListenerExecutor); } /** @@ -1496,11 +1514,12 @@ public class ImsCallSession { */ @Override public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType, - targetNetworkType), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType, + targetNetworkType); + } + }, mListenerExecutor); } /** @@ -1509,11 +1528,12 @@ public class ImsCallSession { @Override public void callSessionHandover(int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionHandover(ImsCallSession.this, srcNetworkType, - targetNetworkType, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHandover(ImsCallSession.this, srcNetworkType, + targetNetworkType, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1522,11 +1542,12 @@ public class ImsCallSession { @Override public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType, - targetNetworkType, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType, + targetNetworkType, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1534,11 +1555,11 @@ public class ImsCallSession { */ @Override public void callSessionTtyModeReceived(int mode) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionTtyModeReceived(ImsCallSession.this, mode), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionTtyModeReceived(ImsCallSession.this, mode); + } + }, mListenerExecutor); } /** @@ -1548,20 +1569,22 @@ public class ImsCallSession { * otherwise. */ public void callSessionMultipartyStateChanged(boolean isMultiParty) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionMultipartyStateChanged(ImsCallSession.this, - isMultiParty), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionMultipartyStateChanged(ImsCallSession.this, + isMultiParty); + } + }, mListenerExecutor); } @Override public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionSuppServiceReceived(ImsCallSession.this, - suppServiceInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionSuppServiceReceived(ImsCallSession.this, + suppServiceInfo); + } + }, mListenerExecutor); } /** @@ -1569,11 +1592,12 @@ public class ImsCallSession { */ @Override public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, - callProfile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, + callProfile); + } + }, mListenerExecutor); } /** @@ -1581,11 +1605,11 @@ public class ImsCallSession { */ @Override public void callSessionRttModifyResponseReceived(int status) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRttModifyResponseReceived(status), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRttModifyResponseReceived(status); + } + }, mListenerExecutor); } /** @@ -1593,10 +1617,11 @@ public class ImsCallSession { */ @Override public void callSessionRttMessageReceived(String rttMessage) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRttMessageReceived(rttMessage), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRttMessageReceived(rttMessage); + } + }, mListenerExecutor); } /** @@ -1604,28 +1629,29 @@ public class ImsCallSession { */ @Override public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRttAudioIndicatorChanged(profile), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRttAudioIndicatorChanged(profile); + } + }, mListenerExecutor); } @Override public void callSessionTransferred() { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionTransferred(ImsCallSession.this), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionTransferred(ImsCallSession.this); + } + }, mListenerExecutor); } @Override public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1634,10 +1660,11 @@ public class ImsCallSession { */ @Override public void callSessionDtmfReceived(char dtmf) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionDtmfReceived( - dtmf), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionDtmfReceived(dtmf); + } + }, mListenerExecutor); } /** @@ -1645,10 +1672,11 @@ public class ImsCallSession { */ @Override public void callQualityChanged(CallQuality callQuality) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callQualityChanged( - callQuality), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callQualityChanged(callQuality); + } + }, mListenerExecutor); } /** @@ -1658,11 +1686,12 @@ public class ImsCallSession { @Override public void callSessionRtpHeaderExtensionsReceived( @NonNull List<RtpHeaderExtension> extensions) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRtpHeaderExtensionsReceived( - new ArraySet<RtpHeaderExtension>(extensions)), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRtpHeaderExtensionsReceived( + new ArraySet<RtpHeaderExtension>(extensions)); + } + }, mListenerExecutor); } } diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index 53dff545b0ce..be233b82c426 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -42,6 +42,7 @@ import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.SipTransportImplBase; import android.util.Log; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.internal.annotations.VisibleForTesting; @@ -180,6 +181,12 @@ public class ImsService extends Service { // call ImsFeature#onFeatureRemoved. private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>(); + // A map of slot id -> boolean array, where each entry in the boolean array corresponds to an + // ImsFeature that was created for a slot id and not a sub id for backwards compatibility + // purposes. + private final SparseArray<SparseBooleanArray> mCreateImsFeatureWithSlotIdFlagMap = + new SparseArray<>(); + private IImsServiceControllerListener mListener; private Executor mExecutor; @@ -222,15 +229,36 @@ public class ImsService extends Service { } @Override - public IImsMmTelFeature createMmTelFeature(int slotId) { - return executeMethodAsyncForResult(() -> createMmTelFeatureInternal(slotId), - "createMmTelFeature"); + public IImsMmTelFeature createMmTelFeature(int slotId, int subId) { + MmTelFeature f = (MmTelFeature) getImsFeature(slotId, ImsFeature.FEATURE_MMTEL); + if (f == null) { + return executeMethodAsyncForResult(() -> createMmTelFeatureInternal(slotId, subId), + "createMmTelFeature"); + } else { + return f.getBinder(); + } + } + + @Override + public IImsMmTelFeature createEmergencyOnlyMmTelFeature(int slotId) { + MmTelFeature f = (MmTelFeature) getImsFeature(slotId, ImsFeature.FEATURE_MMTEL); + if (f == null) { + return executeMethodAsyncForResult(() -> createEmergencyOnlyMmTelFeatureInternal( + slotId), "createEmergencyOnlyMmTelFeature"); + } else { + return f.getBinder(); + } } @Override - public IImsRcsFeature createRcsFeature(int slotId) { - return executeMethodAsyncForResult(() -> createRcsFeatureInternal(slotId), - "createRcsFeature"); + public IImsRcsFeature createRcsFeature(int slotId, int subId) { + RcsFeature f = (RcsFeature) getImsFeature(slotId, ImsFeature.FEATURE_RCS); + if (f == null) { + return executeMethodAsyncForResult(() -> + createRcsFeatureInternal(slotId, subId), "createRcsFeature"); + } else { + return f.getBinder(); + } } @Override @@ -248,9 +276,14 @@ public class ImsService extends Service { } @Override - public void removeImsFeature(int slotId, int featureType) { + public void removeImsFeature(int slotId, int featureType, boolean changeSubId) { + if (changeSubId && isImsFeatureCreatedForSlot(slotId, featureType)) { + Log.w(LOG_TAG, "Do not remove Ims feature for compatibility"); + return; + } executeMethodAsync(() -> ImsService.this.removeImsFeature(slotId, featureType), "removeImsFeature"); + setImsFeatureCreatedForSlot(slotId, featureType, false); } @Override @@ -279,9 +312,10 @@ public class ImsService extends Service { } @Override - public IImsConfig getConfig(int slotId) { + public IImsConfig getConfig(int slotId, int subId) { return executeMethodAsyncForResult(() -> { - ImsConfigImplBase c = ImsService.this.getConfig(slotId); + ImsConfigImplBase c = + ImsService.this.getConfigForSubscription(slotId, subId); if (c != null) { c.setDefaultExecutor(mExecutor); return c.getIImsConfig(); @@ -292,9 +326,10 @@ public class ImsService extends Service { } @Override - public IImsRegistration getRegistration(int slotId) { + public IImsRegistration getRegistration(int slotId, int subId) { return executeMethodAsyncForResult(() -> { - ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); + ImsRegistrationImplBase r = + ImsService.this.getRegistrationForSubscription(slotId, subId); if (r != null) { r.setDefaultExecutor(mExecutor); return r.getBinder(); @@ -318,13 +353,15 @@ public class ImsService extends Service { } @Override - public void enableIms(int slotId) { - executeMethodAsync(() -> ImsService.this.enableIms(slotId), "enableIms"); + public void enableIms(int slotId, int subId) { + executeMethodAsync(() -> + ImsService.this.enableImsForSubscription(slotId, subId), "enableIms"); } @Override - public void disableIms(int slotId) { - executeMethodAsync(() -> ImsService.this.disableIms(slotId), "disableIms"); + public void disableIms(int slotId, int subId) { + executeMethodAsync(() -> + ImsService.this.disableImsForSubscription(slotId, subId), "disableIms"); } // Call the methods with a clean calling identity on the executor and wait indefinitely for @@ -364,28 +401,32 @@ public class ImsService extends Service { return null; } - /** - * @hide - */ - @VisibleForTesting - public SparseArray<ImsFeature> getFeatures(int slotId) { - return mFeaturesBySlot.get(slotId); + private IImsMmTelFeature createMmTelFeatureInternal(int slotId, int subscriptionId) { + MmTelFeature f = createMmTelFeatureForSubscription(slotId, subscriptionId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL); + f.setDefaultExecutor(mExecutor); + return f.getBinder(); + } else { + Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned."); + return null; + } } - private IImsMmTelFeature createMmTelFeatureInternal(int slotId) { - MmTelFeature f = createMmTelFeature(slotId); + private IImsMmTelFeature createEmergencyOnlyMmTelFeatureInternal(int slotId) { + MmTelFeature f = createEmergencyOnlyMmTelFeature(slotId); if (f != null) { setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL); f.setDefaultExecutor(mExecutor); return f.getBinder(); } else { - Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned."); + Log.e(LOG_TAG, "createEmergencyOnlyMmTelFeatureInternal: null feature returned."); return null; } } - private IImsRcsFeature createRcsFeatureInternal(int slotId) { - RcsFeature f = createRcsFeature(slotId); + private IImsRcsFeature createRcsFeatureInternal(int slotId, int subI) { + RcsFeature f = createRcsFeatureForSubscription(slotId, subI); if (f != null) { f.setDefaultExecutor(mExecutor); setupFeature(f, slotId, ImsFeature.FEATURE_RCS); @@ -466,6 +507,49 @@ public class ImsService extends Service { f.onFeatureRemoved(); features.remove(featureType); } + + } + + /** + * @hide + */ + @VisibleForTesting + public ImsFeature getImsFeature(int slotId, int featureType) { + synchronized (mFeaturesBySlot) { + // Get SparseArray for Features, by querying slot Id + SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); + if (features == null) { + return null; + } + return features.get(featureType); + } + } + + private void setImsFeatureCreatedForSlot(int slotId, + @ImsFeature.FeatureType int featureType, boolean createdForSlot) { + synchronized (mCreateImsFeatureWithSlotIdFlagMap) { + getImsFeatureCreatedForSlot(slotId).put(featureType, createdForSlot); + } + } + + /** + * @hide + */ + @VisibleForTesting + public boolean isImsFeatureCreatedForSlot(int slotId, + @ImsFeature.FeatureType int featureType) { + synchronized (mCreateImsFeatureWithSlotIdFlagMap) { + return getImsFeatureCreatedForSlot(slotId).get(featureType); + } + } + + private SparseBooleanArray getImsFeatureCreatedForSlot(int slotId) { + SparseBooleanArray createFlag = mCreateImsFeatureWithSlotIdFlagMap.get(slotId); + if (createFlag == null) { + createFlag = new SparseBooleanArray(); + mCreateImsFeatureWithSlotIdFlagMap.put(slotId, createFlag); + } + return createFlag; } /** @@ -524,27 +608,95 @@ public class ImsService extends Service { } /** + * The framework has enabled IMS for the subscription specified, the ImsService should register + * for IMS and perform all appropriate initialization to bring up all ImsFeatures. + * + * @param slotId The slot ID that IMS will be enabled for. + * @param subscriptionId The subscription ID that IMS will be enabled for. + */ + public void enableImsForSubscription(int slotId, int subscriptionId) { + enableIms(slotId); + } + + /** + * The framework has disabled IMS for the subscription specified. The ImsService must deregister + * for IMS and set capability status to false for all ImsFeatures. + * @param slotId The slot ID that IMS will be disabled for. + * @param subscriptionId The subscription ID that IMS will be disabled for. + */ + public void disableImsForSubscription(int slotId, int subscriptionId) { + disableIms(slotId); + } + + /** * The framework has enabled IMS for the slot specified, the ImsService should register for IMS * and perform all appropriate initialization to bring up all ImsFeatures. + * @deprecated Use {@link #enableImsForSubscription} instead. */ + @Deprecated public void enableIms(int slotId) { } /** * The framework has disabled IMS for the slot specified. The ImsService must deregister for IMS * and set capability status to false for all ImsFeatures. + * @deprecated Use {@link #disableImsForSubscription} instead. */ + @Deprecated public void disableIms(int slotId) { } /** * When called, the framework is requesting that a new {@link MmTelFeature} is created for the + * specified subscription. + * + * @param subscriptionId The subscription ID that the MMTEL Feature is being created for. + * @return The newly created {@link MmTelFeature} associated with the subscription or null if + * the feature is not supported. + */ + public @Nullable MmTelFeature createMmTelFeatureForSubscription(int slotId, + int subscriptionId) { + setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_MMTEL, true); + return createMmTelFeature(slotId); + } + + /** + * When called, the framework is requesting that a new {@link RcsFeature} is created for the + * specified subscription. + * + * @param subscriptionId The subscription ID that the RCS Feature is being created for. + * @return The newly created {@link RcsFeature} associated with the subscription or null if the + * feature is not supported. + */ + public @Nullable RcsFeature createRcsFeatureForSubscription(int slotId, int subscriptionId) { + setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_RCS, true); + return createRcsFeature(slotId); + } + + /** + * When called, the framework is requesting that a new emergency-only {@link MmTelFeature} is + * created for the specified slot. For emergency calls, there is no known Subscription Id. + * + * @param slotId The slot ID that the MMTEL Feature is being created for. + * @return An MmTelFeature instance to be used for the slot ID when there is not + * subscription inserted. Only requested when there is no subscription active on + * the specified slot. + */ + public @Nullable MmTelFeature createEmergencyOnlyMmTelFeature(int slotId) { + setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_MMTEL, true); + return createMmTelFeature(slotId); + } + + /** + * When called, the framework is requesting that a new {@link MmTelFeature} is created for the * specified slot. + * @deprecated Use {@link #createMmTelFeatureForSubscription} instead * * @param slotId The slot ID that the MMTEL Feature is being created for. * @return The newly created {@link MmTelFeature} associated with the slot or null if the * feature is not supported. */ + @Deprecated public MmTelFeature createMmTelFeature(int slotId) { return null; } @@ -552,32 +704,62 @@ public class ImsService extends Service { /** * When called, the framework is requesting that a new {@link RcsFeature} is created for the * specified slot. + * @deprecated Use {@link #createRcsFeatureForSubscription} instead * * @param slotId The slot ID that the RCS Feature is being created for. * @return The newly created {@link RcsFeature} associated with the slot or null if the feature * is not supported. */ + @Deprecated public RcsFeature createRcsFeature(int slotId) { return null; } /** + * Return the {@link ImsConfigImplBase} implementation associated with the provided + * subscription. This will be used by the platform to get/set specific IMS related + * configurations. + * + * @param subscriptionId The subscription ID that the IMS configuration is associated with. + * @return ImsConfig implementation that is associated with the specified subscription. + */ + public @NonNull ImsConfigImplBase getConfigForSubscription(int slotId, int subscriptionId) { + return getConfig(slotId); + } + + /** + * Return the {@link ImsRegistrationImplBase} implementation associated with the provided + * subscription. + * + * @param subscriptionId The subscription ID that is associated with the IMS Registration. + * @return the ImsRegistration implementation associated with the subscription. + */ + public @NonNull ImsRegistrationImplBase getRegistrationForSubscription(int slotId, + int subscriptionId) { + return getRegistration(slotId); + } + + /** * Return the {@link ImsConfigImplBase} implementation associated with the provided slot. This * will be used by the platform to get/set specific IMS related configurations. + * @deprecated use {@link #getConfigForSubscription} instead. * * @param slotId The slot that the IMS configuration is associated with. * @return ImsConfig implementation that is associated with the specified slot. */ + @Deprecated public ImsConfigImplBase getConfig(int slotId) { return new ImsConfigImplBase(); } /** * Return the {@link ImsRegistrationImplBase} implementation associated with the provided slot. + * @deprecated use {@link #getRegistrationForSubscription} instead. * * @param slotId The slot that is associated with the IMS Registration. * @return the ImsRegistration implementation associated with the slot. */ + @Deprecated public ImsRegistrationImplBase getRegistration(int slotId) { return new ImsRegistrationImplBase(); } diff --git a/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl index c6966b3cf53e..ae6166fea02a 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl @@ -32,18 +32,19 @@ import com.android.ims.internal.IImsFeatureStatusCallback; */ interface IImsServiceController { void setListener(IImsServiceControllerListener l); - IImsMmTelFeature createMmTelFeature(int slotId); - IImsRcsFeature createRcsFeature(int slotId); + IImsMmTelFeature createMmTelFeature(int slotId, int subId); + IImsMmTelFeature createEmergencyOnlyMmTelFeature(int slotId); + IImsRcsFeature createRcsFeature(int slotId, int subId); ImsFeatureConfiguration querySupportedImsFeatures(); long getImsServiceCapabilities(); void addFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c); void removeFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c); // Synchronous call to ensure the ImsService is ready before continuing with feature creation. void notifyImsServiceReadyForFeatureCreation(); - void removeImsFeature(int slotId, int featureType); - IImsConfig getConfig(int slotId); - IImsRegistration getRegistration(int slotId); + void removeImsFeature(int slotId, int featureType, boolean changeSubId); + IImsConfig getConfig(int slotId, int subId); + IImsRegistration getRegistration(int slotId, int subId); ISipTransport getSipTransport(int slotId); - oneway void enableIms(int slotId); - oneway void disableIms(int slotId); + oneway void enableIms(int slotId, int subId); + oneway void disableIms(int slotId, int subId); } diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl index f5f67bd36ec3..416096b1f6ec 100644 --- a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl +++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl @@ -23,11 +23,11 @@ import com.android.ims.ImsFeatureContainer; * {@hide} */ oneway interface IImsServiceFeatureCallback { - void imsFeatureCreated(in ImsFeatureContainer feature); + void imsFeatureCreated(in ImsFeatureContainer feature, int subId); // Reason defined in FeatureConnector.UnavailableReason void imsFeatureRemoved(int reason); // Status defined in ImsFeature.ImsState. - void imsStatusChanged(int status); + void imsStatusChanged(int status, int subId); //Capabilities defined in ImsService.ImsServiceCapability void updateCapabilities(long capabilities); }
\ No newline at end of file diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index ba9584112b75..39ab7eb0973c 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -592,6 +592,7 @@ public interface RILConstants { int RIL_UNSOL_UNTHROTTLE_APN = 1052; int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053; int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054; + int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055; /* The following unsols are not defined in RIL.h */ int RIL_UNSOL_HAL_NON_RIL_BASE = 1100; diff --git a/test-base/Android.bp b/test-base/Android.bp index 97ebba689d8b..8be732452228 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -26,7 +26,19 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 // SPDX-license-identifier-CPL-1.0 - default_applicable_licenses: ["frameworks_base_license"], + default_applicable_licenses: ["frameworks_base_test-base_license"], +} + +license { + name: "frameworks_base_test-base_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + "SPDX-license-identifier-CPL-1.0", + ], + license_text: [ + "src/junit/cpl-v10.html", + ], } java_sdk_library { diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 0f56bb3819f1..2a19af9f8cd2 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -18,12 +18,19 @@ // ===================================== package { // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - // SPDX-license-identifier-CPL-1.0 - default_applicable_licenses: ["frameworks_base_license"], + default_applicable_licenses: ["frameworks_base_test-runner_license"], +} + +license { + name: "frameworks_base_test-runner_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + "SPDX-license-identifier-CPL-1.0", + ], + license_text: [ + "src/junit/cpl-v10.html", + ], } java_sdk_library { diff --git a/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java b/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java index 4ec86b186285..56848b89be6f 100644 --- a/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java +++ b/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java @@ -15,20 +15,19 @@ */ package com.android.tests.dataidle; +import static android.net.NetworkStats.METERED_YES; + +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.content.Context; -import android.net.INetworkStatsService; -import android.net.INetworkStatsSession; -import android.net.NetworkStats; -import android.net.NetworkStats.Entry; import android.net.NetworkTemplate; -import android.net.TrafficStats; import android.os.Bundle; -import android.os.RemoteException; -import android.os.ServiceManager; import android.telephony.TelephonyManager; import android.test.InstrumentationTestCase; import android.util.Log; +import java.util.Set; + /** * A test that dumps data usage to instrumentation out, used for measuring data usage for idle * devices. @@ -36,7 +35,7 @@ import android.util.Log; public class DataIdleTest extends InstrumentationTestCase { private TelephonyManager mTelephonyManager; - private INetworkStatsService mStatsService; + private NetworkStatsManager mStatsManager; private static final String LOG_TAG = "DataIdleTest"; private final static int INSTRUMENTATION_IN_PROGRESS = 2; @@ -44,8 +43,7 @@ public class DataIdleTest extends InstrumentationTestCase { protected void setUp() throws Exception { super.setUp(); Context c = getInstrumentation().getTargetContext(); - mStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + mStatsManager = c.getSystemService(NetworkStatsManager.class); mTelephonyManager = (TelephonyManager) c.getSystemService(Context.TELEPHONY_SERVICE); } @@ -53,7 +51,9 @@ public class DataIdleTest extends InstrumentationTestCase { * Test that dumps all the data usage metrics for wifi to instrumentation out. */ public void testWifiIdle() { - NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard(); + final NetworkTemplate template = new NetworkTemplate + .Builder(NetworkTemplate.MATCH_WIFI) + .build(); fetchStats(template); } @@ -61,8 +61,11 @@ public class DataIdleTest extends InstrumentationTestCase { * Test that dumps all the data usage metrics for all mobile to instrumentation out. */ public void testMobile() { - String subscriberId = mTelephonyManager.getSubscriberId(); - NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId); + final String subscriberId = mTelephonyManager.getSubscriberId(); + NetworkTemplate template = new NetworkTemplate + .Builder(NetworkTemplate.MATCH_MOBILE) + .setMeteredness(METERED_YES) + .setSubscriberIds(Set.of(subscriberId)).build(); fetchStats(template); } @@ -72,49 +75,26 @@ public class DataIdleTest extends InstrumentationTestCase { * @param template {@link NetworkTemplate} to match. */ private void fetchStats(NetworkTemplate template) { - INetworkStatsSession session = null; try { - mStatsService.forceUpdate(); - session = mStatsService.openSession(); - final NetworkStats stats = session.getSummaryForAllUid( - template, Long.MIN_VALUE, Long.MAX_VALUE, false); - reportStats(stats); - } catch (RemoteException e) { + mStatsManager.forceUpdate(); + final NetworkStats.Bucket bucket = + mStatsManager.querySummaryForDevice(template, Long.MIN_VALUE, Long.MAX_VALUE); + reportStats(bucket); + } catch (RuntimeException e) { Log.w(LOG_TAG, "Failed to fetch network stats."); - } finally { - TrafficStats.closeQuietly(session); } } /** * Print network data usage stats to instrumentation out - * @param stats {@link NetworkorStats} to print + * @param bucket {@link NetworkStats} to print */ - void reportStats(NetworkStats stats) { + void reportStats(NetworkStats.Bucket bucket) { Bundle result = new Bundle(); - long rxBytes = 0; - long txBytes = 0; - long rxPackets = 0; - long txPackets = 0; - for (int i = 0; i < stats.size(); ++i) { - // Label will be iface_uid_tag_set - Entry statsEntry = stats.getValues(i, null); - // Debugging use. - /* - String labelTemplate = String.format("%s_%d_%d_%d", statsEntry.iface, statsEntry.uid, - statsEntry.tag, statsEntry.set) + "_%s"; - result.putLong(String.format(labelTemplate, "rxBytes"), statsEntry.rxBytes); - result.putLong(String.format(labelTemplate, "txBytes"), statsEntry.txBytes); - */ - rxPackets += statsEntry.rxPackets; - rxBytes += statsEntry.rxBytes; - txPackets += statsEntry.txPackets; - txBytes += statsEntry.txBytes; - } - result.putLong("Total rx Bytes", rxBytes); - result.putLong("Total tx Bytes", txBytes); - result.putLong("Total rx Packets", rxPackets); - result.putLong("Total tx Packets", txPackets); + result.putLong("Total rx Bytes", bucket.getRxBytes()); + result.putLong("Total tx Bytes", bucket.getTxBytes()); + result.putLong("Total rx Packets", bucket.getRxPackets()); + result.putLong("Total tx Packets", bucket.getTxPackets()); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, result); } diff --git a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java index 6985702c24e6..9a88abdd7b37 100644 --- a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java +++ b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java @@ -81,7 +81,8 @@ public class DozeTestDream extends DreamService { intent.setPackage(getPackageName()); IntentFilter filter = new IntentFilter(); filter.addAction(intent.getAction()); - registerReceiver(mAlarmReceiver, filter); + registerReceiver(mAlarmReceiver, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index 56879c90006e..c87d8e1b98e1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -31,7 +31,6 @@ import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible @@ -39,7 +38,6 @@ import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -157,11 +155,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index c28466c485e9..f2696d8a71ff 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -34,12 +34,10 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -117,11 +115,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Presubmit @Test - fun entireScreenCovered() { - // This test doesn't work in shell transitions because of b/206086894 - assumeFalse(isShellTransitionsEnabled) - testSpec.entireScreenCovered() - } + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test @@ -159,11 +153,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index c7f1b99329fb..24b1598f899c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -32,7 +32,6 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName @@ -134,11 +133,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index 46ed0ad88d31..e5d82a11c389 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -34,11 +34,9 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -126,11 +124,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun entireScreenCovered() { - // This test doesn't work in shell transitions because of b/206086894 - assumeFalse(isShellTransitionsEnabled) - testSpec.entireScreenCovered() - } + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test @@ -152,11 +146,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index ebe4be2b437f..87f8ef28cfda 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -34,11 +34,9 @@ import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -130,11 +128,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index f6e5adc2309b..0ad0a033a4fe 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -101,8 +101,6 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - // This test doesn't work in shell transitions because of b/204570898 - assumeFalse(isShellTransitionsEnabled) val component = FlickerComponentName("", "RecentTaskScreenshotSurface") testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry( @@ -116,8 +114,6 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun launcherWindowBecomesInvisible() { - // This test doesn't work in shell transitions because of b/204574221 - assumeFalse(isShellTransitionsEnabled) testSpec.assertWm { this.isAppWindowVisible(LAUNCHER_COMPONENT) .then() @@ -127,11 +123,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun imeWindowIsAlwaysVisible() { - // This test doesn't work in shell transitions because of b/204570898 - assumeFalse(isShellTransitionsEnabled) - testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) - } + fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) @Presubmit @Test @@ -202,8 +194,6 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun appLayerReplacesLauncher() { - // This test doesn't work in shell transitions because of b/204574221 - assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(LAUNCHER_COMPONENT) .then() @@ -219,11 +209,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index dcb5c86f32ad..8f2803e97986 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -261,7 +261,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { testSpec.assertWm { this.isAppWindowOnTop(LAUNCHER_COMPONENT) .then() - .isAppWindowVisible(FlickerComponentName.SNAPSHOT) + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) .then() .isAppWindowVisible(testApp.component) } @@ -342,4 +342,4 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { ) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 0879b98031bb..3f0de7f3cd7d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -25,13 +25,11 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -167,11 +165,7 @@ class ChangeAppRotationTest( */ @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() /** {@inheritDoc} */ @FlakyTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index e44bee644ceb..3ae484b5e5d2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -26,10 +26,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -102,8 +100,6 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appWindowFullScreen() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) testSpec.assertWm { this.invoke("isFullScreen") { val appWindow = it.windowState(testApp.`package`) @@ -139,8 +135,6 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appLayerAlwaysVisible() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { isVisible(testApp.component) } @@ -152,8 +146,6 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appLayerRotates() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.invoke("entireScreenCovered") { entry -> entry.entry.displays.map { display -> @@ -193,8 +185,6 @@ class SeamlessAppRotationTest( @Presubmit @Test fun focusDoesNotChange() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) testSpec.assertEventLog { this.focusDoesNotChange() } diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java index d03aee282ee1..2fbcf9d87bd4 100644 --- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java +++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java @@ -15,60 +15,105 @@ */ package android.net.vcn; -import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; -import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Test; import java.util.HashSet; import java.util.Set; -public class VcnCellUnderlyingNetworkTemplateTest { +public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase { private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>(); private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>(); // Package private for use in VcnGatewayConnectionConfigTest - static VcnCellUnderlyingNetworkTemplate getTestNetworkPriority() { + static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() { return new VcnCellUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setAllowedOperatorPlmnIds(ALLOWED_PLMN_IDS) - .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS) - .setAllowRoaming(true /* allowRoaming */) - .setRequireOpportunistic(true /* requireOpportunistic */) + .setMetered(MATCH_FORBIDDEN) + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS) + .setOperatorPlmnIds(ALLOWED_PLMN_IDS) + .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS) + .setRoaming(MATCH_FORBIDDEN) + .setOpportunistic(MATCH_REQUIRED) .build(); } @Test public void testBuilderAndGetters() { - final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); - assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality()); - assertTrue(networkPriority.allowMetered()); - assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedOperatorPlmnIds()); - assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds()); - assertTrue(networkPriority.allowRoaming()); - assertTrue(networkPriority.requireOpportunistic()); + final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); + assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered()); + assertEquals( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinEntryUpstreamBandwidthKbps()); + assertEquals( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinExitUpstreamBandwidthKbps()); + assertEquals( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinEntryDownstreamBandwidthKbps()); + assertEquals( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinExitDownstreamBandwidthKbps()); + assertEquals(ALLOWED_PLMN_IDS, networkPriority.getOperatorPlmnIds()); + assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds()); + assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming()); + assertEquals(MATCH_REQUIRED, networkPriority.getOpportunistic()); } @Test public void testBuilderAndGettersForDefaultValues() { final VcnCellUnderlyingNetworkTemplate networkPriority = new VcnCellUnderlyingNetworkTemplate.Builder().build(); - assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality()); - assertFalse(networkPriority.allowMetered()); - assertEquals(new HashSet<String>(), networkPriority.getAllowedOperatorPlmnIds()); - assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds()); - assertFalse(networkPriority.allowRoaming()); - assertFalse(networkPriority.requireOpportunistic()); + assertEquals(MATCH_ANY, networkPriority.getMetered()); + + // Explicitly expect 0, as documented in Javadoc on setter methods. + assertEquals(0, networkPriority.getMinEntryUpstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinExitUpstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinEntryDownstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinExitDownstreamBandwidthKbps()); + + assertEquals(new HashSet<String>(), networkPriority.getOperatorPlmnIds()); + assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds()); + assertEquals(MATCH_ANY, networkPriority.getRoaming()); + assertEquals(MATCH_ANY, networkPriority.getOpportunistic()); + } + + @Test + public void testBuilderRequiresStricterEntryCriteria() { + try { + new VcnCellUnderlyingNetworkTemplate.Builder() + .setMinUpstreamBandwidthKbps( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS); + + fail("Expected IAE for exit threshold > entry threshold"); + } catch (IllegalArgumentException expected) { + } + + try { + new VcnCellUnderlyingNetworkTemplate.Builder() + .setMinDownstreamBandwidthKbps( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS); + + fail("Expected IAE for exit threshold > entry threshold"); + } catch (IllegalArgumentException expected) { + } } @Test public void testPersistableBundle() { - final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); + final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); assertEquals( networkPriority, VcnUnderlyingNetworkTemplate.fromPersistableBundle( diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 1f2905da08f4..2aef9ae7ca32 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -17,8 +17,8 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; -import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES; -import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_PRIORITIES_KEY; +import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES; +import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -40,8 +40,9 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedHashSet; +import java.util.List; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @@ -54,17 +55,17 @@ public class VcnGatewayConnectionConfigTest { }; public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN}; - private static final LinkedHashSet<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_PRIORITIES = - new LinkedHashSet(); + private static final List<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_TEMPLATES = + new ArrayList(); static { Arrays.sort(EXPOSED_CAPS); Arrays.sort(UNDERLYING_CAPS); - UNDERLYING_NETWORK_PRIORITIES.add( - VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority()); - UNDERLYING_NETWORK_PRIORITIES.add( - VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); + UNDERLYING_NETWORK_TEMPLATES.add( + VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); + UNDERLYING_NETWORK_TEMPLATES.add( + VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); } public static final long[] RETRY_INTERVALS_MS = @@ -95,7 +96,7 @@ public class VcnGatewayConnectionConfigTest { // Public for use in VcnGatewayConnectionTest public static VcnGatewayConnectionConfig buildTestConfig() { final VcnGatewayConnectionConfig.Builder builder = - newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES); + newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES); return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS); } @@ -174,10 +175,10 @@ public class VcnGatewayConnectionConfigTest { } @Test - public void testBuilderRequiresNonNullNetworkPriorities() { + public void testBuilderRequiresNonNullNetworkTemplates() { try { newBuilder().setVcnUnderlyingNetworkPriorities(null); - fail("Expected exception due to invalid underlyingNetworkPriorities"); + fail("Expected exception due to invalid underlyingNetworkTemplates"); } catch (NullPointerException e) { } } @@ -219,7 +220,7 @@ public class VcnGatewayConnectionConfigTest { Arrays.sort(exposedCaps); assertArrayEquals(EXPOSED_CAPS, exposedCaps); - assertEquals(UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities()); + assertEquals(UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities()); assertEquals(TUNNEL_CONNECTION_PARAMS, config.getTunnelConnectionParams()); assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis()); @@ -234,13 +235,13 @@ public class VcnGatewayConnectionConfigTest { } @Test - public void testParsePersistableBundleWithoutVcnUnderlyingNetworkPriorities() { + public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() { PersistableBundle configBundle = buildTestConfig().toPersistableBundle(); - configBundle.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, null); + configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null); final VcnGatewayConnectionConfig config = new VcnGatewayConnectionConfig(configBundle); assertEquals( - DEFAULT_UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities()); + DEFAULT_UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities()); } private static IkeTunnelConnectionParams buildTunnelConnectionParams(String ikePsk) { @@ -285,39 +286,36 @@ public class VcnGatewayConnectionConfigTest { assertNotEquals(config, anotherConfig); } - private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkPriorities( - LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPriorities) { + private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkTemplates( + List<VcnUnderlyingNetworkTemplate> networkTemplates) { return buildTestConfigWithExposedCaps( new VcnGatewayConnectionConfig.Builder( - "buildTestConfigWithVcnUnderlyingNetworkPriorities", + "buildTestConfigWithVcnUnderlyingNetworkTemplates", TUNNEL_CONNECTION_PARAMS) - .setVcnUnderlyingNetworkPriorities(networkPriorities), + .setVcnUnderlyingNetworkPriorities(networkTemplates), EXPOSED_CAPS); } @Test - public void testVcnUnderlyingNetworkPrioritiesEquality() throws Exception { + public void testVcnUnderlyingNetworkTemplatesEquality() throws Exception { final VcnGatewayConnectionConfig config = - buildTestConfigWithVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES); + buildTestConfigWithVcnUnderlyingNetworkTemplates(UNDERLYING_NETWORK_TEMPLATES); - final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesEqual = - new LinkedHashSet(); - networkPrioritiesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority()); - networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); + final List<VcnUnderlyingNetworkTemplate> networkTemplatesEqual = new ArrayList(); + networkTemplatesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); + networkTemplatesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); final VcnGatewayConnectionConfig configEqual = - buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesEqual); + buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesEqual); - final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesNotEqual = - new LinkedHashSet(); - networkPrioritiesNotEqual.add( - VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); + final List<VcnUnderlyingNetworkTemplate> networkTemplatesNotEqual = new ArrayList(); + networkTemplatesNotEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); final VcnGatewayConnectionConfig configNotEqual = - buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesNotEqual); + buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesNotEqual); - assertEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesEqual); + assertEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesEqual); assertEquals(config, configEqual); - assertNotEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesNotEqual); + assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual); assertNotEquals(config, configNotEqual); } } diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java new file mode 100644 index 000000000000..399e13600442 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkTemplateTestBase.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.vcn; + +public class VcnUnderlyingNetworkTemplateTestBase { + // Public for use in NetworkPriorityClassifierTest + public static final int TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS = 200; + public static final int TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS = 100; + public static final int TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS = 400; + public static final int TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS = 300; +} diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java index 652057fd48c7..4063178e005d 100644 --- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java +++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java @@ -15,60 +15,94 @@ */ package android.net.vcn; -import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; -import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; -public class VcnWifiUnderlyingNetworkTemplateTest { +import java.util.Set; + +public class VcnWifiUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase { private static final String SSID = "TestWifi"; - private static final int INVALID_NETWORK_QUALITY = -1; // Package private for use in VcnGatewayConnectionConfigTest - static VcnWifiUnderlyingNetworkTemplate getTestNetworkPriority() { + static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() { return new VcnWifiUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setSsid(SSID) + .setMetered(MATCH_FORBIDDEN) + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS) + .setSsids(Set.of(SSID)) .build(); } @Test public void testBuilderAndGetters() { - final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); - assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality()); - assertTrue(networkPriority.allowMetered()); - assertEquals(SSID, networkPriority.getSsid()); + final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); + assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered()); + assertEquals( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinEntryUpstreamBandwidthKbps()); + assertEquals( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinExitUpstreamBandwidthKbps()); + assertEquals( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinEntryDownstreamBandwidthKbps()); + assertEquals( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + networkPriority.getMinExitDownstreamBandwidthKbps()); + assertEquals(Set.of(SSID), networkPriority.getSsids()); } @Test public void testBuilderAndGettersForDefaultValues() { final VcnWifiUnderlyingNetworkTemplate networkPriority = new VcnWifiUnderlyingNetworkTemplate.Builder().build(); - assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality()); - assertFalse(networkPriority.allowMetered()); - assertNull(SSID, networkPriority.getSsid()); + assertEquals(MATCH_ANY, networkPriority.getMetered()); + + // Explicitly expect 0, as documented in Javadoc on setter methods.. + assertEquals(0, networkPriority.getMinEntryUpstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinExitUpstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinEntryDownstreamBandwidthKbps()); + assertEquals(0, networkPriority.getMinExitDownstreamBandwidthKbps()); + + assertTrue(networkPriority.getSsids().isEmpty()); } @Test - public void testBuildWithInvalidNetworkQuality() { + public void testBuilderRequiresStricterEntryCriteria() { try { new VcnWifiUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(INVALID_NETWORK_QUALITY); - fail("Expected to fail due to the invalid network quality"); - } catch (Exception expected) { + .setMinUpstreamBandwidthKbps( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS); + + fail("Expected IAE for exit threshold > entry threshold"); + } catch (IllegalArgumentException expected) { + } + + try { + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinDownstreamBandwidthKbps( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS); + + fail("Expected IAE for exit threshold > entry threshold"); + } catch (IllegalArgumentException expected) { } } @Test public void testPersistableBundle() { - final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); + final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); assertEquals( networkPriority, VcnUnderlyingNetworkTemplate.fromPersistableBundle( diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 5d2f9d748581..6d269686e42f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -58,6 +58,7 @@ import android.util.ArraySet; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; +import com.android.server.vcn.Vcn.VcnUserMobileDataStateListener; import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; import org.junit.Before; @@ -208,6 +209,13 @@ public class VcnTest { } @Test + public void testMobileDataStateListenersRegistered() { + // Validate state from setUp() + verify(mTelephonyManager, times(3)) + .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class)); + } + + @Test public void testMobileDataStateCheckedOnInitialization_enabled() { // Validate state from setUp() assertTrue(mVcn.isMobileDataEnabled()); @@ -263,6 +271,24 @@ public class VcnTest { assertFalse(mVcn.isMobileDataEnabled()); } + @Test + public void testSubscriptionSnapshotUpdatesMobileDataStateListeners() { + final TelephonySubscriptionSnapshot updatedSnapshot = + mock(TelephonySubscriptionSnapshot.class); + + doReturn(new ArraySet<>(Arrays.asList(2, 4))) + .when(updatedSnapshot) + .getAllSubIdsInGroup(any()); + + mVcn.updateSubscriptionSnapshot(updatedSnapshot); + mTestLooper.dispatchAll(); + + verify(mTelephonyManager, times(4)) + .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class)); + verify(mTelephonyManager, times(2)) + .unregisterTelephonyCallback(any(VcnUserMobileDataStateListener.class)); + } + private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { for (final int[] caps : TEST_CAPS) { startVcnGatewayWithCapabilities(requestListener, caps); @@ -402,24 +428,17 @@ public class VcnTest { verify(mVcnNetworkProvider).resendAllRequests(requestListener); } - private void verifyMobileDataToggled(boolean startingToggleState, boolean endingToggleState) { - final ArgumentCaptor<ContentObserver> captor = - ArgumentCaptor.forClass(ContentObserver.class); - verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture()); - final ContentObserver contentObserver = captor.getValue(); - + private void setupForMobileDataTest(boolean startingToggleState) { // Start VcnGatewayConnections final NetworkRequestListener requestListener = verifyAndGetRequestListener(); mVcn.setMobileDataEnabled(startingToggleState); triggerVcnRequestListeners(requestListener); - final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = - mVcn.getVcnGatewayConnectionConfigMap(); - - // Trigger data toggle change. - doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); - contentObserver.onChange(false /* selfChange, ignored */); - mTestLooper.dispatchAll(); + } + private void verifyMobileDataToggledUpdatesGatewayConnections( + boolean startingToggleState, + boolean endingToggleState, + Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways) { // Verify that data toggle changes restart ONLY INTERNET or DUN networks, and only if the // toggle state changed. for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : gateways.entrySet()) { @@ -433,29 +452,98 @@ public class VcnTest { } } + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); if (startingToggleState != endingToggleState) { verify(mVcnNetworkProvider).resendAllRequests(requestListener); } assertEquals(endingToggleState, mVcn.isMobileDataEnabled()); } + private void verifyGlobalMobileDataToggled( + boolean startingToggleState, boolean endingToggleState) { + setupForMobileDataTest(startingToggleState); + final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = + mVcn.getVcnGatewayConnectionConfigMap(); + + // Trigger data toggle change + final ArgumentCaptor<ContentObserver> captor = + ArgumentCaptor.forClass(ContentObserver.class); + verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture()); + final ContentObserver contentObserver = captor.getValue(); + + doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); + contentObserver.onChange(false /* selfChange, ignored */); + mTestLooper.dispatchAll(); + + // Verify resultant behavior + verifyMobileDataToggledUpdatesGatewayConnections( + startingToggleState, endingToggleState, gateways); + } + + @Test + public void testGlobalMobileDataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, true /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataDisabled() { + verifyGlobalMobileDataToggled( + true /* startingToggleState */, false /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataObserverFiredWithoutChanges_dataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, false /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataObserverFiredWithoutChanges_dataDisabled() { + verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); + } + + private void verifySubscriptionMobileDataToggled( + boolean startingToggleState, boolean endingToggleState) { + setupForMobileDataTest(startingToggleState); + final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = + mVcn.getVcnGatewayConnectionConfigMap(); + + // Trigger data toggle change. + final ArgumentCaptor<VcnUserMobileDataStateListener> captor = + ArgumentCaptor.forClass(VcnUserMobileDataStateListener.class); + verify(mTelephonyManager, times(3)).registerTelephonyCallback(any(), captor.capture()); + final VcnUserMobileDataStateListener listener = captor.getValue(); + + doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); + listener.onUserMobileDataStateChanged(false /* enabled, ignored */); + mTestLooper.dispatchAll(); + + // Verify resultant behavior + verifyMobileDataToggledUpdatesGatewayConnections( + startingToggleState, endingToggleState, gateways); + } + @Test - public void testMobileDataEnabled() { - verifyMobileDataToggled(false /* startingToggleState */, true /* endingToggleState */); + public void testSubscriptionMobileDataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, true /* endingToggleState */); } @Test - public void testMobileDataDisabled() { - verifyMobileDataToggled(true /* startingToggleState */, false /* endingToggleState */); + public void testSubscriptionMobileDataDisabled() { + verifyGlobalMobileDataToggled( + true /* startingToggleState */, false /* endingToggleState */); } @Test - public void testMobileDataObserverFiredWithoutChanges_dataEnabled() { - verifyMobileDataToggled(false /* startingToggleState */, false /* endingToggleState */); + public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, false /* endingToggleState */); } @Test - public void testMobileDataObserverFiredWithoutChanges_dataDisabled() { - verifyMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); + public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataDisabled() { + verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); } } diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java index f23d5bf67ebf..6c849b5af888 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -16,7 +16,12 @@ package com.android.server.vcn.routeselection; -import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; +import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS; +import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS; +import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS; +import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS; import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY; @@ -74,6 +79,12 @@ public class NetworkPriorityClassifierTest { private static final int CARRIER_ID = 1; private static final int CARRIER_ID_OTHER = 2; + private static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024; + private static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048; + + private static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100; + private static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200; + private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES = @@ -81,6 +92,8 @@ public class NetworkPriorityClassifierTest { .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .setSignalStrength(WIFI_RSSI) .setSsid(SSID) + .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS) + .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS) .build(); private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER = @@ -91,6 +104,8 @@ public class NetworkPriorityClassifierTest { .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .setSubscriptionIds(Set.of(SUB_ID)) .setNetworkSpecifier(TEL_NETWORK_SPECIFIER) + .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS) + .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS) .build(); private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface"); @@ -144,8 +159,7 @@ public class NetworkPriorityClassifierTest { public void testMatchWithoutNotMeteredBit() { final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = new VcnWifiUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(false /* allowMetered */) + .setMetered(MATCH_FORBIDDEN) .build(); assertFalse( @@ -159,12 +173,133 @@ public class NetworkPriorityClassifierTest { null /* carrierConfig */)); } + private void verifyMatchesPriorityRuleForUpstreamBandwidth( + int entryUpstreamBandwidth, + int exitUpstreamBandwidth, + UnderlyingNetworkRecord currentlySelected, + boolean expectMatch) { + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinUpstreamBandwidthKbps(entryUpstreamBandwidth, exitUpstreamBandwidth) + .build(); + + assertEquals( + expectMatch, + checkMatchesPriorityRule( + mVcnContext, + wifiNetworkPriority, + mWifiNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot, + currentlySelected, + null /* carrierConfig */)); + } + + private void verifyMatchesPriorityRuleForDownstreamBandwidth( + int entryDownstreamBandwidth, + int exitDownstreamBandwidth, + UnderlyingNetworkRecord currentlySelected, + boolean expectMatch) { + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() + .setMinDownstreamBandwidthKbps( + entryDownstreamBandwidth, exitDownstreamBandwidth) + .build(); + + assertEquals( + expectMatch, + checkMatchesPriorityRule( + mVcnContext, + wifiNetworkPriority, + mWifiNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot, + currentlySelected, + null /* carrierConfig */)); + } + + @Test + public void testMatchWithEntryUpstreamBandwidthEquals() { + verifyMatchesPriorityRuleForUpstreamBandwidth( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + null /* currentlySelected */, + true); + } + + @Test + public void testMatchWithEntryUpstreamBandwidthTooLow() { + verifyMatchesPriorityRuleForUpstreamBandwidth( + LINK_UPSTREAM_BANDWIDTH_KBPS + 1, + LINK_UPSTREAM_BANDWIDTH_KBPS + 1, + null /* currentlySelected */, + false); + } + + @Test + public void testMatchWithEntryDownstreamBandwidthEquals() { + verifyMatchesPriorityRuleForDownstreamBandwidth( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + null /* currentlySelected */, + true); + } + + @Test + public void testMatchWithEntryDownstreamBandwidthTooLow() { + verifyMatchesPriorityRuleForDownstreamBandwidth( + LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1, + LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1, + null /* currentlySelected */, + false); + } + + @Test + public void testMatchWithExitUpstreamBandwidthEquals() { + verifyMatchesPriorityRuleForUpstreamBandwidth( + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS, + mWifiNetworkRecord, + true); + } + + @Test + public void testMatchWithExitUpstreamBandwidthTooLow() { + verifyMatchesPriorityRuleForUpstreamBandwidth( + LINK_UPSTREAM_BANDWIDTH_KBPS + 1, + LINK_UPSTREAM_BANDWIDTH_KBPS + 1, + mWifiNetworkRecord, + false); + } + + @Test + public void testMatchWithExitDownstreamBandwidthEquals() { + verifyMatchesPriorityRuleForDownstreamBandwidth( + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS, + mWifiNetworkRecord, + true); + } + + @Test + public void testMatchWithExitDownstreamBandwidthTooLow() { + verifyMatchesPriorityRuleForDownstreamBandwidth( + LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1, + LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1, + mWifiNetworkRecord, + false); + } + private void verifyMatchWifi( boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) { final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = new VcnWifiUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS) .build(); final UnderlyingNetworkRecord selectedNetworkRecord = isSelectedNetwork ? mWifiNetworkRecord : null; @@ -213,9 +348,13 @@ public class NetworkPriorityClassifierTest { final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER; final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = new VcnWifiUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setSsid(nwPrioritySsid) + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS) + .setSsids(Set.of(nwPrioritySsid)) .build(); assertEquals( @@ -239,9 +378,12 @@ public class NetworkPriorityClassifierTest { private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() { return new VcnCellUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setAllowRoaming(true /* allowRoaming */); + .setMinUpstreamBandwidthKbps( + TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS) + .setMinDownstreamBandwidthKbps( + TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS, + TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS); } @Test @@ -258,9 +400,7 @@ public class NetworkPriorityClassifierTest { @Test public void testMatchOpportunisticCell() { final VcnCellUnderlyingNetworkTemplate opportunisticCellNetworkPriority = - getCellNetworkPriorityBuilder() - .setRequireOpportunistic(true /* requireOpportunistic */) - .build(); + getCellNetworkPriorityBuilder().setOpportunistic(MATCH_REQUIRED).build(); when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true); when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>()); @@ -279,7 +419,7 @@ public class NetworkPriorityClassifierTest { final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER; final VcnCellUnderlyingNetworkTemplate networkPriority = getCellNetworkPriorityBuilder() - .setAllowedOperatorPlmnIds(Set.of(networkPriorityPlmnId)) + .setOperatorPlmnIds(Set.of(networkPriorityPlmnId)) .build(); assertEquals( @@ -308,7 +448,7 @@ public class NetworkPriorityClassifierTest { final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER; final VcnCellUnderlyingNetworkTemplate networkPriority = getCellNetworkPriorityBuilder() - .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId)) + .setSimSpecificCarrierIds(Set.of(networkPriorityCarrierId)) .build(); assertEquals( @@ -336,7 +476,7 @@ public class NetworkPriorityClassifierTest { @Test public void testMatchWifiFailWithoutNotRoamingBit() { final VcnCellUnderlyingNetworkTemplate networkPriority = - getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build(); + getCellNetworkPriorityBuilder().setRoaming(MATCH_FORBIDDEN).build(); assertFalse( checkMatchesCellPriorityRule( @@ -353,7 +493,7 @@ public class NetworkPriorityClassifierTest { calculatePriorityClass( mVcnContext, networkRecord, - VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES, + VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES, SUB_GROUP, mSubscriptionSnapshot, null /* currentlySelected */, diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index b939f354e89f..4a2d0aea82d5 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -311,7 +311,7 @@ class ManifestVisitor : public BaseVisitor { component_process ? component_process->value : default_process_; get_name = !process.empty() && process[0] != ':'; } - } else if (node->name == "instrumentation") { + } else if (node->name == "instrumentation" || node->name == "process") { get_name = true; } diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp index e1040666e410..466b7d97da00 100644 --- a/tools/aapt2/java/ProguardRules_test.cpp +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -44,6 +44,9 @@ TEST(ProguardRulesTest, ManifestRuleDefaultConstructorOnly) { android:name="com.foo.BarApplication" android:zygotePreloadName="com.foo.BarZygotePreload" > + <processes> + <process android:process=":sub" android:name="com.foo.BazApplication" /> + </processes> <activity android:name="com.foo.BarActivity"/> <service android:name="com.foo.BarService"/> <receiver android:name="com.foo.BarReceiver"/> @@ -59,6 +62,7 @@ TEST(ProguardRulesTest, ManifestRuleDefaultConstructorOnly) { EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BazApplication { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarActivity { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarService { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarReceiver { <init>(); }")); diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh index 36bea57b710f..95b43cdf253d 100755 --- a/tools/aosp/aosp_sha.sh +++ b/tools/aosp/aosp_sha.sh @@ -1,7 +1,7 @@ #!/bin/bash LOCAL_DIR="$( dirname "${BASH_SOURCE}" )" -if git branch -vv | grep -q -E "^\*[^\[]+\[aosp/"; then +if git log -n 1 --format='%D' HEAD@{upstream} | grep -q aosp/; then # Change appears to be in AOSP exit 0 elif git log -n 1 --format='%B' $1 | grep -q -E "^Ignore-AOSP-First: .+" ; then diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index fe2b0186c57f..3131f5687c6a 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -57,6 +57,7 @@ LANG_TO_SCRIPT = { 'sk': 'Latn', 'sl': 'Latn', 'sq': 'Latn', + 'sv': 'Latn', 'ta': 'Taml', 'te': 'Telu', 'tk': 'Latn', diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp index 9d773e44faea..ec2bffdfaf57 100644 --- a/tools/sdkparcelables/Android.bp +++ b/tools/sdkparcelables/Android.bp @@ -14,7 +14,7 @@ java_binary_host { "src/**/*.kt", ], static_libs: [ - "asm-6.0", + "asm-7.0", ], } diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt index 22e8d781335b..0fb062f280e3 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt @@ -39,7 +39,7 @@ fun main(args: Array<String>) { kotlin.system.exitProcess(2) } - val ancestorCollector = AncestorCollector(Opcodes.ASM6, null) + val ancestorCollector = AncestorCollector(Opcodes.ASM7, null) for (entry in zipFile.entries()) { if (entry.name.endsWith(".class")) { |