diff options
270 files changed, 6256 insertions, 4407 deletions
diff --git a/Android.bp b/Android.bp index efe530798041..0d9cbd8daf5c 100644 --- a/Android.bp +++ b/Android.bp @@ -442,8 +442,6 @@ java_library { "libcore-platform-compat-config", "services-platform-compat-config", "media-provider-platform-compat-config", - "services-devicepolicy-platform-compat-config", - "services-core-platform-compat-config", ], static_libs: [ // If MimeMap ever becomes its own APEX, then this dependency would need to be removed @@ -619,6 +617,15 @@ java_library { } filegroup { + name: "framework-ike-shared-srcs", + visibility: ["//frameworks/opt/net/ike"], + srcs: [ + "core/java/android/net/annotations/PolicyDirection.java", + "telephony/java/android/telephony/Annotation.java", + ], +} + +filegroup { name: "framework-networkstack-shared-srcs", srcs: [ // TODO: remove these annotations as soon as we can use andoid.support.annotations.* diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 041825c235d0..6109b713de24 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -1,5 +1,6 @@ package com.android.server.usage; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageEvents; @@ -99,8 +100,18 @@ public interface AppStandbyInternal { List<AppStandbyInfo> getAppStandbyBuckets(int userId); - void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, - int reason, long elapsedRealtime, boolean resetTimeout); + /** + * Changes an app's standby bucket to the provided value. The caller can only set the standby + * bucket for a different app than itself. + */ + void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, + int callingPid); + + /** + * Changes the app standby bucket for multiple apps at once. + */ + void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid, + int callingPid); void addActiveDeviceAdmin(String adminPkg, int userId); 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 2f8b5130edb0..58eb58961ac4 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -47,6 +47,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppGlobals; @@ -101,7 +102,6 @@ import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.usage.AppIdleHistory.AppUsageHistory; -import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import java.io.File; import java.io.PrintWriter; @@ -109,6 +109,7 @@ import java.time.Duration; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -1014,14 +1015,57 @@ public class AppStandbyController implements AppStandbyInternal { } } + @Override + public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, + int callingUid, int callingPid) { + setAppStandbyBuckets( + Collections.singletonList(new AppStandbyInfo(packageName, bucket)), + userId, callingUid, callingPid); + } + + @Override + public void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, + int callingUid, int callingPid) { + userId = ActivityManager.handleIncomingUser( + callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null); + final boolean shellCaller = callingUid == Process.ROOT_UID + || callingUid == Process.SHELL_UID; + final boolean systemCaller = UserHandle.isCore(callingUid); + final int reason = systemCaller ? REASON_MAIN_FORCED : REASON_MAIN_PREDICTED; + final int packageFlags = PackageManager.MATCH_ANY_USER + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE; + final int numApps = appBuckets.size(); + final long elapsedRealtime = mInjector.elapsedRealtime(); + for (int i = 0; i < numApps; ++i) { + final AppStandbyInfo bucketInfo = appBuckets.get(i); + final String packageName = bucketInfo.mPackageName; + final int bucket = bucketInfo.mStandbyBucket; + if (bucket < STANDBY_BUCKET_ACTIVE || bucket > STANDBY_BUCKET_NEVER) { + throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket); + } + final int packageUid = mInjector.getPackageManagerInternal() + .getPackageUid(packageName, packageFlags, userId); + // Caller cannot set their own standby state + if (packageUid == callingUid) { + throw new IllegalArgumentException("Cannot set your own standby bucket"); + } + if (packageUid < 0) { + throw new IllegalArgumentException( + "Cannot set standby bucket for non existent package (" + packageName + ")"); + } + setAppStandbyBucket(packageName, userId, bucket, reason, elapsedRealtime, shellCaller); + } + } + @VisibleForTesting void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, - int reason, long elapsedRealtime) { - setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false); + int reason) { + setAppStandbyBucket( + packageName, userId, newBucket, reason, mInjector.elapsedRealtime(), false); } - @Override - public void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, + private void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, int reason, long elapsedRealtime, boolean resetTimeout) { synchronized (mAppIdleLock) { // If the package is not installed, don't allow the bucket to be set. @@ -1444,6 +1488,10 @@ public class AppStandbyController implements AppStandbyInternal { mBatteryStats.noteEvent(event, packageName, uid); } + PackageManagerInternal getPackageManagerInternal() { + return mPackageManagerInternal; + } + boolean isPackageEphemeral(int userId, String packageName) { return mPackageManagerInternal.isPackageEphemeral(userId, packageName); } diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING index 7e7762337afc..4e1883382e2c 100644 --- a/apex/sdkextensions/TEST_MAPPING +++ b/apex/sdkextensions/TEST_MAPPING @@ -1,7 +1,7 @@ { "presubmit": [ { - "name": "CtsSdkExtTestCases" + "name": "CtsSdkExtensionsTestCases" }, { "name": "apiextensions_e2e_tests" diff --git a/api/current.txt b/api/current.txt index b673df0b5c90..0c93555fc483 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11356,6 +11356,8 @@ package android.content.pm { } public class CrossProfileApps { + method public boolean canInteractAcrossProfiles(); + method public boolean canRequestInteractAcrossProfiles(); method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle); method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle); method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles(); @@ -11928,6 +11930,7 @@ package android.content.pm { field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore"; field public static final String FEATURE_TELEPHONY = "android.hardware.telephony"; field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; + field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data"; field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; @@ -29155,6 +29158,7 @@ package android.net { ctor public DhcpInfo(); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR; field public int dns1; field public int dns2; field public int gateway; @@ -39524,7 +39528,9 @@ package android.provider { field public static final String ACTION_LOCALE_SETTINGS = "android.settings.LOCALE_SETTINGS"; field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS"; field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"; + field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION"; field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS"; + field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION"; field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; @@ -45010,6 +45016,7 @@ package android.telephony { field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff + field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool"; field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array"; @@ -45207,6 +45214,7 @@ package android.telephony { public static final class CarrierConfigManager.Ims { field public static final String KEY_PREFIX = "ims."; + field public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = "ims.wifi_off_deferring_time_int"; } public abstract class CellIdentity implements android.os.Parcelable { @@ -46134,12 +46142,12 @@ package android.telephony { method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle); method public boolean hasCarrierPrivileges(); method public boolean hasIccCard(); - method public boolean iccCloseLogicalChannel(int); - method public byte[] iccExchangeSimIO(int, int, int, int, int, String); + method @Deprecated public boolean iccCloseLogicalChannel(int); + method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String); method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String); - method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int); - method public String iccTransmitApduBasicChannel(int, int, int, int, int, String); - method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String); + method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int); + method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String); + method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String); method public boolean isConcurrentVoiceAndDataSupported(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled(); @@ -46157,7 +46165,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); method public void sendDialerSpecialCode(String); - method public String sendEnvelopeWithStatus(String); + method @Deprecated public String sendEnvelopeWithStatus(String); method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean); @@ -51599,9 +51607,10 @@ package android.view { method public boolean dispatchUnhandledMove(android.view.View, int); method protected void dispatchVisibilityChanged(@NonNull android.view.View, int); method public void dispatchWindowFocusChanged(boolean); - method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets); - method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); + method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); method public void dispatchWindowSystemUiVisiblityChanged(int); method public void dispatchWindowVisibilityChanged(int); method @CallSuper public void draw(android.graphics.Canvas); @@ -53281,9 +53290,10 @@ package android.view { } public interface WindowInsetsAnimationCallback { - method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets); - method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); + method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); } public static final class WindowInsetsAnimationCallback.AnimationBounds { diff --git a/api/system-current.txt b/api/system-current.txt index 384dc13ff567..3d5a2a8d4a1c 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4311,7 +4311,7 @@ package android.media.session { } public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener { - method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token); + method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @Nullable android.media.session.MediaSession.Token); } public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener { @@ -4855,6 +4855,7 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { + method public boolean deduceRestrictedCapability(); method @NonNull public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String); @@ -5835,6 +5836,7 @@ package android.net.wifi { method public int getMaxNumberOfClients(); method @Nullable public String getPassphrase(); method public int getSecurityType(); + method public int getShutdownTimeoutMillis(); method @Nullable public String getSsid(); method @Nullable public String getWpa2Passphrase(); method public boolean isHiddenSsid(); @@ -5860,6 +5862,7 @@ package android.net.wifi { method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String); } @@ -7185,7 +7188,7 @@ package android.os { ctor public UpdateEngine(); method @NonNull public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]); method public void applyPayload(String, long, long, String[]); - method public void applyPayload(@NonNull android.os.ParcelFileDescriptor, long, long, @NonNull String[]); + method public void applyPayload(@NonNull android.content.res.AssetFileDescriptor, @NonNull String[]); method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler); method public boolean bind(android.os.UpdateEngineCallback); method public void cancel(); @@ -7860,6 +7863,7 @@ package android.provider { } public final class Settings { + method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean); field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS"; field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS"; field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; @@ -10399,10 +10403,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int); + method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int); - method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String); - method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int); @@ -11833,6 +11837,8 @@ package android.view.accessibility { public final class AccessibilityManager { method public int getAccessibilityWindowId(@Nullable android.os.IBinder); method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut(); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int); } } diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 118a508a9071..1c6867c39790 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -78,7 +78,6 @@ cc_defaults { "src/external/StatsPuller.cpp", "src/external/StatsPullerManager.cpp", "src/external/SubsystemSleepStatePuller.cpp", - "src/external/SurfaceflingerStatsPuller.cpp", "src/external/TrainInfoPuller.cpp", "src/FieldValue.cpp", "src/guardrail/StatsdStats.cpp", @@ -148,7 +147,6 @@ cc_defaults { "libincident", "libservices", "libstatsmetadata", - "libtimestats_proto", "libutils", ], } @@ -297,7 +295,6 @@ cc_test { "tests/external/puller_util_test.cpp", "tests/external/StatsCallbackPuller_test.cpp", "tests/external/StatsPuller_test.cpp", - "tests/external/SurfaceflingerStatsPuller_test.cpp", "tests/FieldValue_test.cpp", "tests/guardrail/StatsdStats_test.cpp", "tests/indexed_priority_queue_test.cpp", diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 50896f84da43..1d31873209b2 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -41,7 +41,6 @@ #include "StatsCallbackPullerDeprecated.h" #include "StatsCompanionServicePuller.h" #include "SubsystemSleepStatePuller.h" -#include "SurfaceflingerStatsPuller.h" #include "TrainInfoPuller.h" #include "statslog.h" @@ -273,10 +272,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { // App ops {{.atomTag = android::util::APP_OPS}, {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}}, - // SurfaceflingerStatsGlobalInfo - {{.atomTag = android::util::SURFACEFLINGER_STATS_GLOBAL_INFO}, - {.puller = - new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}}, // VmsClientStats {{.atomTag = android::util::VMS_CLIENT_STATS}, {.additiveFields = {5, 6, 7, 8, 9, 10}, diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp deleted file mode 100644 index 23b2236f35f2..000000000000 --- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp +++ /dev/null @@ -1,99 +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. - */ - -#include "SurfaceflingerStatsPuller.h" - -#include <cutils/compiler.h> - -#include <numeric> - -#include "logd/LogEvent.h" -#include "stats_log_util.h" -#include "statslog.h" - -namespace android { -namespace os { -namespace statsd { - -SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) { -} - -bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) { - switch (mTagId) { - case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO: - return pullGlobalInfo(data); - default: - break; - } - - return false; -} - -static int64_t getTotalTime( - const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>& - buckets) { - int64_t total = 0; - for (const auto& bucket : buckets) { - if (bucket.time_millis() == 1000) { - continue; - } - - total += bucket.time_millis() * bucket.frame_count(); - } - - return total; -} - -bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) { - std::string protoBytes; - if (CC_UNLIKELY(mStatsProvider)) { - protoBytes = mStatsProvider(); - } else { - std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose); - if (!pipe.get()) { - return false; - } - char buf[1024]; - size_t bytesRead = 0; - do { - bytesRead = fread(buf, 1, sizeof(buf), pipe.get()); - protoBytes.append(buf, bytesRead); - } while (bytesRead > 0); - } - surfaceflinger::SFTimeStatsGlobalProto proto; - proto.ParseFromString(protoBytes); - - int64_t totalTime = getTotalTime(proto.present_to_present()); - - data->clear(); - data->reserve(1); - std::shared_ptr<LogEvent> event = - make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(), - getElapsedRealtimeNs()); - if (!event->write(proto.total_frames())) return false; - if (!event->write(proto.missed_frames())) return false; - if (!event->write(proto.client_composition_frames())) return false; - if (!event->write(proto.display_on_time())) return false; - if (!event->write(totalTime)) return false; - event->init(); - data->emplace_back(event); - - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h deleted file mode 100644 index ed7153edf797..000000000000 --- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h +++ /dev/null @@ -1,48 +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. - */ - -#pragma once - -#include <timestatsproto/TimeStatsProtoHeader.h> - -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * Pull metrics from Surfaceflinger - */ -class SurfaceflingerStatsPuller : public StatsPuller { -public: - explicit SurfaceflingerStatsPuller(const int tagId); - - // StatsPuller interface - bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override; - -protected: - // Test-only, for injecting fake data - using StatsProvider = std::function<std::string()>; - StatsProvider mStatsProvider; - -private: - bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp deleted file mode 100644 index 5b7a30d4a5aa..000000000000 --- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp +++ /dev/null @@ -1,96 +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. - */ - -#undef LOG_TAG -#define LOG_TAG "SurfaceflingerStatsPuller_test" - -#include "src/external/SurfaceflingerStatsPuller.h" -#include "statslog.h" - -#include <gtest/gtest.h> -#include <log/log.h> - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller { -public: - TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){}; - - void injectStats(const StatsProvider& statsProvider) { - mStatsProvider = statsProvider; - } -}; - -class SurfaceflingerStatsPullerTest : public ::testing::Test { -public: - SurfaceflingerStatsPullerTest() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - } - - ~SurfaceflingerStatsPullerTest() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); - } -}; - -TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) { - surfaceflinger::SFTimeStatsGlobalProto proto; - proto.set_total_frames(1); - proto.set_missed_frames(2); - proto.set_client_composition_frames(2); - proto.set_display_on_time(4); - - auto bucketOne = proto.add_present_to_present(); - bucketOne->set_time_millis(2); - bucketOne->set_frame_count(4); - auto bucketTwo = proto.add_present_to_present(); - bucketTwo->set_time_millis(4); - bucketTwo->set_frame_count(1); - auto bucketThree = proto.add_present_to_present(); - bucketThree->set_time_millis(1000); - bucketThree->set_frame_count(1); - static constexpr int64_t expectedAnimationMillis = 12; - TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO); - - puller.injectStats([&] { - return proto.SerializeAsString(); - }); - puller.ForceClearCache(); - vector<std::shared_ptr<LogEvent>> outData; - puller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId()); - EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value); - EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value); - EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value); - EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value); - EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index f299d456a18f..e6c89d9071e6 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -17,9 +17,12 @@ package android.app.admin; import android.annotation.UserIdInt; +import android.content.ComponentName; import android.content.Intent; +import android.os.UserHandle; import java.util.List; +import java.util.Set; /** * Device policy manager local system service interface. @@ -165,4 +168,23 @@ public abstract class DevicePolicyManagerInternal { * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead. */ protected abstract DeviceStateCache getDeviceStateCache(); + + /** + * Returns the combined set of the following: + * <ul> + * <li>The package names that the admin has previously set as allowed to request user consent + * for cross-profile communication, via {@link + * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li> + * <li>The default package names that are allowed to request user consent for cross-profile + * communication without being explicitly enabled by the admin , via {@link + * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li> + * </ul> + * + * @return the combined set of whitelisted package names set via + * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and + * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)} + * + * @hide + */ + public abstract List<String> getAllCrossProfilePackages(); } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index b1b6f0d61f9f..cb1f05573d93 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -2670,6 +2670,9 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.PAN) { BluetoothPan pan = new BluetoothPan(context, listener); return true; + } else if (profile == BluetoothProfile.PBAP) { + BluetoothPbap pbap = new BluetoothPbap(context, listener); + return true; } else if (profile == BluetoothProfile.HEALTH) { Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated"); return false; @@ -2742,6 +2745,10 @@ public final class BluetoothAdapter { 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(); diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index e897b917fcc2..abf32c5e0840 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -30,6 +30,7 @@ import com.android.internal.R; import com.android.internal.util.UserIcons; import java.util.List; +import java.util.Set; /** * Class for handling cross profile operations. Apps can use this class to interact with its @@ -169,6 +170,62 @@ public class CrossProfileApps { } } + /** + * Returns whether the calling package can request to interact across profiles. + * + * <p>The package's current ability to interact across profiles can be checked with + * {@link #canInteractAcrossProfiles()}. + * + * <p>Specifically, returns whether the following are all true: + * <ul> + * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li> + * <li>The calling app has requested</li> + * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest. + * <li>The calling package has either been whitelisted by default by the OEM or has been + * explicitly whitelisted by the admin via + * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}. + * </li> + * </ul> + * + * @return true if the calling package can request to interact across profiles. + */ + public boolean canRequestInteractAcrossProfiles() { + try { + return mService.canRequestInteractAcrossProfiles(mContext.getPackageName()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Returns whether the calling package can interact across profiles. + * + * <p>The package's current ability to request to interact across profiles can be checked with + * {@link #canRequestInteractAcrossProfiles()}. + * + * <p>Specifically, returns whether the following are all true: + * <ul> + * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li> + * <li>The user has previously consented to cross-profile communication for the calling + * package.</li> + * <li>The calling package has either been whitelisted by default by the OEM or has been + * explicitly whitelisted by the admin via + * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}. + * </li> + * </ul> + * + * @return true if the calling package can interact across profiles. + * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the + * calling UID. + */ + public boolean canInteractAcrossProfiles() { + try { + return mService.canInteractAcrossProfiles(mContext.getPackageName()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private void verifyCanAccessUser(UserHandle userHandle) { if (!getTargetUserProfiles().contains(userHandle)) { throw new SecurityException("Not allowed to access " + userHandle); diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl index d2d66cbda610..c5db0ccebf52 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -30,4 +30,6 @@ interface ICrossProfileApps { void startActivityAsUser(in IApplicationThread caller, in String callingPackage, in ComponentName component, int userId, boolean launchMainActivity); List<UserHandle> getTargetUserProfiles(in String callingPackage); + boolean canInteractAcrossProfiles(in String callingPackage); + boolean canRequestInteractAcrossProfiles(in String callingPackage); }
\ No newline at end of file diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b85c58ad0e2f..4bfc40e698b9 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2323,6 +2323,13 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * has a telephony radio that support data. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data"; + + /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * The device supports telephony carrier restriction mechanism. * diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java index 98bab44e19fb..912df67a0b45 100644 --- a/core/java/android/net/DhcpInfo.java +++ b/core/java/android/net/DhcpInfo.java @@ -16,8 +16,8 @@ package android.net; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; /** * A simple object for retrieving the results of a DHCP request. @@ -67,12 +67,12 @@ public class DhcpInfo implements Parcelable { buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress()); } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public int describeContents() { return 0; } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(ipAddress); dest.writeInt(gateway); @@ -83,7 +83,7 @@ public class DhcpInfo implements Parcelable { dest.writeInt(leaseDuration); } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public static final @android.annotation.NonNull Creator<DhcpInfo> CREATOR = new Creator<DhcpInfo>() { public DhcpInfo createFromParcel(Parcel in) { diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 45d0c7313fca..09ec6c35fcb9 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,7 +17,6 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -26,6 +25,7 @@ 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.ParcelFileDescriptor; import android.os.RemoteException; @@ -41,8 +41,6 @@ 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; @@ -78,11 +76,6 @@ public final class IpSecManager { */ public static final int DIRECTION_OUT = 1; - /** @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. * diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 33c39d489835..739e8178e68e 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -587,15 +587,14 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are - * typically provided by restricted networks. + * Deduces that all the capabilities it provides are typically provided by restricted networks + * or not. * - * TODO: consider: - * - Renaming it to guessRestrictedCapability and make it set the - * restricted capability bit in addition to clearing it. + * @return {@code true} if the network should be restricted. * @hide */ - public void maybeMarkCapabilitiesRestricted() { + @SystemApi + public boolean deduceRestrictedCapability() { // Check if we have any capability that forces the network to be restricted. final boolean forceRestrictedCapability = (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0; @@ -609,8 +608,17 @@ public final class NetworkCapabilities implements Parcelable { final boolean hasRestrictedCapabilities = (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0; - if (forceRestrictedCapability - || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities)) { + return forceRestrictedCapability + || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities); + } + + /** + * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted. + * + * @hide + */ + public void maybeMarkCapabilitiesRestricted() { + if (deduceRestrictedCapability()) { removeCapability(NET_CAPABILITY_NOT_RESTRICTED); } } diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java new file mode 100644 index 000000000000..febd9b406111 --- /dev/null +++ b/core/java/android/net/annotations/PolicyDirection.java @@ -0,0 +1,35 @@ +/* + * 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.net.annotations; + +import android.annotation.IntDef; +import android.net.IpSecManager; + +import java.lang.annotation.Retention; +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}) +@Retention(RetentionPolicy.SOURCE) +public @interface PolicyDirection {} diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 3ef86ed9f994..5fe647c8b6bf 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -758,11 +758,12 @@ public class Process { /** * Set the priority of a thread, based on Linux priorities. - * - * @param tid The identifier of the thread/process to change. + * + * @param tid The identifier of the thread/process to change. It should be + * the native thread id but not the managed id of {@link java.lang.Thread}. * @param priority A Linux priority level, from -20 for highest scheduling * priority to 19 for lowest scheduling priority. - * + * * @throws IllegalArgumentException Throws IllegalArgumentException if * <var>tid</var> does not exist. * @throws SecurityException Throws SecurityException if your process does diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java index 16f7b398731c..73e1adf134f2 100644 --- a/core/java/android/os/UpdateEngine.java +++ b/core/java/android/os/UpdateEngine.java @@ -19,6 +19,7 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.res.AssetFileDescriptor; import android.os.IUpdateEngine; import android.os.IUpdateEngineCallback; import android.os.RemoteException; @@ -349,16 +350,17 @@ public class UpdateEngine { } /** - * Applies the payload passed as ParcelFileDescriptor {@code pfd} instead of - * using the {@code file://} scheme. + * Applies the payload passed as AssetFileDescriptor {@code assetFd} + * instead of using the {@code file://} scheme. * * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and * {@code headerKeyValuePairs} parameters. */ - public void applyPayload(@NonNull ParcelFileDescriptor pfd, long offset, long size, + public void applyPayload(@NonNull AssetFileDescriptor assetFd, @NonNull String[] headerKeyValuePairs) { try { - mUpdateEngine.applyPayloadFd(pfd, offset, size, headerKeyValuePairs); + mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(), + assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9ee4cc30949d..06805238e6f5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14404,6 +14404,42 @@ public final class Settings { }; /** + * Activity Action: Show screen for controlling which apps have access to manage external + * storage. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * If you want to control a specific app's access to manage external storage, use + * {@link #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION} instead. + * <p> + * Output: Nothing. + * @see #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = + "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION"; + + /** + * Activity Action: Show screen for controlling if the app specified in the data URI of the + * intent can manage external storage. + * <p> + * Launching the corresponding activity requires the permission + * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * Input: The Intent's data URI MUST specify the application package name whose ability of + * managing external storage you want to control. + * For example "package:com.my.app". + * <p> + * Output: Nothing. + * @see #ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = + "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; + + /** * Performs a strict and comprehensive check of whether a calling package is allowed to * write/modify system settings, as the condition differs for pre-M, M+, and * privileged/preinstalled apps. If the provided uid does not match the @@ -14429,8 +14465,9 @@ public final class Settings { * current time. * @hide */ - public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid, - String callingPackage, boolean throwException) { + @SystemApi + public static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, + @NonNull String callingPackage, boolean throwException) { return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid, callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS, PM_WRITE_SETTINGS, true); diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java index b85ef76bd2d5..4c9328a1c372 100644 --- a/core/java/android/text/SpannableStringInternal.java +++ b/core/java/android/text/SpannableStringInternal.java @@ -40,9 +40,10 @@ import java.lang.reflect.Array; if (source instanceof Spanned) { if (source instanceof SpannableStringInternal) { - copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan); + copySpansFromInternal( + (SpannableStringInternal) source, start, end, ignoreNoCopySpan); } else { - copySpans((Spanned) source, start, end, ignoreNoCopySpan); + copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan); } } } @@ -65,7 +66,7 @@ import java.lang.reflect.Array; * @param end End index in the source object. * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source} */ - private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) { + private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) { Object[] spans = src.getSpans(start, end, Object.class); for (int i = 0; i < spans.length; i++) { @@ -94,7 +95,7 @@ import java.lang.reflect.Array; * @param end End index in the source object. * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons. */ - private void copySpans(SpannableStringInternal src, int start, int end, + private void copySpansFromInternal(SpannableStringInternal src, int start, int end, boolean ignoreNoCopySpan) { int count = 0; final int[] srcData = src.mSpanData; @@ -555,12 +556,12 @@ import java.lang.reflect.Array; */ @UnsupportedAppUsage private void copySpans(Spanned src, int start, int end) { - copySpans(src, start, end, false); + copySpansFromSpanned(src, start, end, false); } @UnsupportedAppUsage private void copySpans(SpannableStringInternal src, int start, int end) { - copySpans(src, start, end, false); + copySpansFromInternal(src, start, end, false); } diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 6fdadc60afea..27edb0b69bdd 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -16,17 +16,28 @@ package android.view; +import android.view.InsetsController.LayoutInsetsDuringAnimation; +import android.view.WindowInsetsAnimationCallback.AnimationBounds; +import android.view.WindowInsetsAnimationCallback.InsetsAnimation; + /** * Provide an interface to let InsetsAnimationControlImpl call back into its owner. * @hide */ public interface InsetsAnimationControlCallbacks { + /** - * Dispatch the animation started event to all listeners. - * @param animation + * Executes the necessary code to start the animation in the correct order, including: + * <ul> + * <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li> + * <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li> + * <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li> + * <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li> + * </ul> */ - void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation, - WindowInsetsAnimationCallback.AnimationBounds bounds); + void startAnimation(InsetsAnimationControlImpl controller, + WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation, + AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation); /** * Schedule the apply by posting the animation callback. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index a3245b9916ec..6589e75c7bc2 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsState.ISIDE_BOTTOM; import static android.view.InsetsState.ISIDE_FLOATING; import static android.view.InsetsState.ISIDE_LEFT; @@ -30,6 +32,7 @@ import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseSetArray; +import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.InsetsState.InternalInsetsSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; @@ -80,7 +83,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, - InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) { + InsetsAnimationControlCallbacks controller, long durationMs, boolean fade, + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { mControls = controls; mListener = listener; mTypes = types; @@ -95,14 +99,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mFrame = new Rect(frame); buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls); - // TODO: Check for controllability first and wait for IME if needed. - listener.onReady(this, types); - mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes, InsetsController.INTERPOLATOR, durationMs); mAnimation.setAlpha(getCurrentAlpha()); - mController.dispatchAnimationStarted(mAnimation, - new AnimationBounds(mHiddenInsets, mShownInsets)); + mController.startAnimation(this, listener, types, mAnimation, + new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation); } @Override diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 2a7a4e3a922c..0207abdda355 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -37,6 +37,7 @@ import android.util.SparseArray; import android.view.InsetsSourceConsumer.ShowResult; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; +import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimationCallback.AnimationBounds; @@ -47,6 +48,8 @@ import android.view.animation.PathInterpolator; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** @@ -67,6 +70,37 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private @interface AnimationDirection{} /** + * Layout mode during insets animation: The views should be laid out as if the changing inset + * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will + * be called as if the changing insets types are shown, which will result in the views being + * laid out as if the insets are fully shown. + */ + static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0; + + /** + * Layout mode during insets animation: The views should be laid out as if the changing inset + * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will + * be called as if the changing insets types are hidden, which will result in the views being + * laid out as if the insets are fully hidden. + */ + static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1; + + /** + * Determines the behavior of how the views should be laid out during an insets animation that + * is controlled by the application by calling {@link #controlWindowInsetsAnimation}. + * <p> + * When the animation is system-initiated, the layout mode is always chosen such that the + * pre-animation layout will represent the opposite of the starting state, i.e. when insets + * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets + * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN, + LAYOUT_INSETS_DURING_ANIMATION_HIDDEN}) + @interface LayoutInsetsDuringAnimation { + } + + /** * Translation animation evaluator. */ private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of( @@ -109,11 +143,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; - if (mShow) { - showDirectly(types); - } else { - hideDirectly(types); - } + mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE; mAnimator = ObjectAnimator.ofObject( controller, @@ -131,7 +161,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation onAnimationFinish(); } }); + mStartingAnimation = true; mAnimator.start(); + mStartingAnimation = false; } @Override @@ -185,6 +217,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private int mPendingTypesToShow; private int mLastLegacySoftInputMode; + private boolean mStartingAnimation; private SyncRtSurfaceTransactionApplier mApplier; @@ -312,7 +345,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // Only one animator (with multiple InsetsType) can run at a time. // previous one should be cancelled for simplicity. cancelExistingAnimation(); - } else if (consumer.isVisible() + } else if (consumer.isRequestedVisible() && (mAnimationDirection == DIRECTION_NONE || mAnimationDirection == DIRECTION_HIDE)) { // no-op: already shown or animating in (because window visibility is @@ -338,7 +371,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); if (mAnimationDirection == DIRECTION_SHOW) { cancelExistingAnimation(); - } else if (!consumer.isVisible() + } else if (!consumer.isRequestedVisible() && (mAnimationDirection == DIRECTION_NONE || mAnimationDirection == DIRECTION_HIDE)) { // no-op: already hidden or animating out. @@ -363,12 +396,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener.onCancelled(); return; } - controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */); + controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */, + getLayoutInsetsDuringAnimationMode(types)); } private void controlAnimationUnchecked(@InsetsType int types, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, - long durationMs, boolean fade) { + long durationMs, boolean fade, + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { if (types == 0) { // nothing to animate. return; @@ -398,7 +433,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls, - frame, mState, listener, typesReady, this, durationMs, fade); + frame, mState, listener, typesReady, this, durationMs, fade, + layoutInsetsDuringAnimation); mAnimationControls.add(controller); } @@ -412,7 +448,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean isReady = true; for (int i = internalTypes.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); - boolean setVisible = !consumer.isVisible(); + boolean setVisible = !consumer.isRequestedVisible(); if (setVisible) { // Show request switch(consumer.requestShow(fromIme)) { @@ -454,6 +490,29 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return typesReady; } + private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode( + @InsetsType int types) { + + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + + // Generally, we want to layout the opposite of the current state. This is to make animation + // callbacks easy to use: The can capture the layout values and then treat that as end-state + // during the animation. + // + // However, if controlling multiple sources, we want to treat it as shown if any of the + // types is currently hidden. + for (int i = internalTypes.size() - 1; i >= 0; i--) { + InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i)); + if (consumer == null) { + continue; + } + if (!consumer.isRequestedVisible()) { + return LAYOUT_INSETS_DURING_ANIMATION_SHOWN; + } + } + return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; + } + private void cancelExistingControllers(@InsetsType int types) { for (int i = mAnimationControls.size() - 1; i >= 0; i--) { InsetsAnimationControlImpl control = mAnimationControls.get(i); @@ -597,7 +656,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // and hidden state insets are correct. controlAnimationUnchecked( types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(), - true /* fade */); + true /* fade */, show + ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN + : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); } private void hideDirectly(@InsetsType int types) { @@ -629,18 +690,40 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting @Override - public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) { - mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds); + public void startAnimation(InsetsAnimationControlImpl controller, + WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation, + AnimationBounds bounds, int layoutDuringAnimation) { + if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) { + showDirectly(types); + } else { + hideDirectly(types); + } + mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation); + mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this); + mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds); + listener.onReady(controller, types); + return true; + } + }); + mViewRoot.mView.invalidate(); } @VisibleForTesting public void dispatchAnimationFinished(InsetsAnimation animation) { - mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation); + mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation); } @VisibleForTesting @Override public void scheduleApplyChangeInsets() { + if (mStartingAnimation) { + mAnimCallback.run(); + mAnimCallbackScheduled = false; + return; + } if (!mAnimCallbackScheduled) { mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION, mAnimCallback, null /* token*/); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index c6d9898a425c..b2a5d915c2a6 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -53,7 +53,7 @@ public class InsetsSourceConsumer { } protected final InsetsController mController; - protected boolean mVisible; + protected boolean mRequestedVisible; private final Supplier<Transaction> mTransactionSupplier; private final @InternalInsetsType int mType; private final InsetsState mState; @@ -66,7 +66,7 @@ public class InsetsSourceConsumer { mState = state; mTransactionSupplier = transactionSupplier; mController = controller; - mVisible = InsetsState.getDefaultVisibility(type); + mRequestedVisible = InsetsState.getDefaultVisibility(type); } public void setControl(@Nullable InsetsSourceControl control) { @@ -94,12 +94,12 @@ public class InsetsSourceConsumer { @VisibleForTesting public void show() { - setVisible(true); + setRequestedVisible(true); } @VisibleForTesting public void hide() { - setVisible(false); + setRequestedVisible(false); } /** @@ -126,16 +126,16 @@ public class InsetsSourceConsumer { if (mSourceControl == null) { return false; } - if (mState.getSource(mType).isVisible() == mVisible) { + if (mState.getSource(mType).isVisible() == mRequestedVisible) { return false; } - mState.getSource(mType).setVisible(mVisible); + mState.getSource(mType).setVisible(mRequestedVisible); return true; } @VisibleForTesting - public boolean isVisible() { - return mVisible; + public boolean isRequestedVisible() { + return mRequestedVisible; } /** @@ -157,11 +157,15 @@ public class InsetsSourceConsumer { // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. } - private void setVisible(boolean visible) { - if (mVisible == visible) { + /** + * Sets requested visibility from the client, regardless of whether we are able to control it at + * the moment. + */ + private void setRequestedVisible(boolean requestedVisible) { + if (mRequestedVisible == requestedVisible) { return; } - mVisible = visible; + mRequestedVisible = requestedVisible; applyLocalVisibilityOverride(); mController.notifyVisibilityChanged(); } @@ -173,7 +177,7 @@ public class InsetsSourceConsumer { } final Transaction t = mTransactionSupplier.get(); - if (mVisible) { + if (mRequestedVisible) { t.show(mSourceControl.getLeash()); } else { t.hide(mSourceControl.getLeash()); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0db80e2749c3..13d609b16541 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11117,7 +11117,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)} + * Dispatches {@link WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)} + * when Window Insets animation is being prepared. + * @param animation current animation + * + * @see WindowInsetsAnimationCallback#onPrepare(InsetsAnimation) + */ + public void dispatchWindowInsetsAnimationPrepare( + @NonNull InsetsAnimation animation) { + if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { + mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation); + } + } + + /** + * Dispatches {@link WindowInsetsAnimationCallback#onStart(InsetsAnimation, AnimationBounds)} * when Window Insets animation is started. * @param animation current animation * @param bounds the upper and lower {@link AnimationBounds} that provides range of @@ -11125,10 +11139,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return the upper and lower {@link AnimationBounds}. */ @NonNull - public AnimationBounds dispatchWindowInsetsAnimationStarted( + public AnimationBounds dispatchWindowInsetsAnimationStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { - return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds); + return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds); } return bounds; } @@ -11149,13 +11163,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)} + * Dispatches {@link WindowInsetsAnimationCallback#onFinish(InsetsAnimation)} * when Window Insets animation finishes. * @param animation The current ongoing {@link InsetsAnimation}. */ - public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) { + public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) { if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { - mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation); + mListenerInfo.mWindowInsetsAnimationCallback.onFinish(animation); } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 5fb71773db8f..047d7da7536f 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7199,13 +7199,23 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override + public void dispatchWindowInsetsAnimationPrepare( + @NonNull InsetsAnimation animation) { + super.dispatchWindowInsetsAnimationPrepare(animation); + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation); + } + } + + @Override @NonNull - public AnimationBounds dispatchWindowInsetsAnimationStarted( + public AnimationBounds dispatchWindowInsetsAnimationStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { - super.dispatchWindowInsetsAnimationStarted(animation, bounds); + super.dispatchWindowInsetsAnimationStart(animation, bounds); final int count = getChildCount(); for (int i = 0; i < count; i++) { - getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds); + getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds); } return bounds; } @@ -7222,11 +7232,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override - public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) { - super.dispatchWindowInsetsAnimationFinished(animation); + public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) { + super.dispatchWindowInsetsAnimationFinish(animation); final int count = getChildCount(); for (int i = 0; i < count; i++) { - getChildAt(i).dispatchWindowInsetsAnimationFinished(animation); + getChildAt(i).dispatchWindowInsetsAnimationFinish(animation); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index bf8dc65abe28..ab89ef46e09e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1464,6 +1464,7 @@ public final class ViewRootImpl implements ViewParent, return; } mApplyInsetsRequested = true; + requestLayout(); // If this changes during traversal, no need to schedule another one as it will dispatch it // during the current traversal. diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java index 5e71f271f1d4..e84c3e33c000 100644 --- a/core/java/android/view/WindowInsetsAnimationCallback.java +++ b/core/java/android/view/WindowInsetsAnimationCallback.java @@ -20,6 +20,7 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Insets; +import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.animation.Interpolator; @@ -30,7 +31,47 @@ import android.view.animation.Interpolator; public interface WindowInsetsAnimationCallback { /** - * Called when an inset animation gets started. + * Called when an insets animation is about to start and before the views have been laid out in + * the end state of the animation. The ordering of events during an insets animation is the + * following: + * <p> + * <ul> + * <li>Application calls {@link WindowInsetsController#hideInputMethod()}, + * {@link WindowInsetsController#showInputMethod()}, + * {@link WindowInsetsController#controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)}</li> + * <li>onPrepare is called on the view hierarchy listeners</li> + * <li>{@link View#onApplyWindowInsets} will be called with the end state of the + * animation</li> + * <li>View hierarchy gets laid out according to the changes the application has requested + * due to the new insets being dispatched</li> + * <li>{@link #onStart} is called <em>before</em> the view + * hierarchy gets drawn in the new laid out state</li> + * <li>{@link #onProgress} is called immediately after with the animation start state</li> + * <li>The frame gets drawn.</li> + * </ul> + * <p> + * This ordering allows the application to inspect the end state after the animation has + * finished, and then revert to the starting state of the animation in the first + * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} and + * related methods. + * <p> + * Note: If the animation is application controlled by using + * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation + * is undefined as the application may decide on the end state only by passing in the + * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this + * situation, the system will dispatch the insets in the opposite visibility state before the + * animation starts. Example: When controlling the input method with + * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently + * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for + * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}. + * + * @param animation The animation that is about to start. + */ + default void onPrepare(@NonNull InsetsAnimation animation) { + } + + /** + * Called when an insets animation gets started. * <p> * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical: * It will starts at the root of the view hierarchy and then traverse it and invoke the callback @@ -45,7 +86,7 @@ public interface WindowInsetsAnimationCallback { * subtree of the hierarchy. */ @NonNull - default AnimationBounds onStarted( + default AnimationBounds onStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { return bounds; } @@ -72,12 +113,12 @@ public interface WindowInsetsAnimationCallback { WindowInsets onProgress(@NonNull WindowInsets insets); /** - * Called when an inset animation has finished. + * Called when an insets animation has finished. * * @param animation The animation that has finished running. This will be the same instance as - * passed into {@link #onStarted} + * passed into {@link #onStart} */ - default void onFinished(@NonNull InsetsAnimation animation) { + default void onFinish(@NonNull InsetsAnimation animation) { } /** @@ -253,14 +294,14 @@ public interface WindowInsetsAnimationCallback { /** * Insets both the lower and upper bound by the specified insets. This is to be used in - * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has + * {@link WindowInsetsAnimationCallback#onStart} to indicate that a part of the insets has * been used to offset or clip its children, and the children shouldn't worry about that * part anymore. * * @param insets The amount to inset. * @return A copy of this instance inset in the given directions. * @see WindowInsets#inset - * @see WindowInsetsAnimationCallback#onStarted + * @see WindowInsetsAnimationCallback#onStart */ @NonNull public AnimationBounds inset(@NonNull Insets insets) { diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java index 8a226c1bbe23..f91254de33ff 100644 --- a/core/java/android/view/WindowInsetsAnimationControlListener.java +++ b/core/java/android/view/WindowInsetsAnimationControlListener.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Hide; import android.annotation.NonNull; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.EditorInfo; @@ -26,6 +27,12 @@ import android.view.inputmethod.EditorInfo; public interface WindowInsetsAnimationControlListener { /** + * @hide + */ + default void onPrepare(int types) { + } + + /** * Called when the animation is ready to be controlled. This may be delayed when the IME needs * to redraw because of an {@link EditorInfo} change, or when the window is starting up. * diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 6de56be2f3c5..9d7f292dbdf5 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -149,7 +149,8 @@ public interface WindowInsetsController { * * @param types The {@link InsetsType}s the application has requested to control. * @param durationMillis duration of animation in - * {@link java.util.concurrent.TimeUnit#MILLISECONDS} + * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the + * animation doesn't have a predetermined duration. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * windows are ready to be controlled, among other callbacks. * @hide @@ -162,7 +163,8 @@ public interface WindowInsetsController { * modifying the position of the IME when it's causing insets. * * @param durationMillis duration of the animation in - * {@link java.util.concurrent.TimeUnit#MILLISECONDS} + * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the + * animation doesn't have a predetermined duration. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * IME are ready to be controlled, among other callbacks. */ diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index ff31bccfa648..2e5a4b57da18 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -31,6 +31,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.app.RemoteAction; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -1209,6 +1210,61 @@ public final class AccessibilityManager { } /** + * Register the provided {@link RemoteAction} with the given actionId + * + * @param action The remote action to be registered with the given actionId as system action. + * @param actionId The id uniquely identify the system action. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + public void registerSystemAction(@NonNull RemoteAction action, int actionId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.registerSystemAction(action, actionId); + + if (DEBUG) { + Log.i(LOG_TAG, "System action " + action.getTitle() + " is registered."); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error registering system action " + action.getTitle() + " ", re); + } + } + + /** + * Unregister a system action with the given actionId + * + * @param actionId The id uniquely identify the system action to be unregistered. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + public void unregisterSystemAction(int actionId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.unregisterSystemAction(actionId); + + if (DEBUG) { + Log.i(LOG_TAG, "System action with actionId " + actionId + " is unregistered."); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error unregistering system action with actionId " + actionId + " ", re); + } + } + + /** * Notifies that the accessibility button in the system's navigation area has been clicked * * @param displayId The logical display id. diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 36515b3ba094..392db574d988 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.app.RemoteAction; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IAccessibilityServiceClient; @@ -82,4 +83,7 @@ interface IAccessibilityManager { int getAccessibilityWindowId(IBinder windowToken); long getRecommendedTimeoutMillis(); + + oneway void registerSystemAction(in RemoteAction action, int actionId); + oneway void unregisterSystemAction(int actionId); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 67ce8d2e49b5..f3007a794344 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -93,9 +93,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** @@ -429,7 +432,10 @@ public final class InputMethodManager { * in a background thread. Later, if there is an actual startInput it will wait on * main thread till the background thread completes. */ - private CompletableFuture<Void> mWindowFocusGainFuture; + private Future<?> mWindowFocusGainFuture; + + private ExecutorService mStartInputWorker = Executors.newSingleThreadExecutor( + new ImeThreadFactory("StartInputWorker")); /** * The instance that has previously been sent to the input method. @@ -790,6 +796,19 @@ public final class InputMethodManager { } } + private static class ImeThreadFactory implements ThreadFactory { + private final String mThreadName; + + ImeThreadFactory(String name) { + mThreadName = name; + } + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, mThreadName); + } + } + final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { @@ -1978,7 +1997,7 @@ public final class InputMethodManager { if (mWindowFocusGainFuture != null) { mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */); } - mWindowFocusGainFuture = CompletableFuture.runAsync(() -> { + mWindowFocusGainFuture = mStartInputWorker.submit(() -> { if (checkFocusNoStartInput(forceNewFocus1)) { // We need to restart input on the current focus view. This // should be done in conjunction with telling the system service diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java index 0a0d05ce8fc8..de204badfd0d 100644 --- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -16,6 +16,7 @@ package com.android.internal.app; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; +import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; import static android.view.accessibility.AccessibilityManager.ShortcutType; import android.accessibilityservice.AccessibilityServiceInfo; @@ -24,13 +25,17 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.os.UserHandle; import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArraySet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -49,7 +54,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.StringJoiner; /** * Activity used to display and persist a service or feature target for the Accessibility button. @@ -59,12 +67,46 @@ public class AccessibilityButtonChooserActivity extends Activity { private static final String MAGNIFICATION_COMPONENT_ID = "com.android.server.accessibility.MagnificationController"; + private static final char SERVICES_SEPARATOR = ':'; + private static final TextUtils.SimpleStringSplitter sStringColonSplitter = + new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR); + private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType( + ACCESSIBILITY_BUTTON); + private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType( + ACCESSIBILITY_SHORTCUT_KEY); + private int mShortcutType; private List<AccessibilityButtonTarget> mTargets = new ArrayList<>(); private AlertDialog mAlertDialog; private TargetAdapter mTargetAdapter; /** + * Annotation for different user shortcut type UI type. + * + * {@code DEFAULT} for displaying default value. + * {@code SOFTWARE} for displaying specifying the accessibility services or features which + * choose accessibility button in the navigation bar as preferred shortcut. + * {@code HARDWARE} for displaying specifying the accessibility services or features which + * choose accessibility shortcut as preferred shortcut. + * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly + * tapping screen 3 times as preferred shortcut. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + UserShortcutType.DEFAULT, + UserShortcutType.SOFTWARE, + UserShortcutType.HARDWARE, + UserShortcutType.TRIPLETAP, + }) + /** Denotes the user shortcut type. */ + public @interface UserShortcutType { + int DEFAULT = 0; + int SOFTWARE = 1; // 1 << 0 + int HARDWARE = 2; // 1 << 1 + int TRIPLETAP = 4; // 1 << 2 + } + + /** * Annotation for different accessibilityService fragment UI type. * * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service @@ -395,19 +437,63 @@ public class AccessibilityButtonChooserActivity extends Activity { } private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) { - // TODO(b/146967898): disable service when deleting the target and the target only have - // last one shortcut item, only remove it from shortcut list otherwise. - if ((mShortcutType == ACCESSIBILITY_BUTTON) && (mTargets.get(position).mFragmentType - != AccessibilityServiceFragmentType.LEGACY)) { - mTargets.remove(position); - mTargetAdapter.notifyDataSetChanged(); + final AccessibilityButtonTarget target = mTargets.get(position); + final ComponentName targetComponentName = + ComponentName.unflattenFromString(target.getId()); + + switch (target.getFragmentType()) { + case AccessibilityServiceFragmentType.INVISIBLE: + onInvisibleTargetDeleted(targetComponentName); + break; + case AccessibilityServiceFragmentType.INTUITIVE: + onIntuitiveTargetDeleted(targetComponentName); + break; + case AccessibilityServiceFragmentType.LEGACY: + case AccessibilityServiceFragmentType.BOUNCE: + // Do nothing + break; + default: + throw new IllegalStateException("Unexpected fragment type"); } + mTargets.remove(position); + mTargetAdapter.notifyDataSetChanged(); + if (mTargets.isEmpty()) { mAlertDialog.dismiss(); } } + private void onInvisibleTargetDeleted(ComponentName componentName) { + if (mShortcutType == ACCESSIBILITY_BUTTON) { + optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName); + + if (!hasValueInSettings(this, + ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) { + setAccessibilityServiceState(this, componentName, /* enabled= */ false); + } + } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); + + if (!hasValueInSettings(this, + ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) { + setAccessibilityServiceState(this, componentName, /* enabled= */ false); + } + } else { + throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); + } + } + + private void onIntuitiveTargetDeleted(ComponentName componentName) { + if (mShortcutType == ACCESSIBILITY_BUTTON) { + optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName); + } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); + } else { + throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); + } + } + private void onCancelButtonClicked() { mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH); mTargetAdapter.notifyDataSetChanged(); @@ -437,4 +523,166 @@ public class AccessibilityButtonChooserActivity extends Activity { mAlertDialog.getListView().setOnItemClickListener( isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected); } + + /** + * @return the set of enabled accessibility services for {@param userId}. If there are no + * services, it returns the unmodifiable {@link Collections#emptySet()}. + */ + private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) { + final String enabledServicesSetting = Settings.Secure.getStringForUser( + context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userId); + if (TextUtils.isEmpty(enabledServicesSetting)) { + return Collections.emptySet(); + } + + final Set<ComponentName> enabledServices = new HashSet<>(); + final TextUtils.StringSplitter colonSplitter = + new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR); + colonSplitter.setString(enabledServicesSetting); + + for (String componentNameString : colonSplitter) { + final ComponentName enabledService = ComponentName.unflattenFromString( + componentNameString); + if (enabledService != null) { + enabledServices.add(enabledService); + } + } + + return enabledServices; + } + + /** + * Changes an accessibility component's state. + */ + private void setAccessibilityServiceState(Context context, ComponentName componentName, + boolean enabled) { + setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId()); + } + + /** + * Changes an accessibility component's state for {@param userId}. + */ + private void setAccessibilityServiceState(Context context, ComponentName componentName, + boolean enabled, int userId) { + Set<ComponentName> enabledServices = getEnabledServicesFromSettings( + context, userId); + + if (enabledServices.isEmpty()) { + enabledServices = new ArraySet<>(/* capacity= */ 1); + } + + if (enabled) { + enabledServices.add(componentName); + } else { + enabledServices.remove(componentName); + } + + final StringBuilder enabledServicesBuilder = new StringBuilder(); + for (ComponentName enabledService : enabledServices) { + enabledServicesBuilder.append(enabledService.flattenToString()); + enabledServicesBuilder.append( + SERVICES_SEPARATOR); + } + + final int enabledServicesBuilderLength = enabledServicesBuilder.length(); + if (enabledServicesBuilderLength > 0) { + enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); + } + + Settings.Secure.putStringForUser(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + enabledServicesBuilder.toString(), userId); + } + + /** + * Opts out component name into colon-separated {@code shortcutType} key's string in Settings. + * + * @param context The current context. + * @param shortcutType The preferred shortcut type user selected. + * @param componentName The component name that need to be opted out from Settings. + */ + private void optOutValueFromSettings( + Context context, int shortcutType, ComponentName componentName) { + final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR)); + final String targetsKey = convertToKey(shortcutType); + final String targetsValue = Settings.Secure.getString(context.getContentResolver(), + targetsKey); + + if (TextUtils.isEmpty(targetsValue)) { + return; + } + + sStringColonSplitter.setString(targetsValue); + while (sStringColonSplitter.hasNext()) { + final String name = sStringColonSplitter.next(); + if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) { + continue; + } + joiner.add(name); + } + + Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString()); + } + + /** + * Returns if component name existed in Settings. + * + * @param context The current context. + * @param shortcutType The preferred shortcut type user selected. + * @param componentName The component name that need to be checked existed in Settings. + * @return {@code true} if componentName existed in Settings. + */ + private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType, + @NonNull ComponentName componentName) { + final String targetKey = convertToKey(shortcutType); + final String targetString = Settings.Secure.getString(context.getContentResolver(), + targetKey); + + if (TextUtils.isEmpty(targetString)) { + return false; + } + + sStringColonSplitter.setString(targetString); + while (sStringColonSplitter.hasNext()) { + final String name = sStringColonSplitter.next(); + if ((componentName.flattenToString()).equals(name)) { + return true; + } + } + + return false; + } + + /** + * Converts {@link UserShortcutType} to key in Settings. + * + * @param type The shortcut type. + * @return Mapping key in Settings. + */ + private String convertToKey(@UserShortcutType int type) { + switch (type) { + case UserShortcutType.SOFTWARE: + return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT; + case UserShortcutType.HARDWARE: + return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; + case UserShortcutType.TRIPLETAP: + return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED; + default: + throw new IllegalArgumentException( + "Unsupported user shortcut type: " + type); + } + } + + private static @UserShortcutType int convertToUserType(@ShortcutType int type) { + switch (type) { + case ACCESSIBILITY_BUTTON: + return UserShortcutType.SOFTWARE; + case ACCESSIBILITY_SHORTCUT_KEY: + return UserShortcutType.HARDWARE; + default: + throw new IllegalArgumentException( + "Unsupported shortcut type:" + type); + } + } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a808ed8fcc81..5aa0a2dda17e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -933,11 +933,10 @@ <!-- Allows an application a broad access to external storage in scoped storage. Intended to be used by few apps that need to manage files on behalf of the users. - <p>Protection level: signature|appop - <p>This protection level is temporary and will most likely be changed to |preinstalled --> + <p>Protection level: signature|appop|preinstalled --> <permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:permissionGroup="android.permission-group.UNDEFINED" - android:protectionLevel="signature|appop" /> + android:protectionLevel="signature|appop|preinstalled" /> <!-- ====================================================================== --> <!-- Permissions for accessing the device location --> diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 0e19ca84d433..d0fd92a838c9 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -90,12 +90,12 @@ public class ImeInsetsSourceConsumerTest { mImeConsumer.onWindowFocusGained(); mImeConsumer.applyImeVisibility(true); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test if setVisibility can hide IME mImeConsumer.applyImeVisibility(false); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); } diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 179929f2aae0..fa61a0a0250b 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -16,11 +16,11 @@ package android.view; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.WindowInsets.Type.systemBars; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -39,8 +39,6 @@ import android.view.SurfaceControl.Transaction; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.test.InsetsModeSession; -import androidx.test.runner.AndroidJUnit4; - import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -52,6 +50,8 @@ import org.mockito.MockitoAnnotations; import java.util.List; +import androidx.test.runner.AndroidJUnit4; + /** * Tests for {@link InsetsAnimationControlImpl}. * @@ -116,7 +116,7 @@ public class InsetsAnimationControlImplTest { mController = new InsetsAnimationControlImpl(controls, new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(), mMockController, 10 /* durationMs */, - false /* fade */); + false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN); } @Test diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index a89fc1e6315f..1db96b15f83a 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -68,6 +68,7 @@ public class InsetsControllerTest { private InsetsController mController; private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; + private ViewRootImpl mViewRoot; @Before public void setup() { @@ -77,13 +78,13 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { Context context = InstrumentationRegistry.getTargetContext(); // cannot mock ViewRootImpl since it's final. - ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); + mViewRoot = new ViewRootImpl(context, context.getDisplay()); try { - viewRootImpl.setView(new TextView(context), new LayoutParams(), null); + mViewRoot.setView(new TextView(context), new LayoutParams(), null); } catch (BadTokenException e) { // activity isn't running, we will ignore BadTokenException. } - mController = new InsetsController(viewRootImpl); + mController = new InsetsController(mViewRoot); final Rect rect = new Rect(5, 5, 5, 5); mController.calculateInsets( false, @@ -117,16 +118,22 @@ public class InsetsControllerTest { @Test public void testControlsRevoked_duringAnim() { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[] { control }); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + InsetsSourceControl control = + new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); + mController.onControlsChanged(new InsetsSourceControl[] { control }); - WindowInsetsAnimationControlListener mockListener = - mock(WindowInsetsAnimationControlListener.class); - mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener); - verify(mockListener).onReady(any(), anyInt()); - mController.onControlsChanged(new InsetsSourceControl[0]); - verify(mockListener).onCancelled(); + WindowInsetsAnimationControlListener mockListener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, + mockListener); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + verify(mockListener).onReady(any(), anyInt()); + mController.onControlsChanged(new InsetsSourceControl[0]); + verify(mockListener).onCancelled(); + }); } @Test @@ -154,16 +161,16 @@ public class InsetsControllerTest { mController.show(Type.all()); // quickly jump to final state by cancelling it. mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.applyImeVisibility(false /* setVisible */); mController.hide(Type.all()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -180,10 +187,10 @@ public class InsetsControllerTest { mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(); mController.applyImeVisibility(true); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.applyImeVisibility(false); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -201,16 +208,16 @@ public class InsetsControllerTest { // test show select types. mController.show(types); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all mController.hide(types); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -227,29 +234,29 @@ public class InsetsControllerTest { // test show select types. mController.show(types); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all mController.hide(Type.all()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test single show mController.show(Type.navigationBars()); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test single hide mController.hide(Type.navigationBars()); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -267,31 +274,31 @@ public class InsetsControllerTest { mController.show(Type.navigationBars()); mController.show(Type.systemBars()); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.navigationBars()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); int types = Type.navigationBars() | Type.systemBars(); // show two at a time and hide one by one. mController.show(types); mController.hide(Type.navigationBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -309,15 +316,15 @@ public class InsetsControllerTest { mController.show(types); mController.hide(Type.navigationBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -336,12 +343,16 @@ public class InsetsControllerTest { ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor = ArgumentCaptor.forClass(WindowInsetsAnimationController.class); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + verify(mockListener).onReady(controllerCaptor.capture(), anyInt()); controllerCaptor.getValue().finish(false /* shown */); }); waitUntilNextFrame(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isVisible()); + assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 7af833bfcba4..492c03653990 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -96,7 +96,7 @@ public class InsetsSourceConsumerTest { @Test public void testHide() { mConsumer.hide(); - assertFalse("Consumer should not be visible", mConsumer.isVisible()); + assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible()); verify(mSpyInsetsSource).setVisible(eq(false)); } @@ -106,7 +106,7 @@ public class InsetsSourceConsumerTest { // Insets source starts out visible mConsumer.hide(); mConsumer.show(); - assertTrue("Consumer should be visible", mConsumer.isVisible()); + assertTrue("Consumer should be visible", mConsumer.isRequestedVisible()); verify(mSpyInsetsSource).setVisible(eq(false)); verify(mSpyInsetsSource).setVisible(eq(true)); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java index b9b6d55b52d6..e23c51e66a02 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.accessibility; +package android.view.accessibility; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertSame; @@ -29,16 +29,17 @@ import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Instrumentation; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.Intent; +import android.graphics.drawable.Icon; import android.os.UserHandle; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.IAccessibilityManager; -import android.view.accessibility.IAccessibilityManagerClient; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.IntPair; +import com.android.server.accessibility.test.MessageCapturingHandler; import org.junit.After; import org.junit.Before; @@ -57,6 +58,16 @@ import java.util.List; public class AccessibilityManagerTest { private static final boolean WITH_A11Y_ENABLED = true; private static final boolean WITH_A11Y_DISABLED = false; + private static final String LABEL = "label"; + private static final String INTENT_ACTION = "TESTACTION"; + private static final String DESCRIPTION = "description"; + private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast( + InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0); + private static final RemoteAction TEST_ACTION = new RemoteAction( + Icon.createWithContentUri("content://test"), + LABEL, + DESCRIPTION, + TEST_PENDING_INTENT); @Mock private IAccessibilityManager mMockService; private MessageCapturingHandler mHandler; @@ -122,6 +133,29 @@ public class AccessibilityManagerTest { } @Test + public void testRegisterSystemAction() throws Exception { + AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); + RemoteAction action = new RemoteAction( + Icon.createWithContentUri("content://test"), + LABEL, + DESCRIPTION, + TEST_PENDING_INTENT); + final int actionId = 0; + manager.registerSystemAction(TEST_ACTION, actionId); + + verify(mMockService).registerSystemAction(TEST_ACTION, actionId); + } + + @Test + public void testUnregisterSystemAction() throws Exception { + AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); + final int actionId = 0; + manager.unregisterSystemAction(actionId); + + verify(mMockService).unregisterSystemAction(actionId); + } + + @Test public void testIsEnabled() throws Exception { // Create manager with a11y enabled AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index eb1d1ab1089c..9930ea262b32 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -243,6 +243,7 @@ applications that come with the platform <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> + <permission name="android.permission.TETHER_PRIVILEGED"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> </privapp-permissions> diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java index 1370b1095ae1..26f73f784879 100644 --- a/location/java/android/location/Criteria.java +++ b/location/java/android/location/Criteria.java @@ -16,9 +16,16 @@ package android.location; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A class indicating the application criteria for selecting a * location provider. Providers may be ordered according to accuracy, @@ -26,6 +33,25 @@ import android.os.Parcelable; * cost. */ public class Criteria implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, POWER_HIGH}) + public @interface PowerRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH}) + public @interface AccuracyRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, ACCURACY_FINE, ACCURACY_COARSE}) + public @interface LocationAccuracyRequirement { + } + /** * A constant indicating that the application does not choose to * place requirement on a particular feature. @@ -81,15 +107,15 @@ public class Criteria implements Parcelable { */ public static final int ACCURACY_HIGH = 3; - private int mHorizontalAccuracy = NO_REQUIREMENT; - private int mVerticalAccuracy = NO_REQUIREMENT; - private int mSpeedAccuracy = NO_REQUIREMENT; - private int mBearingAccuracy = NO_REQUIREMENT; - private int mPowerRequirement = NO_REQUIREMENT; - private boolean mAltitudeRequired = false; - private boolean mBearingRequired = false; - private boolean mSpeedRequired = false; - private boolean mCostAllowed = false; + private int mHorizontalAccuracy = NO_REQUIREMENT; + private int mVerticalAccuracy = NO_REQUIREMENT; + private int mSpeedAccuracy = NO_REQUIREMENT; + private int mBearingAccuracy = NO_REQUIREMENT; + private int mPowerRequirement = NO_REQUIREMENT; + private boolean mAltitudeRequired = false; + private boolean mBearingRequired = false; + private boolean mSpeedRequired = false; + private boolean mCostAllowed = false; /** * Constructs a new Criteria object. The new object will have no @@ -97,7 +123,8 @@ public class Criteria implements Parcelable { * require altitude, speed, or bearing; and will not allow monetary * cost. */ - public Criteria() {} + public Criteria() { + } /** * Constructs a new Criteria object that is a copy of the given criteria. @@ -115,125 +142,121 @@ public class Criteria implements Parcelable { } /** - * Indicates the desired horizontal accuracy (latitude and longitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired horizontal accuracy (latitude and longitude). Accuracy may be + * {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or + * {@link #NO_REQUIREMENT}. More accurate location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setHorizontalAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mHorizontalAccuracy = accuracy; + public void setHorizontalAccuracy(@AccuracyRequirement int accuracy) { + mHorizontalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired horizontal accuracy (latitude and longitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. + * + * @see #setHorizontalAccuracy(int) */ + @AccuracyRequirement public int getHorizontalAccuracy() { return mHorizontalAccuracy; } /** - * Indicates the desired vertical accuracy (altitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired vertical accuracy (altitude). Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setVerticalAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mVerticalAccuracy = accuracy; + public void setVerticalAccuracy(@AccuracyRequirement int accuracy) { + mVerticalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired vertical accuracy (altitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * + * @see #setVerticalAccuracy(int) */ + @AccuracyRequirement public int getVerticalAccuracy() { return mVerticalAccuracy; } /** - * Indicates the desired speed accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired speed accuracy. Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setSpeedAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mSpeedAccuracy = accuracy; + public void setSpeedAccuracy(@AccuracyRequirement int accuracy) { + mSpeedAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_HIGH, + "accuracy"); } /** - * Returns a constant indicating the desired speed accuracy - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * Returns a constant indicating the desired speed accuracy. + * + * @see #setSpeedAccuracy(int) */ + @AccuracyRequirement public int getSpeedAccuracy() { return mSpeedAccuracy; } /** - * Indicates the desired bearing accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired bearing accuracy. Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setBearingAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mBearingAccuracy = accuracy; + public void setBearingAccuracy(@AccuracyRequirement int accuracy) { + mBearingAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired bearing accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * + * @see #setBearingAccuracy(int) */ + @AccuracyRequirement public int getBearingAccuracy() { return mBearingAccuracy; } /** - * Indicates the desired accuracy for latitude and longitude. Accuracy - * may be {@link #ACCURACY_FINE} if desired location - * is fine, else it can be {@link #ACCURACY_COARSE}. - * More accurate location may consume more power and may take longer. + * Indicates the desired accuracy for latitude and longitude. Accuracy may be + * {@link #ACCURACY_FINE} or {@link #ACCURACY_COARSE}. More accurate location may consume more + * power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - if (accuracy == ACCURACY_FINE) { - mHorizontalAccuracy = ACCURACY_HIGH; - } else { - mHorizontalAccuracy = ACCURACY_LOW; + public void setAccuracy(@LocationAccuracyRequirement int accuracy) { + Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_COARSE, "accuracy"); + switch (accuracy) { + case NO_REQUIREMENT: + setHorizontalAccuracy(NO_REQUIREMENT); + break; + case ACCURACY_FINE: + setHorizontalAccuracy(ACCURACY_HIGH); + break; + case ACCURACY_COARSE: + setHorizontalAccuracy(ACCURACY_LOW); + break; } } /** - * Returns a constant indicating desired accuracy of location - * Accuracy may be {@link #ACCURACY_FINE} if desired location - * is fine, else it can be {@link #ACCURACY_COARSE}. + * Returns a constant indicating desired accuracy of location. + * + * @see #setAccuracy(int) */ + @LocationAccuracyRequirement public int getAccuracy() { if (mHorizontalAccuracy >= ACCURACY_HIGH) { return ACCURACY_FINE; @@ -243,21 +266,20 @@ public class Criteria implements Parcelable { } /** - * Indicates the desired maximum power level. The level parameter - * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or - * POWER_HIGH. + * Indicates the desired maximum power requirement. The power requirement parameter may be + * {@link #NO_REQUIREMENT}, {@link #POWER_LOW}, {@link #POWER_MEDIUM}, or {@link #POWER_HIGH}. */ - public void setPowerRequirement(int level) { - if (level < NO_REQUIREMENT || level > POWER_HIGH) { - throw new IllegalArgumentException("level=" + level); - } - mPowerRequirement = level; + public void setPowerRequirement(@PowerRequirement int powerRequirement) { + mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, NO_REQUIREMENT, + POWER_HIGH, "powerRequirement"); } /** - * Returns a constant indicating the desired power requirement. The - * returned + * Returns a constant indicating the desired maximum power requirement. + * + * @see #setPowerRequirement(int) */ + @PowerRequirement public int getPowerRequirement() { return mPowerRequirement; } @@ -277,8 +299,8 @@ public class Criteria implements Parcelable { } /** - * Indicates whether the provider must provide altitude information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide altitude information. Not all fixes are + * guaranteed to contain such information. */ public void setAltitudeRequired(boolean altitudeRequired) { mAltitudeRequired = altitudeRequired; @@ -286,15 +308,16 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide altitude information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setAltitudeRequired(boolean) */ public boolean isAltitudeRequired() { return mAltitudeRequired; } /** - * Indicates whether the provider must provide speed information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide speed information. Not all fixes are guaranteed + * to contain such information. */ public void setSpeedRequired(boolean speedRequired) { mSpeedRequired = speedRequired; @@ -302,15 +325,16 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide speed information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setSpeedRequired(boolean) */ public boolean isSpeedRequired() { return mSpeedRequired; } /** - * Indicates whether the provider must provide bearing information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide bearing information. Not all fixes are guaranteed + * to contain such information. */ public void setBearingRequired(boolean bearingRequired) { mBearingRequired = bearingRequired; @@ -318,34 +342,36 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide bearing information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setBearingRequired(boolean) */ public boolean isBearingRequired() { return mBearingRequired; } - public static final @android.annotation.NonNull Parcelable.Creator<Criteria> CREATOR = - new Parcelable.Creator<Criteria>() { - @Override - public Criteria createFromParcel(Parcel in) { - Criteria c = new Criteria(); - c.mHorizontalAccuracy = in.readInt(); - c.mVerticalAccuracy = in.readInt(); - c.mSpeedAccuracy = in.readInt(); - c.mBearingAccuracy = in.readInt(); - c.mPowerRequirement = in.readInt(); - c.mAltitudeRequired = in.readInt() != 0; - c.mBearingRequired = in.readInt() != 0; - c.mSpeedRequired = in.readInt() != 0; - c.mCostAllowed = in.readInt() != 0; - return c; - } - - @Override - public Criteria[] newArray(int size) { - return new Criteria[size]; - } - }; + @NonNull + public static final Parcelable.Creator<Criteria> CREATOR = + new Parcelable.Creator<Criteria>() { + @Override + public Criteria createFromParcel(Parcel in) { + Criteria c = new Criteria(); + c.mHorizontalAccuracy = in.readInt(); + c.mVerticalAccuracy = in.readInt(); + c.mSpeedAccuracy = in.readInt(); + c.mBearingAccuracy = in.readInt(); + c.mPowerRequirement = in.readInt(); + c.mAltitudeRequired = in.readInt() != 0; + c.mBearingRequired = in.readInt() != 0; + c.mSpeedRequired = in.readInt() != 0; + c.mCostAllowed = in.readInt() != 0; + return c; + } + + @Override + public Criteria[] newArray(int size) { + return new Criteria[size]; + } + }; @Override public int describeContents() { @@ -365,42 +391,57 @@ public class Criteria implements Parcelable { parcel.writeInt(mCostAllowed ? 1 : 0); } - private static String powerToString(int power) { + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Criteria["); + s.append("power=").append(requirementToString(mPowerRequirement)).append(", "); + s.append("accuracy=").append(requirementToString(mHorizontalAccuracy)); + if (mVerticalAccuracy != NO_REQUIREMENT) { + s.append(", verticalAccuracy=").append(requirementToString(mVerticalAccuracy)); + } + if (mSpeedAccuracy != NO_REQUIREMENT) { + s.append(", speedAccuracy=").append(requirementToString(mSpeedAccuracy)); + } + if (mBearingAccuracy != NO_REQUIREMENT) { + s.append(", bearingAccuracy=").append(requirementToString(mBearingAccuracy)); + } + if (mAltitudeRequired || mBearingRequired || mSpeedRequired) { + s.append(", required=["); + if (mAltitudeRequired) { + s.append("altitude, "); + } + if (mBearingRequired) { + s.append("bearing, "); + } + if (mSpeedRequired) { + s.append("speed, "); + } + s.setLength(s.length() - 2); + s.append("]"); + } + if (mCostAllowed) { + s.append(", costAllowed"); + } + s.append(']'); + return s.toString(); + } + + private static String requirementToString(int power) { switch (power) { case NO_REQUIREMENT: - return "NO_REQ"; + return "None"; + //case ACCURACY_LOW: case POWER_LOW: - return "LOW"; + return "Low"; + //case ACCURACY_MEDIUM: case POWER_MEDIUM: - return "MEDIUM"; + return "Medium"; + //case ACCURACY_HIGH: case POWER_HIGH: - return "HIGH"; + return "High"; default: return "???"; } } - - private static String accuracyToString(int accuracy) { - switch (accuracy) { - case NO_REQUIREMENT: - return "---"; - case ACCURACY_HIGH: - return "HIGH"; - case ACCURACY_MEDIUM: - return "MEDIUM"; - case ACCURACY_LOW: - return "LOW"; - default: - return "???"; - } - } - - @Override - public String toString() { - StringBuilder s = new StringBuilder(); - s.append("Criteria[power=").append(powerToString(mPowerRequirement)); - s.append(" acc=").append(accuracyToString(mHorizontalAccuracy)); - s.append(']'); - return s.toString(); - } } diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java index def96f0fb674..68f9ec3c530b 100644 --- a/location/java/com/android/internal/location/ProviderProperties.java +++ b/location/java/com/android/internal/location/ProviderProperties.java @@ -16,15 +16,36 @@ package com.android.internal.location; +import android.annotation.IntDef; +import android.location.Criteria; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A Parcelable containing (legacy) location provider properties. * This object is just used inside the framework and system services. + * * @hide */ public final class ProviderProperties implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Criteria.POWER_LOW, Criteria.POWER_MEDIUM, Criteria.POWER_HIGH}) + public @interface PowerRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Criteria.ACCURACY_FINE, Criteria.ACCURACY_COARSE}) + public @interface Accuracy { + } + /** * True if provider requires access to a * data network (e.g., the Internet), false otherwise. @@ -79,58 +100,58 @@ public final class ProviderProperties implements Parcelable { /** * Power requirement for this provider. - * - * @return the power requirement for this provider, as one of the - * constants Criteria.POWER_*. */ + @PowerRequirement public final int mPowerRequirement; /** * Constant describing the horizontal accuracy returned * by this provider. - * - * @return the horizontal accuracy for this provider, as one of the - * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE */ + @Accuracy public final int mAccuracy; - public ProviderProperties(boolean mRequiresNetwork, - boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost, - boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing, - int mPowerRequirement, int mAccuracy) { - this.mRequiresNetwork = mRequiresNetwork; - this.mRequiresSatellite = mRequiresSatellite; - this.mRequiresCell = mRequiresCell; - this.mHasMonetaryCost = mHasMonetaryCost; - this.mSupportsAltitude = mSupportsAltitude; - this.mSupportsSpeed = mSupportsSpeed; - this.mSupportsBearing = mSupportsBearing; - this.mPowerRequirement = mPowerRequirement; - this.mAccuracy = mAccuracy; + public ProviderProperties(boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, @PowerRequirement int powerRequirement, + @Accuracy int accuracy) { + mRequiresNetwork = requiresNetwork; + mRequiresSatellite = requiresSatellite; + mRequiresCell = requiresCell; + mHasMonetaryCost = hasMonetaryCost; + mSupportsAltitude = supportsAltitude; + mSupportsSpeed = supportsSpeed; + mSupportsBearing = supportsBearing; + mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, Criteria.POWER_LOW, + Criteria.POWER_HIGH, "powerRequirement"); + mAccuracy = Preconditions.checkArgumentInRange(accuracy, Criteria.ACCURACY_FINE, + Criteria.ACCURACY_COARSE, "accuracy"); } public static final Parcelable.Creator<ProviderProperties> CREATOR = new Parcelable.Creator<ProviderProperties>() { - @Override - public ProviderProperties createFromParcel(Parcel in) { - boolean requiresNetwork = in.readInt() == 1; - boolean requiresSatellite = in.readInt() == 1; - boolean requiresCell = in.readInt() == 1; - boolean hasMonetaryCost = in.readInt() == 1; - boolean supportsAltitude = in.readInt() == 1; - boolean supportsSpeed = in.readInt() == 1; - boolean supportsBearing = in.readInt() == 1; - int powerRequirement = in.readInt(); - int accuracy = in.readInt(); - return new ProviderProperties(requiresNetwork, requiresSatellite, - requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing, - powerRequirement, accuracy); - } - @Override - public ProviderProperties[] newArray(int size) { - return new ProviderProperties[size]; - } - }; + @Override + public ProviderProperties createFromParcel(Parcel in) { + boolean requiresNetwork = in.readInt() == 1; + boolean requiresSatellite = in.readInt() == 1; + boolean requiresCell = in.readInt() == 1; + boolean hasMonetaryCost = in.readInt() == 1; + boolean supportsAltitude = in.readInt() == 1; + boolean supportsSpeed = in.readInt() == 1; + boolean supportsBearing = in.readInt() == 1; + int powerRequirement = in.readInt(); + int accuracy = in.readInt(); + return new ProviderProperties(requiresNetwork, requiresSatellite, + requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, + supportsBearing, + powerRequirement, accuracy); + } + + @Override + public ProviderProperties[] newArray(int size) { + return new ProviderProperties[size]; + } + }; @Override public int describeContents() { @@ -149,4 +170,67 @@ public final class ProviderProperties implements Parcelable { parcel.writeInt(mPowerRequirement); parcel.writeInt(mAccuracy); } + + @Override + public String toString() { + StringBuilder b = new StringBuilder("ProviderProperties["); + b.append("power=").append(powerToString(mPowerRequirement)).append(", "); + b.append("accuracy=").append(accuracyToString(mAccuracy)); + if (mRequiresNetwork || mRequiresSatellite || mRequiresCell) { + b.append(", requires="); + if (mRequiresNetwork) { + b.append("network,"); + } + if (mRequiresSatellite) { + b.append("satellite,"); + } + if (mRequiresCell) { + b.append("cell,"); + } + b.setLength(b.length() - 1); + } + if (mHasMonetaryCost) { + b.append(", hasMonetaryCost"); + } + if (mSupportsBearing || mSupportsSpeed || mSupportsAltitude) { + b.append(", supports=["); + if (mSupportsBearing) { + b.append("bearing, "); + } + if (mSupportsSpeed) { + b.append("speed, "); + } + if (mSupportsAltitude) { + b.append("altitude, "); + } + b.setLength(b.length() - 2); + b.append("]"); + } + b.append("]"); + return b.toString(); + } + + private static String powerToString(@PowerRequirement int power) { + switch (power) { + case Criteria.POWER_LOW: + return "Low"; + case Criteria.POWER_MEDIUM: + return "Medium"; + case Criteria.POWER_HIGH: + return "High"; + default: + return "???"; + } + } + + private static String accuracyToString(@Accuracy int accuracy) { + switch (accuracy) { + case Criteria.ACCURACY_COARSE: + return "Coarse"; + case Criteria.ACCURACY_FINE: + return "Fine"; + default: + return "???"; + } + } } diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index c23f49976799..8d8df4533ebe 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -20,33 +20,42 @@ import android.compat.annotation.UnsupportedAppUsage; import android.location.LocationRequest; import android.os.Parcel; import android.os.Parcelable; +import android.os.WorkSource; import android.util.TimeUtils; +import com.android.internal.util.Preconditions; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @hide */ public final class ProviderRequest implements Parcelable { + + public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(false, Long.MAX_VALUE, + false, false, + Collections.emptyList(), new WorkSource()); + /** Location reporting is requested (true) */ @UnsupportedAppUsage - public boolean reportLocation = false; + public final boolean reportLocation; /** The smallest requested interval */ @UnsupportedAppUsage - public long interval = Long.MAX_VALUE; + public final long interval; /** - * When this flag is true, providers should ignore all location settings, user consents, power - * restrictions or any other restricting factors and always satisfy this request to the best of - * their ability. This flag should only be used in event of an emergency. + * Whether provider shall make stronger than normal tradeoffs to substantially restrict power + * use. */ - public boolean locationSettingsIgnored = false; + public final boolean lowPowerMode; /** - * Whether provider shall make stronger than normal tradeoffs to substantially restrict power - * use. + * When this flag is true, providers should ignore all location settings, user consents, power + * restrictions or any other restricting factors and always satisfy this request to the best of + * their ability. This flag should only be used in event of an emergency. */ - public boolean lowPowerMode = false; + public final boolean locationSettingsIgnored; /** * A more detailed set of requests. @@ -56,26 +65,37 @@ public final class ProviderRequest implements Parcelable { * low power fast interval request. */ @UnsupportedAppUsage - public final List<LocationRequest> locationRequests = new ArrayList<>(); + public final List<LocationRequest> locationRequests; - @UnsupportedAppUsage - public ProviderRequest() { + public final WorkSource workSource; + + private ProviderRequest(boolean reportLocation, long interval, boolean lowPowerMode, + boolean locationSettingsIgnored, List<LocationRequest> locationRequests, + WorkSource workSource) { + this.reportLocation = reportLocation; + this.interval = interval; + this.lowPowerMode = lowPowerMode; + this.locationSettingsIgnored = locationSettingsIgnored; + this.locationRequests = Preconditions.checkNotNull(locationRequests); + this.workSource = Preconditions.checkNotNull(workSource); } public static final Parcelable.Creator<ProviderRequest> CREATOR = new Parcelable.Creator<ProviderRequest>() { @Override public ProviderRequest createFromParcel(Parcel in) { - ProviderRequest request = new ProviderRequest(); - request.reportLocation = in.readInt() == 1; - request.interval = in.readLong(); - request.lowPowerMode = in.readBoolean(); - request.locationSettingsIgnored = in.readBoolean(); + boolean reportLocation = in.readInt() == 1; + long interval = in.readLong(); + boolean lowPowerMode = in.readBoolean(); + boolean locationSettingsIgnored = in.readBoolean(); int count = in.readInt(); + ArrayList<LocationRequest> locationRequests = new ArrayList<>(count); for (int i = 0; i < count; i++) { - request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); + locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); } - return request; + WorkSource workSource = in.readParcelable(null); + return new ProviderRequest(reportLocation, interval, lowPowerMode, + locationSettingsIgnored, locationRequests, workSource); } @Override @@ -106,14 +126,13 @@ public final class ProviderRequest implements Parcelable { StringBuilder s = new StringBuilder(); s.append("ProviderRequest["); if (reportLocation) { - s.append("ON"); - s.append(" interval="); + s.append("interval="); TimeUtils.formatDuration(interval, s); if (lowPowerMode) { - s.append(" lowPowerMode"); + s.append(", lowPowerMode"); } if (locationSettingsIgnored) { - s.append(" locationSettingsIgnored"); + s.append(", locationSettingsIgnored"); } } else { s.append("OFF"); @@ -121,4 +140,67 @@ public final class ProviderRequest implements Parcelable { s.append(']'); return s.toString(); } + + /** + * A Builder for {@link ProviderRequest}s. + */ + public static class Builder { + private long mInterval = Long.MAX_VALUE; + private boolean mLowPowerMode; + private boolean mLocationSettingsIgnored; + private List<LocationRequest> mLocationRequests = Collections.emptyList(); + private WorkSource mWorkSource = new WorkSource(); + + public long getInterval() { + return mInterval; + } + + public void setInterval(long interval) { + this.mInterval = interval; + } + + public boolean isLowPowerMode() { + return mLowPowerMode; + } + + public void setLowPowerMode(boolean lowPowerMode) { + this.mLowPowerMode = lowPowerMode; + } + + public boolean isLocationSettingsIgnored() { + return mLocationSettingsIgnored; + } + + public void setLocationSettingsIgnored(boolean locationSettingsIgnored) { + this.mLocationSettingsIgnored = locationSettingsIgnored; + } + + public List<LocationRequest> getLocationRequests() { + return mLocationRequests; + } + + public void setLocationRequests(List<LocationRequest> locationRequests) { + this.mLocationRequests = Preconditions.checkNotNull(locationRequests); + } + + public WorkSource getWorkSource() { + return mWorkSource; + } + + public void setWorkSource(WorkSource workSource) { + mWorkSource = Preconditions.checkNotNull(workSource); + } + + /** + * Builds a ProviderRequest object with the set information. + */ + public ProviderRequest build() { + if (mInterval == Long.MAX_VALUE) { + return EMPTY_REQUEST; + } else { + return new ProviderRequest(true, mInterval, mLowPowerMode, + mLocationSettingsIgnored, mLocationRequests, mWorkSource); + } + } + } } diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 51fa4eeaf4d8..28bf84d6e079 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -24,13 +24,12 @@ import android.media.IMediaRoute2ProviderClient; */ oneway interface IMediaRoute2Provider { void setClient(IMediaRoute2ProviderClient client); - void requestCreateSession(String packageName, String routeId, - String routeType, long requestId); - void releaseSession(int sessionId); + void requestCreateSession(String packageName, String routeId, String routeType, long requestId); + void releaseSession(String sessionId); - void selectRoute(int sessionId, String routeId); - void deselectRoute(int sessionId, String routeId); - void transferToRoute(int sessionId, String routeId); + void selectRoute(String sessionId, String routeId); + void deselectRoute(String sessionId, String routeId); + void transferToRoute(String sessionId, String routeId); void notifyControlRequestSent(String id, in Intent request); void requestSetVolume(String id, int volume); diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 13640a438e7b..1ed53d942b63 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.MediaRouter2Utils.toUniqueId; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -191,13 +193,6 @@ public final class MediaRoute2Info implements Parcelable { } /** - * @hide - */ - public static String toUniqueId(String providerId, String routeId) { - return providerId + ":" + routeId; - } - - /** * Returns true if the route info has all of the required field. * A route info only obtained from {@link com.android.server.media.MediaRouterService} * is valid. diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 91cc44807a73..24b65baebcbd 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -29,6 +29,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -55,7 +56,7 @@ public abstract class MediaRoute2ProviderService extends Service { private MediaRoute2ProviderInfo mProviderInfo; @GuardedBy("mSessionLock") - private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>(); + private ArrayMap<String, RouteSessionInfo> mSessionInfo = new ArrayMap<>(); public MediaRoute2ProviderService() { mHandler = new Handler(Looper.getMainLooper()); @@ -106,7 +107,10 @@ public abstract class MediaRoute2ProviderService extends Service { * null if the session is destroyed or id is not valid. */ @Nullable - public final RouteSessionInfo getSessionInfo(int sessionId) { + public final RouteSessionInfo getSessionInfo(@NonNull String sessionId) { + if (TextUtils.isEmpty(sessionId)) { + throw new IllegalArgumentException("sessionId must not be empty"); + } synchronized (mSessionLock) { return mSessionInfo.get(sessionId); } @@ -134,7 +138,7 @@ public abstract class MediaRoute2ProviderService extends Service { */ public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); if (sessionInfo.getSelectedRoutes().isEmpty()) { releaseSession(sessionId); return; @@ -160,7 +164,7 @@ public abstract class MediaRoute2ProviderService extends Service { public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { mSessionInfo.put(sessionId, sessionInfo); @@ -185,7 +189,7 @@ public abstract class MediaRoute2ProviderService extends Service { * controlled, pass a {@link Bundle} that contains how to control it. * * @param sessionInfo information of the new session. - * The {@link RouteSessionInfo#getSessionId() id} of the session must be + * The {@link RouteSessionInfo#getId() id} of the session must be * unique. Pass {@code null} to reject the request or inform clients that * session creation is failed. * @param requestId id of the previous request to create this session @@ -194,13 +198,13 @@ public abstract class MediaRoute2ProviderService extends Service { // TODO: Maybe better to create notifySessionCreationFailed? public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { if (sessionInfo != null) { - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { Log.w(TAG, "Ignoring duplicate session id."); return; } - mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo); + mSessionInfo.put(sessionInfo.getId(), sessionInfo); } schedulePublishState(); } @@ -220,9 +224,12 @@ public abstract class MediaRoute2ProviderService extends Service { * {@link #onDestroySession} is called if the session is released. * * @param sessionId id of the session to be released - * @see #onDestroySession(int, RouteSessionInfo) + * @see #onDestroySession(String, RouteSessionInfo) */ - public final void releaseSession(int sessionId) { + public final void releaseSession(@NonNull String sessionId) { + if (TextUtils.isEmpty(sessionId)) { + throw new IllegalArgumentException("sessionId must not be empty"); + } //TODO: notify media router service of release. RouteSessionInfo sessionInfo; synchronized (mSessionLock) { @@ -259,9 +266,10 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param sessionId id of the session being destroyed. * @param lastSessionInfo information of the session being destroyed. - * @see #releaseSession(int) + * @see #releaseSession(String) */ - public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo); + public abstract void onDestroySession(@NonNull String sessionId, + @NonNull RouteSessionInfo lastSessionInfo); //TODO: make a way to reject the request /** @@ -274,7 +282,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param routeId id of the route * @see #updateSessionInfo(RouteSessionInfo) */ - public abstract void onSelectRoute(int sessionId, @NonNull String routeId); + public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** @@ -286,7 +294,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param sessionId id of the session * @param routeId id of the route */ - public abstract void onDeselectRoute(int sessionId, @NonNull String routeId); + public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** @@ -298,7 +306,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param sessionId id of the session * @param routeId id of the route */ - public abstract void onTransferToRoute(int sessionId, @NonNull String routeId); + public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId); /** * Called when the {@link RouteDiscoveryRequest discovery request} has changed. @@ -385,37 +393,53 @@ public abstract class MediaRoute2ProviderService extends Service { requestId)); } @Override - public void releaseSession(int sessionId) { + public void releaseSession(@NonNull String sessionId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession, MediaRoute2ProviderService.this, sessionId)); } @Override - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } @Override - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } @Override - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index dea8b045e72a..8ebf6174cabf 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -342,8 +342,7 @@ public class MediaRouter2 { final int requestId; requestId = mSessionCreationRequestCnt.getAndIncrement(); - SessionCreationRequest request = new SessionCreationRequest( - requestId, route, routeType); + SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType); mSessionCreationRequests.add(request); Client2 client; @@ -352,8 +351,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.requestCreateSession( - client, route, routeType, requestId); + mMediaRouterService.requestCreateSession(client, route, routeType, requestId); } catch (RemoteException ex) { Log.e(TAG, "Unable to request to create session.", ex); mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler, @@ -542,7 +540,7 @@ public class MediaRouter2 { if (sessionInfo != null) { RouteSessionController controller = new RouteSessionController(sessionInfo); synchronized (sRouterLock) { - mSessionControllers.put(controller.getUniqueSessionId(), controller); + mSessionControllers.put(controller.getSessionId(), controller); } notifySessionCreated(controller); } @@ -556,12 +554,12 @@ public class MediaRouter2 { RouteSessionController matchingController; synchronized (sRouterLock) { - matchingController = mSessionControllers.get(sessionInfo.getUniqueSessionId()); + matchingController = mSessionControllers.get(sessionInfo.getId()); } if (matchingController == null) { Log.w(TAG, "changeSessionInfoOnHandler: Matching controller not found. uniqueSessionId=" - + sessionInfo.getUniqueSessionId()); + + sessionInfo.getId()); return; } @@ -582,7 +580,7 @@ public class MediaRouter2 { return; } - final String uniqueSessionId = sessionInfo.getUniqueSessionId(); + final String uniqueSessionId = sessionInfo.getId(); RouteSessionController matchingController; synchronized (sRouterLock) { matchingController = mSessionControllers.get(uniqueSessionId); @@ -591,7 +589,7 @@ public class MediaRouter2 { if (matchingController == null) { if (DEBUG) { Log.d(TAG, "releaseControllerOnHandler: Matching controller not found. " - + "uniqueSessionId=" + sessionInfo.getUniqueSessionId()); + + "uniqueSessionId=" + sessionInfo.getId()); } return; } @@ -783,20 +781,9 @@ public class MediaRouter2 { /** * @return the ID of the session */ - public int getSessionId() { + public String getSessionId() { synchronized (mControllerLock) { - return mSessionInfo.getSessionId(); - } - } - - /** - * @return the unique ID of the session - * @hide - */ - @NonNull - public String getUniqueSessionId() { - synchronized (mControllerLock) { - return mSessionInfo.getUniqueSessionId(); + return mSessionInfo.getId(); } } @@ -913,7 +900,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.selectRoute(client, getUniqueSessionId(), route); + mMediaRouterService.selectRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to select route for session.", ex); } @@ -960,7 +947,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.deselectRoute(client, getUniqueSessionId(), route); + mMediaRouterService.deselectRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to remove route from session.", ex); } @@ -1008,7 +995,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.transferToRoute(client, getUniqueSessionId(), route); + mMediaRouterService.transferToRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to transfer to route for session.", ex); } @@ -1033,12 +1020,12 @@ public class MediaRouter2 { Client2 client; synchronized (sRouterLock) { - mSessionControllers.remove(getUniqueSessionId(), this); + mSessionControllers.remove(getSessionId(), this); client = mClient; } if (client != null) { try { - mMediaRouterService.releaseSession(client, getUniqueSessionId()); + mMediaRouterService.releaseSession(client, getSessionId()); } catch (RemoteException ex) { Log.e(TAG, "Unable to notify of controller release", ex); } @@ -1068,6 +1055,7 @@ public class MediaRouter2 { List<MediaRoute2Info> routes = new ArrayList<>(); synchronized (sRouterLock) { + // TODO: Maybe able to change using Collection.stream()? for (String routeId : routeIds) { MediaRoute2Info route = mRoutes.get(routeId); if (route != null) { diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java new file mode 100644 index 000000000000..49045828dbe8 --- /dev/null +++ b/media/java/android/media/MediaRouter2Utils.java @@ -0,0 +1,100 @@ +/* + * 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 android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +/** + * @hide + */ +public class MediaRouter2Utils { + + static final String TAG = "MR2Utils"; + static final String SEPARATOR = ":"; + + /** + * @hide + */ + @NonNull + public static String toUniqueId(@NonNull String providerId, @NonNull String id) { + if (TextUtils.isEmpty(providerId)) { + Log.w(TAG, "toUniqueId: providerId shouldn't be empty"); + return null; + } + if (TextUtils.isEmpty(id)) { + Log.w(TAG, "toUniqueId: id shouldn't be null"); + return null; + } + + return providerId + SEPARATOR + id; + } + + /** + * Gets provider ID from unique ID. + * If the corresponding provider ID could not be generated, it will return null. + * + * @hide + */ + @Nullable + public static String getProviderId(@NonNull String uniqueId) { + if (TextUtils.isEmpty(uniqueId)) { + Log.w(TAG, "getProviderId: uniqueId shouldn't be empty"); + return null; + } + + int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR); + if (firstIndexOfSeparator == -1) { + return null; + } + + String providerId = uniqueId.substring(0, firstIndexOfSeparator); + if (TextUtils.isEmpty(providerId)) { + return null; + } + + return providerId; + } + + /** + * Gets the original ID (i.e. non-unique route/session ID) from unique ID. + * If the corresponding ID could not be generated, it will return null. + * + * @hide + */ + @Nullable + public static String getOriginalId(@NonNull String uniqueId) { + if (TextUtils.isEmpty(uniqueId)) { + Log.w(TAG, "getOriginalId: uniqueId shouldn't be empty"); + return null; + } + + int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR); + if (firstIndexOfSeparator == -1 || firstIndexOfSeparator + 1 >= uniqueId.length()) { + return null; + } + + String providerId = uniqueId.substring(firstIndexOfSeparator + 1); + if (TextUtils.isEmpty(providerId)) { + return null; + } + + return providerId; + } +} diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java index cb1688600fac..5330630ef3a9 100644 --- a/media/java/android/media/RouteSessionInfo.java +++ b/media/java/android/media/RouteSessionInfo.java @@ -22,6 +22,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.Log; import java.util.ArrayList; import java.util.Collections; @@ -33,6 +34,7 @@ import java.util.Objects; * @hide */ public class RouteSessionInfo implements Parcelable { + @NonNull public static final Creator<RouteSessionInfo> CREATOR = new Creator<RouteSessionInfo>() { @@ -46,8 +48,10 @@ public class RouteSessionInfo implements Parcelable { } }; - final int mSessionId; - final String mPackageName; + public static final String TAG = "RouteSessionInfo"; + + final String mId; + final String mClientPackageName; final String mRouteType; @Nullable final String mProviderId; @@ -61,15 +65,19 @@ public class RouteSessionInfo implements Parcelable { RouteSessionInfo(@NonNull Builder builder) { Objects.requireNonNull(builder, "builder must not be null."); - mSessionId = builder.mSessionId; - mPackageName = builder.mPackageName; + mId = builder.mId; + mClientPackageName = builder.mClientPackageName; mRouteType = builder.mRouteType; mProviderId = builder.mProviderId; - mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes); - mSelectableRoutes = Collections.unmodifiableList(builder.mSelectableRoutes); - mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes); - mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes); + mSelectedRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mSelectedRoutes)); + mSelectableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mSelectableRoutes)); + mDeselectableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mDeselectableRoutes)); + mTransferrableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mTransferrableRoutes)); mControlHints = builder.mControlHints; } @@ -77,8 +85,8 @@ public class RouteSessionInfo implements Parcelable { RouteSessionInfo(@NonNull Parcel src) { Objects.requireNonNull(src, "src must not be null."); - mSessionId = src.readInt(); - mPackageName = ensureString(src.readString()); + mId = ensureString(src.readString()); + mClientPackageName = ensureString(src.readString()); mRouteType = ensureString(src.readString()); mProviderId = src.readString(); @@ -105,73 +113,50 @@ public class RouteSessionInfo implements Parcelable { } /** - * Gets non-unique session id (int) from unique session id (string). - * If the corresponding session id could not be generated, it will return null. - * @hide + * Returns whether the session info is valid or not + * + * TODO in this CL: Remove this method. */ - @Nullable - public static Integer getSessionId(@NonNull String uniqueSessionId) { - int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/"); - if (lastIndexOfSeparator == -1 || lastIndexOfSeparator + 1 >= uniqueSessionId.length()) { - return null; - } - - String integerString = uniqueSessionId.substring(lastIndexOfSeparator + 1); - if (TextUtils.isEmpty(integerString)) { - return null; - } - - try { - return Integer.parseInt(integerString); - } catch (NumberFormatException ex) { - return null; - } + public boolean isValid() { + return !TextUtils.isEmpty(mId) + && !TextUtils.isEmpty(mClientPackageName) + && !TextUtils.isEmpty(mRouteType) + && mSelectedRoutes.size() > 0; } /** - * Gets provider ID (string) from unique session id (string). - * If the corresponding provider ID could not be generated, it will return null. - * @hide + * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have + * unique IDs. + * <p> + * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method + * can be different from what was set in {@link MediaRoute2ProviderService}. * - * TODO: This logic seems error-prone. Consider to use long uniqueId. + * @see Builder#Builder(String, String, String) */ - @Nullable - public static String getProviderId(@NonNull String uniqueSessionId) { - int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/"); - if (lastIndexOfSeparator == -1) { - return null; - } - - String result = uniqueSessionId.substring(0, lastIndexOfSeparator); - if (TextUtils.isEmpty(result)) { - return null; + @NonNull + public String getId() { + if (mProviderId != null) { + return MediaRouter2Utils.toUniqueId(mProviderId, mId); + } else { + return mId; } - return result; - } - - /** - * Returns whether the session info is valid or not - */ - public boolean isValid() { - return !TextUtils.isEmpty(mPackageName) - && !TextUtils.isEmpty(mRouteType) - && mSelectedRoutes.size() > 0; } /** - * Gets the id of the session + * Gets the original id set by {@link Builder#Builder(String, String, String)}. + * @hide */ @NonNull - public int getSessionId() { - return mSessionId; + public String getOriginalId() { + return mId; } /** * Gets the client package name of the session */ @NonNull - public String getPackageName() { - return mPackageName; + public String getClientPackageName() { + return mClientPackageName; } /** @@ -193,19 +178,6 @@ public class RouteSessionInfo implements Parcelable { } /** - * Gets the unique id of the session. - * @hide - */ - @NonNull - public String getUniqueSessionId() { - StringBuilder sessionIdBuilder = new StringBuilder() - .append(mProviderId) - .append("/") - .append(mSessionId); - return sessionIdBuilder.toString(); - } - - /** * Gets the list of ids of selected routes for the session. It shouldn't be empty. */ @NonNull @@ -252,8 +224,8 @@ public class RouteSessionInfo implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mSessionId); - dest.writeString(mPackageName); + dest.writeString(mId); + dest.writeString(mClientPackageName); dest.writeString(mRouteType); dest.writeString(mProviderId); dest.writeStringList(mSelectedRoutes); @@ -267,7 +239,7 @@ public class RouteSessionInfo implements Parcelable { public String toString() { StringBuilder result = new StringBuilder() .append("RouteSessionInfo{ ") - .append("sessionId=").append(mSessionId) + .append("sessionId=").append(mId) .append(", routeType=").append(mRouteType) .append(", selectedRoutes={") .append(String.join(",", mSelectedRoutes)) @@ -285,12 +257,30 @@ public class RouteSessionInfo implements Parcelable { return result.toString(); } + private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) { + if (routeIds == null) { + Log.w(TAG, "routeIds is null. Returning an empty list"); + return Collections.emptyList(); + } + + // mProviderId can be null if not set. Return the original list for this case. + if (mProviderId == null) { + return routeIds; + } + + List<String> result = new ArrayList<>(); + for (String routeId : routeIds) { + result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId)); + } + return result; + } + /** * Builder class for {@link RouteSessionInfo}. */ public static final class Builder { - final String mPackageName; - final int mSessionId; + final String mId; + final String mClientPackageName; final String mRouteType; String mProviderId; final List<String> mSelectedRoutes; @@ -299,22 +289,42 @@ public class RouteSessionInfo implements Parcelable { final List<String> mTransferrableRoutes; Bundle mControlHints; - public Builder(int sessionId, @NonNull String packageName, + /** + * Constructor for builder to create {@link RouteSessionInfo}. + * <p> + * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of + * {@link RouteSessionInfo#getId()} can be different from what was set in + * {@link MediaRoute2ProviderService}. + * </p> + * + * @see MediaRoute2Info#getId() + */ + public Builder(@NonNull String id, @NonNull String clientPackageName, @NonNull String routeType) { - mSessionId = sessionId; - mPackageName = Objects.requireNonNull(packageName, "packageName must not be null"); - mRouteType = Objects.requireNonNull(routeType, - "routeType must not be null"); - + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id must not be empty"); + } + mId = id; + mClientPackageName = Objects.requireNonNull( + clientPackageName, "clientPackageName must not be null"); + mRouteType = Objects.requireNonNull(routeType, "routeType must not be null"); mSelectedRoutes = new ArrayList<>(); mSelectableRoutes = new ArrayList<>(); mDeselectableRoutes = new ArrayList<>(); mTransferrableRoutes = new ArrayList<>(); } - public Builder(RouteSessionInfo sessionInfo) { - mSessionId = sessionInfo.mSessionId; - mPackageName = sessionInfo.mPackageName; + /** + * Constructor for builder to create {@link RouteSessionInfo} with + * existing {@link RouteSessionInfo} instance. + * + * @param sessionInfo the existing instance to copy data from. + */ + public Builder(@NonNull RouteSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + mId = sessionInfo.mId; + mClientPackageName = sessionInfo.mClientPackageName; mRouteType = sessionInfo.mRouteType; mProviderId = sessionInfo.mProviderId; @@ -334,23 +344,14 @@ public class RouteSessionInfo implements Parcelable { * @hide */ @NonNull - public Builder setProviderId(String providerId) { + public Builder setProviderId(@NonNull String providerId) { + if (TextUtils.isEmpty(providerId)) { + throw new IllegalArgumentException("providerId must not be empty"); + } mProviderId = providerId; - convertToUniqueRouteIds(providerId, mSelectedRoutes); - convertToUniqueRouteIds(providerId, mSelectableRoutes); - convertToUniqueRouteIds(providerId, mDeselectableRoutes); - convertToUniqueRouteIds(providerId, mTransferrableRoutes); return this; } - private void convertToUniqueRouteIds(@NonNull String providerId, - @NonNull List<String> routeIds) { - for (int i = 0; i < routeIds.size(); i++) { - String routeId = routeIds.get(i); - routeIds.set(i, MediaRoute2Info.toUniqueId(providerId, routeId)); - } - } - /** * Clears the selected routes. */ diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index aff725734ee1..aece39d78694 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -940,16 +940,15 @@ public final class MediaSessionManager { /** * Called when a media key event is dispatched through the media session service. The * session token can be {@link null} if the framework has sent the media key event to the - * media button receiver to revive the media app's playback. - * - * the session is dead when , but the framework sent + * media button receiver to revive the media app's playback after the corresponding session + * is released. * * @param event Dispatched media key event. * @param packageName Package * @param sessionToken The media session's token. Can be {@code null}. */ default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName, - @NonNull MediaSession.Token sessionToken) { } + @Nullable MediaSession.Token sessionToken) { } } /** diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java new file mode 100644 index 000000000000..f9f7a22c3de8 --- /dev/null +++ b/media/java/android/media/tv/tuner/Descrambler.java @@ -0,0 +1,103 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.Nullable; +import android.media.tv.tuner.Tuner.Filter; +import android.media.tv.tuner.TunerConstants.DemuxPidType; + +/** + * This class is used to interact with descramblers. + * + * <p> Descrambler is a hardware component used to descramble data. + * + * <p> This class controls the TIS interaction with Tuner HAL. + * @hide + */ +public class Descrambler implements AutoCloseable { + private long mNativeContext; + + private native int nativeAddPid(int pidType, int pid, Filter filter); + private native int nativeRemovePid(int pidType, int pid, Filter filter); + private native int nativeSetKeyToken(byte[] keyToken); + private native int nativeClose(); + + private Descrambler() {} + + /** + * Add packets' PID to the descrambler for descrambling. + * + * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added + * into one descrambler instance because descambling can happen simultaneously on packets + * from different PIDs. + * + * If the Descrambler previously contained a filter for the PID, the old filter is replaced + * by the specified filter. + * + * @param pidType the type of the PID. + * @param pid the PID of packets to start to be descrambled. + * @param filter an optional filter instance to identify upper stream. + * @return result status of the operation. + * + * @hide + */ + public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { + return nativeAddPid(pidType, pid, filter); + } + + /** + * Remove packets' PID from the descrambler + * + * The descrambler will stop descrambling packets with this PID. + * + * @param pidType the type of the PID. + * @param pid the PID of packets to stop to be descrambled. + * @param filter an optional filter instance to identify upper stream. + * @return result status of the operation. + * + * @hide + */ + public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { + return nativeRemovePid(pidType, pid, filter); + } + + /** + * Set a key token to link descrambler to a key slot + * + * A descrambler instance can have only one key slot to link, but a key slot can hold a few + * keys for different purposes. + * + * @param keyToken the token to be used to link the key slot. + * @return result status of the operation. + * + * @hide + */ + public int setKeyToken(byte[] keyToken) { + return nativeSetKeyToken(keyToken); + } + + /** + * Release the descrambler instance. + * + * @hide + */ + @Override + public void close() { + nativeClose(); + } + +} diff --git a/media/java/android/media/tv/tuner/Dvr.java b/media/java/android/media/tv/tuner/Dvr.java new file mode 100644 index 000000000000..0bfba8f9d4f3 --- /dev/null +++ b/media/java/android/media/tv/tuner/Dvr.java @@ -0,0 +1,152 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.NonNull; +import android.media.tv.tuner.Tuner.DvrCallback; +import android.media.tv.tuner.Tuner.Filter; +import android.os.ParcelFileDescriptor; + +/** @hide */ +public class Dvr { + private long mNativeContext; + private DvrCallback mCallback; + + private native int nativeAttachFilter(Filter filter); + private native int nativeDetachFilter(Filter filter); + private native int nativeConfigureDvr(DvrSettings settings); + private native int nativeStartDvr(); + private native int nativeStopDvr(); + private native int nativeFlushDvr(); + private native int nativeClose(); + private native void nativeSetFileDescriptor(int fd); + private native int nativeRead(int size); + private native int nativeRead(byte[] bytes, int offset, int size); + private native int nativeWrite(int size); + private native int nativeWrite(byte[] bytes, int offset, int size); + + private Dvr() {} + + /** + * Attaches a filter to DVR interface for recording. + * + * @param filter the filter to be attached. + * @return result status of the operation. + */ + public int attachFilter(Filter filter) { + return nativeAttachFilter(filter); + } + + /** + * Detaches a filter from DVR interface. + * + * @param filter the filter to be detached. + * @return result status of the operation. + */ + public int detachFilter(Filter filter) { + return nativeDetachFilter(filter); + } + + /** + * Configures the DVR. + * + * @param settings the settings of the DVR interface. + * @return result status of the operation. + */ + public int configure(DvrSettings settings) { + return nativeConfigureDvr(settings); + } + + /** + * Starts DVR. + * + * Starts consuming playback data or producing data for recording. + * + * @return result status of the operation. + */ + public int start() { + return nativeStartDvr(); + } + + /** + * Stops DVR. + * + * Stops consuming playback data or producing data for recording. + * + * @return result status of the operation. + */ + public int stop() { + return nativeStopDvr(); + } + + /** + * Flushed DVR data. + * + * @return result status of the operation. + */ + public int flush() { + return nativeFlushDvr(); + } + + /** + * closes the DVR instance to release resources. + * + * @return result status of the operation. + */ + public int close() { + return nativeClose(); + } + + /** + * Sets file descriptor to read/write data. + */ + public void setFileDescriptor(ParcelFileDescriptor fd) { + nativeSetFileDescriptor(fd.getFd()); + } + + /** + * Reads data from the file for DVR playback. + */ + public int read(int size) { + return nativeRead(size); + } + + /** + * Reads data from the buffer for DVR playback. + */ + public int read(@NonNull byte[] bytes, int offset, int size) { + if (size + offset > bytes.length) { + throw new ArrayIndexOutOfBoundsException( + "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size); + } + return nativeRead(bytes, offset, size); + } + + /** + * Writes recording data to file. + */ + public int write(int size) { + return nativeWrite(size); + } + + /** + * Writes recording data to buffer. + */ + public int write(@NonNull byte[] bytes, int offset, int size) { + return nativeWrite(bytes, offset, size); + } +} diff --git a/media/java/android/media/tv/tuner/Filter.java b/media/java/android/media/tv/tuner/Filter.java new file mode 100644 index 000000000000..db3b97afb1da --- /dev/null +++ b/media/java/android/media/tv/tuner/Filter.java @@ -0,0 +1,142 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.tv.tuner.Tuner.FilterCallback; +import android.media.tv.tuner.filter.FilterConfiguration; +import android.media.tv.tuner.filter.Settings; + +/** + * Tuner data filter. + * + * <p> This class is used to filter wanted data according to the filter's configuration. + * @hide + */ +public class Filter implements AutoCloseable { + private long mNativeContext; + private FilterCallback mCallback; + int mId; + + private native int nativeConfigureFilter( + int type, int subType, FilterConfiguration settings); + private native int nativeGetId(); + private native int nativeSetDataSource(Tuner.Filter source); + private native int nativeStartFilter(); + private native int nativeStopFilter(); + private native int nativeFlushFilter(); + private native int nativeRead(byte[] buffer, int offset, int size); + private native int nativeClose(); + + private Filter(int id) { + mId = id; + } + + private void onFilterStatus(int status) { + } + + /** + * Configures the filter. + * + * @param settings the settings of the filter. + * @return result status of the operation. + * @hide + */ + public int configure(FilterConfiguration settings) { + int subType = -1; + Settings s = settings.getSettings(); + if (s != null) { + subType = s.getType(); + } + return nativeConfigureFilter(settings.getType(), subType, settings); + } + + /** + * Gets the filter Id. + * + * @return the hardware resource Id for the filter. + * @hide + */ + public int getId() { + return nativeGetId(); + } + + /** + * Sets the filter's data source. + * + * A filter uses demux as data source by default. If the data was packetized + * by multiple protocols, multiple filters may need to work together to + * extract all protocols' header. Then a filter's data source can be output + * from another filter. + * + * @param source the filter instance which provides data input. Switch to + * use demux as data source if the filter instance is NULL. + * @return result status of the operation. + * @hide + */ + public int setDataSource(@Nullable Tuner.Filter source) { + return nativeSetDataSource(source); + } + + /** + * Starts the filter. + * + * @return result status of the operation. + * @hide + */ + public int start() { + return nativeStartFilter(); + } + + + /** + * Stops the filter. + * + * @return result status of the operation. + * @hide + */ + public int stop() { + return nativeStopFilter(); + } + + /** + * Flushes the filter. + * + * @return result status of the operation. + * @hide + */ + public int flush() { + return nativeFlushFilter(); + } + + /** @hide */ + public int read(@NonNull byte[] buffer, int offset, int size) { + size = Math.min(size, buffer.length - offset); + return nativeRead(buffer, offset, size); + } + + /** + * Release the Filter instance. + * + * @hide + */ + @Override + public void close() { + nativeClose(); + } +} diff --git a/media/java/android/media/tv/tuner/FrontendCapabilities.java b/media/java/android/media/tv/tuner/FrontendCapabilities.java deleted file mode 100644 index fcfd7c8c8639..000000000000 --- a/media/java/android/media/tv/tuner/FrontendCapabilities.java +++ /dev/null @@ -1,291 +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.media.tv.tuner; - -/** - * Frontend Capabilities. - * @hide - */ -public class FrontendCapabilities { - /** Analog Capabilities. */ - public class Analog extends FrontendCapabilities { - private final int mTypeCap; - private final int mSifStandardCap; - - Analog(int typeCap, int sifStandardCap) { - mTypeCap = typeCap; - mSifStandardCap = sifStandardCap; - } - /** - * Gets type capability. - */ - public int getTypeCapability() { - return mTypeCap; - } - /** Gets SIF standard capability. */ - public int getSifStandardCapability() { - return mSifStandardCap; - } - } - - /** ATSC Capabilities. */ - public class Atsc extends FrontendCapabilities { - private final int mModulationCap; - - Atsc(int modulationCap) { - mModulationCap = modulationCap; - } - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - } - - /** ATSC-3 Capabilities. */ - public class Atsc3 extends FrontendCapabilities { - private final int mBandwidthCap; - private final int mModulationCap; - private final int mTimeInterleaveModeCap; - private final int mCodeRateCap; - private final int mFecCap; - private final int mDemodOutputFormatCap; - - Atsc3(int bandwidthCap, int modulationCap, int timeInterleaveModeCap, int codeRateCap, - int fecCap, int demodOutputFormatCap) { - mBandwidthCap = bandwidthCap; - mModulationCap = modulationCap; - mTimeInterleaveModeCap = timeInterleaveModeCap; - mCodeRateCap = codeRateCap; - mFecCap = fecCap; - mDemodOutputFormatCap = demodOutputFormatCap; - } - - /** Gets bandwidth capability. */ - public int getBandwidthCapability() { - return mBandwidthCap; - } - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets time interleave mod capability. */ - public int getTimeInterleaveModeCapability() { - return mTimeInterleaveModeCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCodeRateCap; - } - /** Gets FEC capability. */ - public int getFecCapability() { - return mFecCap; - } - /** Gets demodulator output format capability. */ - public int getDemodOutputFormatCapability() { - return mDemodOutputFormatCap; - } - } - - /** DVBS Capabilities. */ - public class Dvbs extends FrontendCapabilities { - private final int mModulationCap; - private final long mInnerFecCap; - private final int mStandard; - - Dvbs(int modulationCap, long innerFecCap, int standard) { - mModulationCap = modulationCap; - mInnerFecCap = innerFecCap; - mStandard = standard; - } - - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets inner FEC capability. */ - public long getInnerFecCapability() { - return mInnerFecCap; - } - /** Gets DVBS standard capability. */ - public int getStandardCapability() { - return mStandard; - } - } - - /** DVBC Capabilities. */ - public class Dvbc extends FrontendCapabilities { - private final int mModulationCap; - private final int mFecCap; - private final int mAnnexCap; - - Dvbc(int modulationCap, int fecCap, int annexCap) { - mModulationCap = modulationCap; - mFecCap = fecCap; - mAnnexCap = annexCap; - } - - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets FEC capability. */ - public int getFecCapability() { - return mFecCap; - } - /** Gets annex capability. */ - public int getAnnexCapability() { - return mAnnexCap; - } - } - - /** DVBT Capabilities. */ - public class Dvbt extends FrontendCapabilities { - private final int mTransmissionModeCap; - private final int mBandwidthCap; - private final int mConstellationCap; - private final int mCoderateCap; - private final int mHierarchyCap; - private final int mGuardIntervalCap; - private final boolean mIsT2Supported; - private final boolean mIsMisoSupported; - - Dvbt(int transmissionModeCap, int bandwidthCap, int constellationCap, int coderateCap, - int hierarchyCap, int guardIntervalCap, boolean isT2Supported, - boolean isMisoSupported) { - mTransmissionModeCap = transmissionModeCap; - mBandwidthCap = bandwidthCap; - mConstellationCap = constellationCap; - mCoderateCap = coderateCap; - mHierarchyCap = hierarchyCap; - mGuardIntervalCap = guardIntervalCap; - mIsT2Supported = isT2Supported; - mIsMisoSupported = isMisoSupported; - } - - /** Gets transmission mode capability. */ - public int getTransmissionModeCapability() { - return mTransmissionModeCap; - } - /** Gets bandwidth capability. */ - public int getBandwidthCapability() { - return mBandwidthCap; - } - /** Gets constellation capability. */ - public int getConstellationCapability() { - return mConstellationCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCoderateCap; - } - /** Gets hierarchy capability. */ - public int getHierarchyCapability() { - return mHierarchyCap; - } - /** Gets guard interval capability. */ - public int getGuardIntervalCapability() { - return mGuardIntervalCap; - } - /** Returns whether T2 is supported. */ - public boolean getIsT2Supported() { - return mIsT2Supported; - } - /** Returns whether MISO is supported. */ - public boolean getIsMisoSupported() { - return mIsMisoSupported; - } - } - - /** ISDBS Capabilities. */ - public class Isdbs extends FrontendCapabilities { - private final int mModulationCap; - private final int mCoderateCap; - - Isdbs(int modulationCap, int coderateCap) { - mModulationCap = modulationCap; - mCoderateCap = coderateCap; - } - - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCoderateCap; - } - } - - /** ISDBS-3 Capabilities. */ - public class Isdbs3 extends FrontendCapabilities { - private final int mModulationCap; - private final int mCoderateCap; - - Isdbs3(int modulationCap, int coderateCap) { - mModulationCap = modulationCap; - mCoderateCap = coderateCap; - } - - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCoderateCap; - } - } - - /** ISDBC Capabilities. */ - public class Isdbc extends FrontendCapabilities { - private final int mModeCap; - private final int mBandwidthCap; - private final int mModulationCap; - private final int mCoderateCap; - private final int mGuardIntervalCap; - - Isdbc(int modeCap, int bandwidthCap, int modulationCap, int coderateCap, - int guardIntervalCap) { - mModeCap = modeCap; - mBandwidthCap = bandwidthCap; - mModulationCap = modulationCap; - mCoderateCap = coderateCap; - mGuardIntervalCap = guardIntervalCap; - } - - /** Gets mode capability. */ - public int getModeCapability() { - return mModeCap; - } - /** Gets bandwidth capability. */ - public int getBandwidthCapability() { - return mBandwidthCap; - } - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCoderateCap; - } - /** Gets guard interval capability. */ - public int getGuardIntervalCapability() { - return mGuardIntervalCap; - } - } -} diff --git a/media/java/android/media/tv/tuner/TimeFilter.java b/media/java/android/media/tv/tuner/TimeFilter.java new file mode 100644 index 000000000000..8bd0d26ce951 --- /dev/null +++ b/media/java/android/media/tv/tuner/TimeFilter.java @@ -0,0 +1,127 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.Nullable; +import android.media.tv.tuner.TunerConstants.Result; + +/** + * A timer filter is used to filter data based on timestamps. + * + * <p> If the timestamp is set, data is discarded if its timestamp is smaller than the + * timestamp in this time filter. + * + * <p> The format of the timestamps is the same as PTS defined in ISO/IEC 13818-1:2019. The + * timestamps may or may not be related to PTS or DTS. + * + * @hide + */ +public class TimeFilter implements AutoCloseable { + private native int nativeSetTimestamp(long timestamp); + private native int nativeClearTimestamp(); + private native Long nativeGetTimestamp(); + private native Long nativeGetSourceTime(); + private native int nativeClose(); + + private boolean mEnable = false; + + /** + * Set timestamp for time based filter. + * + * It is used to set initial timestamp and enable time filtering. Once set, the time will be + * increased automatically like a clock. Contents are discarded if their timestamps are + * older than the time in the time filter. + * + * This method can be called more than once to reset the initial timestamp. + * + * @param timestamp initial timestamp for the time filter before it's increased. It's + * based on the 90KHz counter, and it's the same format as PTS (Presentation Time Stamp) + * defined in ISO/IEC 13818-1:2019. The timestamps may or may not be related to PTS or DTS. + * @return result status of the operation. + */ + @Result + public int setCurrentTimestamp(long timestamp) { + int res = nativeSetTimestamp(timestamp); + // TODO: use a constant for SUCCESS + if (res == 0) { + mEnable = true; + } + return res; + } + + /** + * Clear the timestamp in the time filter. + * + * It is used to clear the time value of the time filter. Time filtering is disabled then. + * + * @return result status of the operation. + */ + @Result + public int clearTimestamp() { + int res = nativeClearTimestamp(); + if (res == 0) { + mEnable = false; + } + return res; + } + + /** + * Get the current time in the time filter. + * + * It is used to inquiry current time in the time filter. + * + * @return current timestamp in the time filter. It's based on the 90KHz counter, and it's + * the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. The + * timestamps may or may not be related to PTS or DTS. {@code null} if the timestamp is + * never set. + */ + @Nullable + public Long getTimeStamp() { + if (!mEnable) { + return null; + } + return nativeGetTimestamp(); + } + + /** + * Get the timestamp from the beginning of incoming data stream. + * + * It is used to inquiry the timestamp from the beginning of incoming data stream. + * + * @return first timestamp of incoming data stream. It's based on the 90KHz counter, and + * it's the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. + * The timestamps may or may not be related to PTS or DTS. + */ + @Nullable + public Long getSourceTime() { + if (!mEnable) { + return null; + } + return nativeGetSourceTime(); + } + + /** + * Close the Time Filter instance + * + * It is to release the TimeFilter instance. Resources are reclaimed so the instance must + * not be accessed after this method is called. + */ + @Override + public void close() { + nativeClose(); + } +} diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 173551812f3a..962a7f6d58f6 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -21,18 +21,15 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; -import android.media.tv.tuner.TunerConstants.DemuxPidType; import android.media.tv.tuner.TunerConstants.FilterStatus; import android.media.tv.tuner.TunerConstants.FilterSubtype; -import android.media.tv.tuner.TunerConstants.FilterType; import android.media.tv.tuner.TunerConstants.FrontendScanType; import android.media.tv.tuner.TunerConstants.LnbPosition; import android.media.tv.tuner.TunerConstants.LnbTone; import android.media.tv.tuner.TunerConstants.LnbVoltage; import android.media.tv.tuner.TunerConstants.Result; -import android.media.tv.tuner.filter.FilterConfiguration; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; import android.media.tv.tuner.filter.FilterEvent; -import android.media.tv.tuner.filter.Settings; import android.media.tv.tuner.frontend.FrontendCallback; import android.media.tv.tuner.frontend.FrontendInfo; import android.media.tv.tuner.frontend.FrontendStatus; @@ -40,7 +37,6 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; -import java.io.FileDescriptor; import java.util.List; /** @@ -458,122 +454,11 @@ public final class Tuner implements AutoCloseable { * Tuner data filter. * * <p> This class is used to filter wanted data according to the filter's configuration. + * TODO: remove */ public class Filter { - private long mNativeContext; - private FilterCallback mCallback; - int mId; - - private native int nativeConfigureFilter( - int type, int subType, FilterConfiguration settings); - private native int nativeGetId(); - private native int nativeSetDataSource(Filter source); - private native int nativeStartFilter(); - private native int nativeStopFilter(); - private native int nativeFlushFilter(); - private native int nativeRead(byte[] buffer, int offset, int size); - private native int nativeClose(); - - private Filter(int id) { - mId = id; - } - - private void onFilterStatus(int status) { - if (mHandler != null) { - mHandler.sendMessage( - mHandler.obtainMessage(MSG_ON_FILTER_STATUS, status, 0, this)); - } - } - - /** - * Configures the filter. - * - * @param settings the settings of the filter. - * @return result status of the operation. - * @hide - */ - public int configure(FilterConfiguration settings) { - int subType = -1; - Settings s = settings.getSettings(); - if (s != null) { - subType = s.getType(); - } - return nativeConfigureFilter(settings.getType(), subType, settings); - } - - /** - * Gets the filter Id. - * - * @return the hardware resource Id for the filter. - * @hide - */ - public int getId() { - return nativeGetId(); - } - - /** - * Sets the filter's data source. - * - * A filter uses demux as data source by default. If the data was packetized - * by multiple protocols, multiple filters may need to work together to - * extract all protocols' header. Then a filter's data source can be output - * from another filter. - * - * @param source the filter instance which provides data input. Switch to - * use demux as data source if the filter instance is NULL. - * @return result status of the operation. - * @hide - */ - public int setDataSource(@Nullable Filter source) { - return nativeSetDataSource(source); - } - - /** - * Starts the filter. - * - * @return result status of the operation. - * @hide - */ - public int start() { - return nativeStartFilter(); - } - - - /** - * Stops the filter. - * - * @return result status of the operation. - * @hide - */ - public int stop() { - return nativeStopFilter(); - } - - /** - * Flushes the filter. - * - * @return result status of the operation. - * @hide - */ - public int flush() { - return nativeFlushFilter(); - } - - /** @hide */ - public int read(@NonNull byte[] buffer, int offset, int size) { - size = Math.min(size, buffer.length - offset); - return nativeRead(buffer, offset, size); - } - - /** - * Release the Filter instance. - * - * @return result status of the operation. - * @hide - */ - public int close() { - return nativeClose(); - } + FilterCallback mCallback; + private Filter() {} } private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize, @@ -590,115 +475,6 @@ public final class Tuner implements AutoCloseable { } /** - * A timer filter is used to filter data based on timestamps. - * - * <p> If the timestamp is set, data is discarded if its timestamp is smaller than the - * timestamp in this time filter. - * - * <p> The format of the timestamps is the same as PTS defined in ISO/IEC 13818-1:2019. The - * timestamps may or may not be related to PTS or DTS. - * - * @hide - */ - public class TimeFilter { - private native int nativeSetTimestamp(long timestamp); - private native int nativeClearTimestamp(); - private native Long nativeGetTimestamp(); - private native Long nativeGetSourceTime(); - private native int nativeClose(); - - private boolean mEnable = false; - - /** - * Set timestamp for time based filter. - * - * It is used to set initial timestamp and enable time filtering. Once set, the time will be - * increased automatically like a clock. Contents are discarded if their timestamps are - * older than the time in the time filter. - * - * This method can be called more than once to reset the initial timestamp. - * - * @param timestamp initial timestamp for the time filter before it's increased. It's - * based on the 90KHz counter, and it's the same format as PTS (Presentation Time Stamp) - * defined in ISO/IEC 13818-1:2019. The timestamps may or may not be related to PTS or DTS. - * @return result status of the operation. - */ - @Result - public int setCurrentTimestamp(long timestamp) { - int res = nativeSetTimestamp(timestamp); - // TODO: use a constant for SUCCESS - if (res == 0) { - mEnable = true; - } - return res; - } - - /** - * Clear the timestamp in the time filter. - * - * It is used to clear the time value of the time filter. Time filtering is disabled then. - * - * @return result status of the operation. - */ - @Result - public int clearTimestamp() { - int res = nativeClearTimestamp(); - if (res == 0) { - mEnable = false; - } - return res; - } - - /** - * Get the current time in the time filter. - * - * It is used to inquiry current time in the time filter. - * - * @return current timestamp in the time filter. It's based on the 90KHz counter, and it's - * the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. The - * timestamps may or may not be related to PTS or DTS. {@code null} if the timestamp is - * never set. - */ - @Nullable - public Long getTimeStamp() { - if (!mEnable) { - return null; - } - return nativeGetTimestamp(); - } - - /** - * Get the timestamp from the beginning of incoming data stream. - * - * It is used to inquiry the timestamp from the beginning of incoming data stream. - * - * @return first timestamp of incoming data stream. It's based on the 90KHz counter, and - * it's the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. - * The timestamps may or may not be related to PTS or DTS. - */ - @Nullable - public Long getSourceTime() { - if (!mEnable) { - return null; - } - return nativeGetSourceTime(); - } - - /** - * Close the Time Filter instance - * - * It is to release the TimeFilter instance. Resources are reclaimed so the instance must - * not be accessed after this method is called. - * - * @return result status of the operation. - */ - @Result - public int close() { - return nativeClose(); - } - } - - /** * Open a time filter instance. * * It is used to open time filter of demux. @@ -822,81 +598,11 @@ public final class Tuner implements AutoCloseable { * <p> Descrambler is a hardware component used to descramble data. * * <p> This class controls the TIS interaction with Tuner HAL. - * TODO: make it static and extends Closable. + * TODO: Remove */ public class Descrambler { - private long mNativeContext; - - private native int nativeAddPid(int pidType, int pid, Filter filter); - private native int nativeRemovePid(int pidType, int pid, Filter filter); - private native int nativeSetKeyToken(byte[] keyToken); - private native int nativeClose(); - - private Descrambler() {} - - /** - * Add packets' PID to the descrambler for descrambling. - * - * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added - * into one descrambler instance because descambling can happen simultaneously on packets - * from different PIDs. - * - * If the Descrambler previously contained a filter for the PID, the old filter is replaced - * by the specified filter. - * - * @param pidType the type of the PID. - * @param pid the PID of packets to start to be descrambled. - * @param filter an optional filter instance to identify upper stream. - * @return result status of the operation. - * - * @hide - */ - public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { - return nativeAddPid(pidType, pid, filter); - } - - /** - * Remove packets' PID from the descrambler - * - * The descrambler will stop descrambling packets with this PID. - * - * @param pidType the type of the PID. - * @param pid the PID of packets to stop to be descrambled. - * @param filter an optional filter instance to identify upper stream. - * @return result status of the operation. - * - * @hide - */ - public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { - return nativeRemovePid(pidType, pid, filter); - } - - /** - * Set a key token to link descrambler to a key slot - * - * A descrambler instance can have only one key slot to link, but a key slot can hold a few - * keys for different purposes. - * - * @param keyToken the token to be used to link the key slot. - * @return result status of the operation. - * - * @hide - */ - public int setKeyToken(byte[] keyToken) { - return nativeSetKeyToken(keyToken); - } - - /** - * Release the descrambler instance. - * - * @return result status of the operation. - * - * @hide - */ - public int close() { - return nativeClose(); + private Descrambler() { } - } /** @@ -911,137 +617,6 @@ public final class Tuner implements AutoCloseable { return nativeOpenDescrambler(); } - // TODO: consider splitting Dvr to Playback and Recording - /** @hide */ - public class Dvr { - private long mNativeContext; - private DvrCallback mCallback; - - private native int nativeAttachFilter(Filter filter); - private native int nativeDetachFilter(Filter filter); - private native int nativeConfigureDvr(DvrSettings settings); - private native int nativeStartDvr(); - private native int nativeStopDvr(); - private native int nativeFlushDvr(); - private native int nativeClose(); - private native void nativeSetFileDescriptor(FileDescriptor fd); - private native int nativeRead(int size); - private native int nativeRead(byte[] bytes, int offset, int size); - private native int nativeWrite(int size); - private native int nativeWrite(byte[] bytes, int offset, int size); - - private Dvr() {} - - /** - * Attaches a filter to DVR interface for recording. - * - * @param filter the filter to be attached. - * @return result status of the operation. - */ - public int attachFilter(Filter filter) { - return nativeAttachFilter(filter); - } - - /** - * Detaches a filter from DVR interface. - * - * @param filter the filter to be detached. - * @return result status of the operation. - */ - public int detachFilter(Filter filter) { - return nativeDetachFilter(filter); - } - - /** - * Configures the DVR. - * - * @param settings the settings of the DVR interface. - * @return result status of the operation. - */ - public int configure(DvrSettings settings) { - return nativeConfigureDvr(settings); - } - - /** - * Starts DVR. - * - * Starts consuming playback data or producing data for recording. - * - * @return result status of the operation. - */ - public int start() { - return nativeStartDvr(); - } - - /** - * Stops DVR. - * - * Stops consuming playback data or producing data for recording. - * - * @return result status of the operation. - */ - public int stop() { - return nativeStopDvr(); - } - - /** - * Flushed DVR data. - * - * @return result status of the operation. - */ - public int flush() { - return nativeFlushDvr(); - } - - /** - * closes the DVR instance to release resources. - * - * @return result status of the operation. - */ - public int close() { - return nativeClose(); - } - - /** - * Sets file descriptor to read/write data. - */ - public void setFileDescriptor(FileDescriptor fd) { - nativeSetFileDescriptor(fd); - } - - /** - * Reads data from the file for DVR playback. - */ - public int read(int size) { - return nativeRead(size); - } - - /** - * Reads data from the buffer for DVR playback. - */ - public int read(@NonNull byte[] bytes, int offset, int size) { - if (size + offset > bytes.length) { - throw new ArrayIndexOutOfBoundsException( - "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size); - } - return nativeRead(bytes, offset, size); - } - - /** - * Writes recording data to file. - */ - public int write(int size) { - return nativeWrite(size); - } - - /** - * Writes recording data to buffer. - */ - public int write(@NonNull byte[] bytes, int offset, int size) { - return nativeWrite(bytes, offset, size); - } - } - private Dvr openDvr(int type, int bufferSize) { Dvr dvr = nativeOpenDvr(type, bufferSize); return dvr; diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index e02443202085..bbaa5180aece 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -128,21 +128,6 @@ public final class TunerConstants { public static final int FRONTEND_SETTINGS_ISDBT = 9; /** @hide */ - @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP}) - @Retention(RetentionPolicy.SOURCE) - public @interface FilterType {} - /** @hide */ - public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS; - /** @hide */ - public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP; - /** @hide */ - public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP; - /** @hide */ - public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV; - /** @hide */ - public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; - - /** @hide */ @IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES, FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD, FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI, diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java index a7ccb0288e03..8780b726e3b2 100644 --- a/media/java/android/media/tv/tuner/TunerUtils.java +++ b/media/java/android/media/tv/tuner/TunerUtils.java @@ -20,7 +20,8 @@ import android.content.Context; import android.content.pm.PackageManager; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.TunerConstants.FilterSubtype; -import android.media.tv.tuner.TunerConstants.FilterType; +import android.media.tv.tuner.filter.FilterConfiguration; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; /** * Utility class for tuner framework. @@ -50,7 +51,7 @@ public final class TunerUtils { * @param subtype filter subtype. */ public static int getFilterSubtype(@FilterType int mainType, @FilterSubtype int subtype) { - if (mainType == TunerConstants.FILTER_TYPE_TS) { + if (mainType == FilterConfiguration.FILTER_TYPE_TS) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxTsFilterType.UNDEFINED; @@ -73,7 +74,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_MMTP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_MMTP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxMmtpFilterType.UNDEFINED; @@ -95,7 +96,7 @@ public final class TunerUtils { break; } - } else if (mainType == TunerConstants.FILTER_TYPE_IP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_IP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxIpFilterType.UNDEFINED; @@ -112,7 +113,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_TLV) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_TLV) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxTlvFilterType.UNDEFINED; @@ -125,7 +126,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_ALP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_ALP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxAlpFilterType.UNDEFINED; diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java index 4d02b849899d..f0fe533093ba 100644 --- a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; - /** * Filter configuration for a ALP filter. * @hide @@ -32,6 +30,6 @@ public class AlpFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_ALP; + return FilterConfiguration.FILTER_TYPE_ALP; } } diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java index 930ca744a168..99b10cde34f9 100644 --- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java @@ -16,28 +16,62 @@ package android.media.tv.tuner.filter; +import android.annotation.IntDef; import android.annotation.Nullable; -import android.media.tv.tuner.TunerConstants.FilterType; +import android.hardware.tv.tuner.V1_0.Constants; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** - * Demux Filter configuration. + * Filter configuration used to configure filters. * * @hide */ public abstract class FilterConfiguration { + + /** @hide */ + @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP}) + @Retention(RetentionPolicy.SOURCE) + public @interface FilterType {} + + /** + * TS filter type. + */ + public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS; + /** + * MMTP filter type. + */ + public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP; + /** + * IP filter type. + */ + public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP; + /** + * TLV filter type. + */ + public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV; + /** + * ALP filter type. + */ + public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; + @Nullable - protected final Settings mSettings; + private final Settings mSettings; - protected FilterConfiguration(Settings settings) { + /* package */ FilterConfiguration(Settings settings) { mSettings = settings; } /** - * Gets filter configuration type + * Gets filter configuration type. + * @hide */ @FilterType public abstract int getType(); + /** @hide */ + @Nullable public Settings getSettings() { return mSettings; } diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java index 2c706c0ce9ce..c89636887628 100644 --- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; - /** * Filter configuration for a IP filter. * @hide @@ -35,6 +33,6 @@ public class IpFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_IP; + return FilterConfiguration.FILTER_TYPE_IP; } } diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java index f70e70a5bdb2..9045ce67a61f 100644 --- a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; - /** * Filter configuration for a MMTP filter. * @hide @@ -31,6 +29,6 @@ public class MmtpFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_MMTP; + return FilterConfiguration.FILTER_TYPE_MMTP; } } diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java index 3d2c265cc00b..f38abf12e120 100644 --- a/media/java/android/media/tv/tuner/filter/PesSettings.java +++ b/media/java/android/media/tv/tuner/filter/PesSettings.java @@ -16,45 +16,54 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerUtils; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; /** * Filter Settings for a PES Data. + * * @hide */ public class PesSettings extends Settings { - private int mStreamId; - private boolean mIsRaw; + private final int mStreamId; + private final boolean mIsRaw; - private PesSettings(int mainType, int streamId, boolean isRaw) { + private PesSettings(@FilterType int mainType, int streamId, boolean isRaw) { super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES)); mStreamId = streamId; mIsRaw = isRaw; } /** - * Creates a builder for PesSettings. + * Creates a builder for {@link PesSettings}. + * + * @param mainType the filter main type of the settings. */ - public static Builder newBuilder(int mainType) { + @NonNull + public static Builder newBuilder(@FilterType int mainType) { return new Builder(mainType); } /** - * Builder for PesSettings. + * Builder for {@link PesSettings}. */ public static class Builder { private final int mMainType; private int mStreamId; private boolean mIsRaw; - public Builder(int mainType) { + private Builder(int mainType) { mMainType = mainType; } /** * Sets stream ID. + * + * @param streamId the stream ID. */ + @NonNull public Builder setStreamId(int streamId) { mStreamId = streamId; return this; @@ -62,16 +71,20 @@ public class PesSettings extends Settings { /** * Sets whether it's raw. - * true if the filter send onFilterStatus instead of onFilterEvent. + * + * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback + * instead of onFilterEvent for raw data. {@code false} otherwise. */ + @NonNull public Builder setIsRaw(boolean isRaw) { mIsRaw = isRaw; return this; } /** - * Builds a PesSettings instance. + * Builds a {@link PesSettings} object. */ + @NonNull public PesSettings build() { return new PesSettings(mMainType, mStreamId, mIsRaw); } diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java index 789ed0895750..146aca74ce0e 100644 --- a/media/java/android/media/tv/tuner/filter/Settings.java +++ b/media/java/android/media/tv/tuner/filter/Settings.java @@ -18,18 +18,20 @@ package android.media.tv.tuner.filter; /** * Settings for filters of different subtypes. + * * @hide */ public abstract class Settings { - protected final int mType; + private final int mType; - protected Settings(int type) { + /* package */ Settings(int type) { mType = type; } /** * Gets filter settings type. - * @return + * + * @hide */ public int getType() { return mType; diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java index 1f8bcb3b4a71..de8ee754a28c 100644 --- a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; - /** * Filter configuration for a TLV filter. * @hide @@ -33,6 +31,6 @@ public class TlvFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_TLV; + return FilterConfiguration.FILTER_TYPE_TLV; } } diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java index 103698cd0d75..d0241b6aba09 100644 --- a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java @@ -16,14 +16,15 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; +import android.annotation.NonNull; /** * Filter configuration for a TS filter. + * * @hide */ public class TsFilterConfiguration extends FilterConfiguration { - private int mTpid; + private final int mTpid; private TsFilterConfiguration(Settings settings, int tpid) { super(settings); @@ -32,42 +33,50 @@ public class TsFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_TS; + return FilterConfiguration.FILTER_TYPE_TS; } /** - * Creates a new builder. + * Creates a builder for {@link TsFilterConfiguration}. */ - public static TsFilterConfiguration.Builder newBuilder() { - return new TsFilterConfiguration.Builder(); + @NonNull + public static Builder newBuilder() { + return new Builder(); } /** - * Builder for TsFilterConfiguration. + * Builder for {@link TsFilterConfiguration}. */ public static class Builder { private Settings mSettings; private int mTpid; /** - * Sets settings. + * Sets filter settings. + * + * @param settings the filter settings. */ - public TsFilterConfiguration.Builder setSettings(Settings settings) { + @NonNull + public Builder setSettings(@NonNull Settings settings) { mSettings = settings; return this; } /** - * Sets TPID. + * Sets Tag Protocol ID. + * + * @param tpid the Tag Protocol ID. */ - public TsFilterConfiguration.Builder setTpid(int tpid) { + @NonNull + public Builder setTpid(int tpid) { mTpid = tpid; return this; } /** - * Builds a TsFilterConfiguration instance. + * Builds a {@link TsFilterConfiguration} object. */ + @NonNull public TsFilterConfiguration build() { return new TsFilterConfiguration(mSettings, mTpid); } diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java new file mode 100644 index 000000000000..2962e98790e5 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java @@ -0,0 +1,41 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * Analog Capabilities. + * @hide + */ +public class AnalogFrontendCapabilities extends FrontendCapabilities { + private final int mTypeCap; + private final int mSifStandardCap; + + AnalogFrontendCapabilities(int typeCap, int sifStandardCap) { + mTypeCap = typeCap; + mSifStandardCap = sifStandardCap; + } + /** + * Gets type capability. + */ + public int getTypeCapability() { + return mTypeCap; + } + /** Gets SIF standard capability. */ + public int getSifStandardCapability() { + return mSifStandardCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java new file mode 100644 index 000000000000..677f9387c6d2 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java @@ -0,0 +1,65 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ATSC-3 Capabilities. + * @hide + */ +public class Atsc3FrontendCapabilities extends FrontendCapabilities { + private final int mBandwidthCap; + private final int mModulationCap; + private final int mTimeInterleaveModeCap; + private final int mCodeRateCap; + private final int mFecCap; + private final int mDemodOutputFormatCap; + + Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap, int timeInterleaveModeCap, + int codeRateCap, int fecCap, int demodOutputFormatCap) { + mBandwidthCap = bandwidthCap; + mModulationCap = modulationCap; + mTimeInterleaveModeCap = timeInterleaveModeCap; + mCodeRateCap = codeRateCap; + mFecCap = fecCap; + mDemodOutputFormatCap = demodOutputFormatCap; + } + + /** Gets bandwidth capability. */ + public int getBandwidthCapability() { + return mBandwidthCap; + } + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets time interleave mod capability. */ + public int getTimeInterleaveModeCapability() { + return mTimeInterleaveModeCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCodeRateCap; + } + /** Gets FEC capability. */ + public int getFecCapability() { + return mFecCap; + } + /** Gets demodulator output format capability. */ + public int getDemodOutputFormatCapability() { + return mDemodOutputFormatCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java new file mode 100644 index 000000000000..6ae3c632f5db --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java @@ -0,0 +1,33 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ATSC Capabilities. + * @hide + */ +public class AtscFrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + + AtscFrontendCapabilities(int modulationCap) { + mModulationCap = modulationCap; + } + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java new file mode 100644 index 000000000000..edea7af06774 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java @@ -0,0 +1,46 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * DVBC Capabilities. + * @hide + */ +public class DvbcFrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + private final int mFecCap; + private final int mAnnexCap; + + DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) { + mModulationCap = modulationCap; + mFecCap = fecCap; + mAnnexCap = annexCap; + } + + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets FEC capability. */ + public int getFecCapability() { + return mFecCap; + } + /** Gets annex capability. */ + public int getAnnexCapability() { + return mAnnexCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java new file mode 100644 index 000000000000..f5a41574cd04 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java @@ -0,0 +1,46 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * DVBS Capabilities. + * @hide + */ +public class DvbsFrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + private final long mInnerFecCap; + private final int mStandard; + + DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) { + mModulationCap = modulationCap; + mInnerFecCap = innerFecCap; + mStandard = standard; + } + + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets inner FEC capability. */ + public long getInnerFecCapability() { + return mInnerFecCap; + } + /** Gets DVBS standard capability. */ + public int getStandardCapability() { + return mStandard; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java new file mode 100644 index 000000000000..e9c16ddd4dc8 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java @@ -0,0 +1,78 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * DVBT Capabilities. + * @hide + */ +public class DvbtFrontendCapabilities extends FrontendCapabilities { + private final int mTransmissionModeCap; + private final int mBandwidthCap; + private final int mConstellationCap; + private final int mCoderateCap; + private final int mHierarchyCap; + private final int mGuardIntervalCap; + private final boolean mIsT2Supported; + private final boolean mIsMisoSupported; + + DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap, int constellationCap, + int coderateCap, int hierarchyCap, int guardIntervalCap, boolean isT2Supported, + boolean isMisoSupported) { + mTransmissionModeCap = transmissionModeCap; + mBandwidthCap = bandwidthCap; + mConstellationCap = constellationCap; + mCoderateCap = coderateCap; + mHierarchyCap = hierarchyCap; + mGuardIntervalCap = guardIntervalCap; + mIsT2Supported = isT2Supported; + mIsMisoSupported = isMisoSupported; + } + + /** Gets transmission mode capability. */ + public int getTransmissionModeCapability() { + return mTransmissionModeCap; + } + /** Gets bandwidth capability. */ + public int getBandwidthCapability() { + return mBandwidthCap; + } + /** Gets constellation capability. */ + public int getConstellationCapability() { + return mConstellationCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCoderateCap; + } + /** Gets hierarchy capability. */ + public int getHierarchyCapability() { + return mHierarchyCap; + } + /** Gets guard interval capability. */ + public int getGuardIntervalCapability() { + return mGuardIntervalCap; + } + /** Returns whether T2 is supported. */ + public boolean getIsT2Supported() { + return mIsT2Supported; + } + /** Returns whether MISO is supported. */ + public boolean getIsMisoSupported() { + return mIsMisoSupported; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java new file mode 100644 index 000000000000..7350bc0c3914 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java @@ -0,0 +1,24 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * Frontend Capabilities. + * @hide + */ +public abstract class FrontendCapabilities { +} diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java index ef6c029fe626..5d03570eea80 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java @@ -16,7 +16,6 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.FrontendCapabilities; import android.media.tv.tuner.TunerConstants.FrontendType; /** diff --git a/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java new file mode 100644 index 000000000000..6544b17609c2 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java @@ -0,0 +1,59 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ISDBC Capabilities. + * @hide + */ +public class IsdbcFrontendCapabilities extends FrontendCapabilities { + private final int mModeCap; + private final int mBandwidthCap; + private final int mModulationCap; + private final int mCoderateCap; + private final int mGuardIntervalCap; + + IsdbcFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap, int coderateCap, + int guardIntervalCap) { + mModeCap = modeCap; + mBandwidthCap = bandwidthCap; + mModulationCap = modulationCap; + mCoderateCap = coderateCap; + mGuardIntervalCap = guardIntervalCap; + } + + /** Gets mode capability. */ + public int getModeCapability() { + return mModeCap; + } + /** Gets bandwidth capability. */ + public int getBandwidthCapability() { + return mBandwidthCap; + } + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCoderateCap; + } + /** Gets guard interval capability. */ + public int getGuardIntervalCapability() { + return mGuardIntervalCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java new file mode 100644 index 000000000000..92832b7fcbdd --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java @@ -0,0 +1,40 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ISDBS-3 Capabilities. + * @hide + */ +public class Isdbs3FrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + private final int mCoderateCap; + + Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) { + mModulationCap = modulationCap; + mCoderateCap = coderateCap; + } + + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCoderateCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java new file mode 100644 index 000000000000..b930b2578092 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java @@ -0,0 +1,40 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ISDBS Capabilities. + * @hide + */ +public class IsdbsFrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + private final int mCoderateCap; + + IsdbsFrontendCapabilities(int modulationCap, int coderateCap) { + mModulationCap = modulationCap; + mCoderateCap = coderateCap; + } + + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCoderateCap; + } +} diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java index ec177321bba3..8c0273b06e8c 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -64,7 +64,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService "com.android.mediarouteprovider.TYPE_SPECIAL"; Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); - Map<String, Integer> mRouteSessionMap = new HashMap<>(); + Map<String, String> mRouteSessionMap = new HashMap<>(); private int mNextSessionId = 1000; private void initializeRoutes() { @@ -177,7 +177,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } maybeDeselectRoute(routeId); - final int sessionId = mNextSessionId; + final String sessionId = String.valueOf(mNextSessionId); mNextSessionId++; mRoutes.put(routeId, new MediaRoute2Info.Builder(route) @@ -196,7 +196,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) { + public void onDestroySession(String sessionId, RouteSessionInfo lastSessionInfo) { for (String routeId : lastSessionInfo.getSelectedRoutes()) { mRouteSessionMap.remove(routeId); MediaRoute2Info route = mRoutes.get(routeId); @@ -209,7 +209,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onSelectRoute(int sessionId, String routeId) { + public void onSelectRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); if (route == null || sessionInfo == null) { @@ -218,7 +218,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService maybeDeselectRoute(routeId); mRoutes.put(routeId, new MediaRoute2Info.Builder(route) - .setClientPackageName(sessionInfo.getPackageName()) + .setClientPackageName(sessionInfo.getClientPackageName()) .build()); mRouteSessionMap.put(routeId, sessionId); @@ -232,7 +232,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDeselectRoute(int sessionId, String routeId) { + public void onDeselectRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); @@ -254,7 +254,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onTransferToRoute(int sessionId, String routeId) { + public void onTransferToRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) .clearSelectedRoutes() @@ -271,7 +271,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService return; } - int sessionId = mRouteSessionMap.get(routeId); + String sessionId = mRouteSessionMap.get(routeId); onDeselectRoute(sessionId, routeId); } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java index af69c7e8699f..ce4bb8ef2688 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java @@ -400,6 +400,7 @@ public class MediaRouter2Test { assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2)); assertTrue(TextUtils.equals(TYPE_SAMPLE, controller1.getRouteType())); assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType())); + } finally { releaseControllers(createdControllers); mRouter2.unregisterRouteCallback(routeCallback); @@ -486,20 +487,21 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } if (onSessionInfoChangedLatchForSelect.getCount() != 0) { // Check oldInfo - assertEquals(controller.getSessionId(), oldInfo.getSessionId()); + assertEquals(controller.getSessionId(), oldInfo.getId()); assertEquals(1, oldInfo.getSelectedRoutes().size()); assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(oldInfo.getSelectableRoutes().contains( ROUTE_ID4_TO_SELECT_AND_DESELECT)); // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(2, newInfo.getSelectedRoutes().size()); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(newInfo.getSelectedRoutes().contains( @@ -510,7 +512,7 @@ public class MediaRouter2Test { onSessionInfoChangedLatchForSelect.countDown(); } else { // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(1, newInfo.getSelectedRoutes().size()); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertFalse(newInfo.getSelectedRoutes().contains( @@ -587,18 +589,19 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } // Check oldInfo - assertEquals(controller.getSessionId(), oldInfo.getSessionId()); + assertEquals(controller.getSessionId(), oldInfo.getId()); assertEquals(1, oldInfo.getSelectedRoutes().size()); assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(oldInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO)); // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(1, newInfo.getSelectedRoutes().size()); assertFalse(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO)); @@ -665,7 +668,8 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } onSessionInfoChangedLatch.countDown(); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index c23a5b0d76e3..9ff9177c1b40 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -99,7 +99,7 @@ public class MediaRouterManagerTest { public static final String TYPE_SPECIAL = "com.android.mediarouteprovider.TYPE_SPECIAL"; - private static final String TYPE_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO"; + private static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO"; private static final int TIMEOUT_MS = 5000; diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java index 2e81a646b0db..9971fc3bbe9f 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java @@ -29,6 +29,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class RouteSessionTest { + private static final String TEST_SESSION_ID = "test_session_id"; private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest"; private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category"; @@ -36,17 +37,17 @@ public class RouteSessionTest { @Test public void testValidity() { - RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, "", TEST_CONTROL_CATEGORY) .addSelectedRoute(TEST_ROUTE_ID1) .build(); - RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME, "") .addSelectedRoute(TEST_ROUTE_ID1) .build(); - RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY) .build(); @@ -54,9 +55,9 @@ public class RouteSessionTest { .addSelectedRoute(TEST_ROUTE_ID1) .build(); - assertFalse(emptySelectedRouteSession.isValid()); assertFalse(emptyPackageSession.isValid()); assertFalse(emptyCategorySession.isValid()); + assertFalse(emptySelectedRouteSession.isValid()); assertTrue(validSession.isValid()); } } diff --git a/mms/OWNERS b/mms/OWNERS index ba00d5d75010..befc320b949c 100644 --- a/mms/OWNERS +++ b/mms/OWNERS @@ -12,3 +12,5 @@ satk@google.com shuoq@google.com refuhoo@google.com nazaninb@google.com +sarahchin@google.com +dbright@google.com
\ No newline at end of file diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 1ee85188eac3..3c3ebe2fe228 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -110,7 +110,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; @@ -277,7 +276,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationEntryManager notificationEntryManager, - NotificationRowContentBinder notificationRowContentBinder, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -365,7 +363,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt notificationGutsManager, notificationLogger, notificationEntryManager, - notificationRowContentBinder, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index 7108e65c8bce..a1eccceea771 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -70,7 +70,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.DozeParameters; @@ -147,7 +146,6 @@ public class CarStatusBarModule { NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationEntryManager notificationEntryManager, - NotificationRowContentBinder notificationRowContentBinder, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -234,7 +232,6 @@ public class CarStatusBarModule { notificationGutsManager, notificationLogger, notificationEntryManager, - notificationRowContentBinder, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index a2bd210b67a6..a784e04ee6a0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -148,17 +151,18 @@ public class A2dpProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService == null) { + return false; } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -182,12 +186,12 @@ public class A2dpProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -197,11 +201,11 @@ public class A2dpProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } boolean isA2dpPlaying() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index bc03c343a909..8ca5a74652dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothA2dpSink; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -116,18 +119,15 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -141,12 +141,12 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -156,11 +156,11 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 560cb3b9b5b4..d65b5da22056 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -112,18 +115,15 @@ public class HeadsetProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -165,12 +165,12 @@ public class HeadsetProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -180,11 +180,11 @@ public class HeadsetProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index b4b55f363020..9f1af669c708 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -146,17 +149,18 @@ public class HearingAidProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - // Downgrade priority as user is disconnecting the hearing aid. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService == null) { + return false; } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -180,12 +184,12 @@ public class HearingAidProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -195,11 +199,11 @@ public class HearingAidProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index a372e23654e0..678f2e37c6bf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -126,7 +129,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } @Override @@ -134,11 +137,8 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } @Override @@ -154,13 +154,13 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } @Override public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -171,11 +171,11 @@ final class HfpClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index 975a1e67af5b..588083e73481 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -99,13 +102,17 @@ public class HidProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -119,12 +126,12 @@ public class HidProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -132,11 +139,11 @@ public class HidProfile implements LocalBluetoothProfile { public void setPreferred(BluetoothDevice device, boolean preferred) { if (mService == null) return; if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 95139a1bfab9..7d121aaa1ad1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -115,18 +118,15 @@ public final class MapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -140,12 +140,12 @@ public final class MapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -155,11 +155,11 @@ public final class MapClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index 31a0eea56b42..a96a4e73feea 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -119,10 +121,8 @@ public class MapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -136,12 +136,12 @@ public class MapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -155,7 +155,7 @@ public class MapProfile implements LocalBluetoothProfile { mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 4ea0df621bea..56267fc596cf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -129,7 +132,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return false; } Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress()); - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { @@ -137,7 +140,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.disconnect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -151,12 +154,12 @@ public final class PbapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -166,11 +169,11 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index 3f920a8cf1dd..f7c0bf5c8c9d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -96,8 +98,10 @@ public class PbapServerProfile implements LocalBluetoothProfile { } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 0ca4d6195a32..3022c5b566eb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -112,17 +115,15 @@ final class SapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -136,12 +137,12 @@ final class SapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -151,11 +152,11 @@ final class SapProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java index 976445eb8c04..ccb6646cf683 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class A2dpSinkProfileTest { @Test public void connect_shouldConnectBluetoothA2dpSink() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothA2dpSink() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java index 69c020dd5c08..91807609df1a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class HfpClientProfileTest { @Test public void connect_shouldConnectBluetoothHeadsetClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothHeadsetClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java index 6f667094a5aa..1425c381256b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class MapClientProfileTest { @Test public void connect_shouldConnectBluetoothMapClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothMapClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java index b21ec9c3e52a..15f560bef73e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class PbapClientProfileTest { @Test public void connect_shouldConnectBluetoothPbapClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothPbapClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java index ec880345f6f0..4f978a822890 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -66,13 +69,13 @@ public class SapProfileTest { @Test public void connect_shouldConnectBluetoothSap() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothSap() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml new file mode 100644 index 000000000000..e91f840fe238 --- /dev/null +++ b/packages/SystemUI/res/layout/media_carousel.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<!-- Carousel for media controls --> +<HorizontalScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_height" + android:padding="@dimen/qs_media_padding" + android:scrollbars="none" + android:visibility="gone" + > + <LinearLayout + android:id="@+id/media_carousel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <!-- QSMediaPlayers will be added here dynamically --> + </LinearLayout> +</HorizontalScrollView> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 59d68bca93c3..6528f3762473 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -97,13 +97,11 @@ public class ExpandedAnimationController private boolean mSpringingBubbleToTouch = false; private int mExpandedViewPadding; - private float mLauncherGridDiff; public ExpandedAnimationController(Point displaySize, int expandedViewPadding, int orientation) { updateOrientation(orientation, displaySize); mExpandedViewPadding = expandedViewPadding; - mLauncherGridDiff = 30f; } /** @@ -569,15 +567,7 @@ public class ExpandedAnimationController * @return Space between bubbles in row above expanded view. */ private float getSpaceBetweenBubbles() { - /** - * Ordered left to right: - * Screen edge - * [mExpandedViewPadding] - * Expanded view edge - * [launcherGridDiff] --- arbitrary value until launcher exports widths - * Launcher's app icon grid edge that we must match - */ - final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2; + final float rowMargins = mExpandedViewPadding * 2; final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins; final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx; diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index f710f7fc47e2..f66a1ece1868 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -68,7 +68,7 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil override fun updateResources(): Boolean { with(mContext.resources) { smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size) - cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) + cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2 cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin) } requestLayout() diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index f7e4c794836e..1077834e7146 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -124,7 +124,7 @@ public class QSMediaPlayer { } } }); - btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay)); + btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play)); btn.setImageTintList(ColorStateList.valueOf(mForegroundColor)); btn.setVisibility(View.VISIBLE); @@ -199,8 +199,7 @@ public class QSMediaPlayer { List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser()); if (info != null) { for (ResolveInfo inf : info) { - if (inf.activityInfo.packageName.equals(notif.contentIntent.getCreatorPackage())) { - Log.d(TAG, "Found receiver for package: " + inf); + if (inf.activityInfo.packageName.equals(mController.getPackageName())) { mRecvComponent = inf.getComponentInfo().getComponentName(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 51e352b30019..35b8312ba25c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -184,21 +184,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne // Add media carousel if (useQsMediaPlayer(context)) { - HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext); - mediaScrollView.setHorizontalScrollBarEnabled(false); - int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height); - int padding = (int) getResources().getDimension(R.dimen.qs_media_padding); - LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight); - lpView.setMarginStart(padding); - lpView.setMarginEnd(padding); - addView(mediaScrollView, lpView); - - LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT); - mMediaCarousel = new LinearLayout(mContext); - mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL); - mediaScrollView.addView(mMediaCarousel, lpCarousel); - mediaScrollView.setVisibility(View.GONE); + HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from( + mContext).inflate(R.layout.media_carousel, this, false); + mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel); + addView(mediaScrollView); } else { mMediaCarousel = null; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index d40e25064352..cec1cb2fb53b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -106,7 +106,7 @@ public class QuickQSMediaPlayer { } } }); - btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay)); + btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play)); btn.setImageTintList(ColorStateList.valueOf(mForegroundColor)); btn.setVisibility(View.VISIBLE); } @@ -136,14 +136,25 @@ public class QuickQSMediaPlayer { * @param actionsContainer a LinearLayout containing the media action buttons * @param actionsToShow indices of which actions to display in the mini player * (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT) + * @param contentIntent Intent to send when user taps on the view */ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor, - View actionsContainer, int[] actionsToShow) { - Log.d(TAG, "Setting media session: " + token); + View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) { mToken = token; mForegroundColor = iconColor; mBackgroundColor = bgColor; - mController = new MediaController(mContext, token); + + String oldPackage = ""; + if (mController != null) { + oldPackage = mController.getPackageName(); + } + MediaController controller = new MediaController(mContext, token); + boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName()); + if (mController != null && !samePlayer && !isPlaying(controller)) { + // Only update if this is a different session and currently playing + return; + } + mController = controller; MediaMetadata mMediaMetadata = mController.getMetadata(); // Try to find a receiver for the media button that matches this app @@ -153,7 +164,6 @@ public class QuickQSMediaPlayer { if (info != null) { for (ResolveInfo inf : info) { if (inf.activityInfo.packageName.equals(mController.getPackageName())) { - Log.d(TAG, "Found receiver for package: " + inf); mRecvComponent = inf.getComponentInfo().getComponentName(); } } @@ -165,6 +175,16 @@ public class QuickQSMediaPlayer { return; } + // Action + mMediaNotifView.setOnClickListener(v -> { + try { + contentIntent.send(); + mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent was canceled: " + e.getMessage()); + } + }); + // Album art addAlbumArtBackground(mMediaMetadata, mBackgroundColor); @@ -237,12 +257,12 @@ public class QuickQSMediaPlayer { * Check whether the media controlled by this player is currently playing * @return whether it is playing, or false if no controller information */ - public boolean isPlaying() { - if (mController == null) { + public boolean isPlaying(MediaController controller) { + if (controller == null) { return false; } - PlaybackState state = mController.getPlaybackState(); + PlaybackState state = controller.getPlaybackState(); if (state == null) { return false; } @@ -261,12 +281,11 @@ public class QuickQSMediaPlayer { private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) { Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); - if (albumArt != null) { - Rect bounds = new Rect(); - mMediaNotifView.getBoundsOnScreen(bounds); - int width = bounds.width(); - int height = bounds.height(); - + Rect bounds = new Rect(); + mMediaNotifView.getBoundsOnScreen(bounds); + int width = bounds.width(); + int height = bounds.height(); + if (albumArt != null && width > 0 && height > 0) { Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); Bitmap scaled = scaleBitmap(original, width, height); Canvas canvas = new Canvas(scaled); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index db52e7d37a92..b05d4fdf7db7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -85,20 +85,19 @@ public class QuickQSPanel extends QSPanel { mHorizontalLinearLayout.setClipChildren(false); mHorizontalLinearLayout.setClipToPadding(false); - LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); - mTileLayout = new DoubleLineTileLayout(context); mMediaTileLayout = mTileLayout; mRegularTileLayout = new HeaderTileLayout(context); + LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); lp.setMarginEnd(10); lp.setMarginStart(0); mHorizontalLinearLayout.addView((View) mTileLayout, lp); mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout); - - lp.setMarginEnd(0); - lp.setMarginStart(10); - mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp); + LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); + lp2.setMarginEnd(0); + lp2.setMarginStart(25); + mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2); sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 667e721ae37d..43d0399c6d62 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -584,15 +584,7 @@ public class NotificationRemoteInputManager implements Dumpable { public void bindRow(ExpandableNotificationRow row) { row.setRemoteInputController(mRemoteInputController); - } - - /** - * Return on-click handler for notification remote views - * - * @return on-click handler - */ - public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() { - return mOnClickHandler; + row.setRemoteViewClickHandler(mOnClickHandler); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java index ec8dbead7de2..d1f6ebf3826d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java @@ -18,8 +18,6 @@ package com.android.systemui.statusbar; import android.content.Context; -import com.android.systemui.statusbar.notification.row.NotificationRowModule; - import javax.inject.Singleton; import dagger.Module; @@ -28,7 +26,7 @@ import dagger.Provides; /** * Dagger Module providing common dependencies of StatusBar. */ -@Module(includes = {NotificationRowModule.class}) +@Module public class StatusBarDependenciesModule { /** * Provides our instance of CommandQueue which is considered optional. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java index ec1efa58868e..b960b42b3e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Objects; /** * Represents a set of grouped notifications. The final notification list is usually a mix of @@ -58,22 +57,15 @@ public class GroupEntry extends ListEntry { @VisibleForTesting public void setSummary(@Nullable NotificationEntry summary) { - if (!Objects.equals(mSummary, summary)) { - mSummary = summary; - onGroupingUpdated(); - } + mSummary = summary; } void clearChildren() { - if (mChildren.size() != 0) { - mChildren.clear(); - onGroupingUpdated(); - } + mChildren.clear(); } void addChild(NotificationEntry child) { mChildren.add(child); - onGroupingUpdated(); } void sortChildren(Comparator<? super NotificationEntry> c) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index 601b3e053e8e..dc68c4bdba78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -18,38 +18,20 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.Nullable; -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.notification.collection.provider.DerivedMember; -import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider; -import com.android.systemui.statusbar.phone.NotificationGroupManager; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - /** * Abstract superclass for top-level entries, i.e. things that can appear in the final notification * list shown to users. In practice, this means either GroupEntries or NotificationEntries. */ public abstract class ListEntry { private final String mKey; - private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider(); - private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider); @Nullable private GroupEntry mParent; @Nullable private GroupEntry mPreviousParent; private int mSection; int mFirstAddedIteration = -1; - // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic - // replaced in GroupEntry and NotifListBuilderImpl - private final NotificationGroupManager mGroupManager; - ListEntry(String key) { mKey = key; - - // TODO: (b/145659174) remove - mGroupManager = Dependency.get(NotificationGroupManager.class); } public String getKey() { @@ -68,11 +50,7 @@ public abstract class ListEntry { } void setParent(@Nullable GroupEntry parent) { - if (!Objects.equals(mParent, parent)) { - invalidateParent(); - mParent = parent; - onGroupingUpdated(); - } + mParent = parent; } @Nullable public GroupEntry getPreviousParent() { @@ -91,58 +69,4 @@ public abstract class ListEntry { void setSection(int section) { mSection = section; } - - /** - * Resets the cached values of DerivedMembers. - */ - void invalidateDerivedMembers() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).invalidate(); - } - } - - /** - * Whether this notification is shown to the user as a high priority notification: visible on - * the lock screen/status bar and in the top section in the shade. - */ - public boolean isHighPriority() { - return mIsHighPriorityProvider.get(this); - } - - private void invalidateParent() { - // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children - if (getParent() != null) { - getParent().invalidateDerivedMembers(); - } - - // TODO: (b/145659174) remove - final NotificationEntry notifEntry = getRepresentativeEntry(); - if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) { - NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn()); - if (summary != null) { - summary.invalidateDerivedMembers(); - } - } - } - - void onGroupingUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onGroupingUpdated(); - } - invalidateParent(); - } - - void onSbnUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onSbnUpdated(); - } - invalidateParent(); - } - - void onRankingUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onRankingUpdated(); - } - invalidateParent(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 28e486df29b9..7301fe1df398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -204,11 +204,8 @@ public final class NotificationEntry extends ListEntry { + " doesn't match existing key " + mKey); } - if (!Objects.equals(mSbn, sbn)) { - mSbn = sbn; - mBubbleMetadata = mSbn.getNotification().getBubbleMetadata(); - onSbnUpdated(); - } + mSbn = sbn; + mBubbleMetadata = mSbn.getNotification().getBubbleMetadata(); } /** @@ -233,10 +230,7 @@ public final class NotificationEntry extends ListEntry { + " doesn't match existing key " + mKey); } - if (!Objects.equals(mRanking, ranking)) { - mRanking = ranking; - onRankingUpdated(); - } + mRanking = ranking; } /* diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 7010943559ba..3bbd722517f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -24,6 +24,7 @@ import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.logging.NotifEvent import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier @@ -54,7 +55,8 @@ open class NotificationRankingManager @Inject constructor( private val notifFilter: NotificationFilter, private val notifLog: NotifLog, sectionsFeatureManager: NotificationSectionsFeatureManager, - private val peopleNotificationIdentifier: PeopleNotificationIdentifier + private val peopleNotificationIdentifier: PeopleNotificationIdentifier, + private val highPriorityProvider: HighPriorityProvider ) { var rankingMap: RankingMap? = null @@ -81,6 +83,9 @@ open class NotificationRankingManager @Inject constructor( val aHeadsUp = a.isRowHeadsUp val bHeadsUp = b.isRowHeadsUp + val aIsHighPriority = a.isHighPriority() + val bIsHighPriority = b.isHighPriority() + when { usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1 aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1 @@ -90,8 +95,8 @@ open class NotificationRankingManager @Inject constructor( aMedia != bMedia -> if (aMedia) -1 else 1 // Upsort PRIORITY_MAX system notifications aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1 - a.isHighPriority != b.isHighPriority -> - -1 * a.isHighPriority.compareTo(b.isHighPriority) + aIsHighPriority != bIsHighPriority -> + -1 * aIsHighPriority.compareTo(bIsHighPriority) aRank != bRank -> aRank - bRank else -> nb.notification.`when`.compareTo(na.notification.`when`) } @@ -154,7 +159,7 @@ open class NotificationRankingManager @Inject constructor( ) { if (usePeopleFiltering && entry.isPeopleNotification()) { entry.bucket = BUCKET_PEOPLE - } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) { + } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) { entry.bucket = BUCKET_ALERTING } else { entry.bucket = BUCKET_SILENT @@ -178,10 +183,6 @@ open class NotificationRankingManager @Inject constructor( // TODO: notify group manager here? groupManager.onEntryUpdated(entry, oldSbn) } - - // TODO: (b/145659174) remove after moving to new NotifPipeline - // (should be able to remove all groupManager code post-migration) - entry.invalidateDerivedMembers() } } } @@ -191,6 +192,9 @@ open class NotificationRankingManager @Inject constructor( sbn.isPeopleNotification() private fun StatusBarNotification.isPeopleNotification() = peopleNotificationIdentifier.isPeopleNotification(this) + + private fun NotificationEntry.isHighPriority() = + highPriorityProvider.isHighPriority(this) } // Convenience functions diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java index 20f206b91f10..6dc647d33046 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java @@ -41,8 +41,8 @@ import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -65,7 +65,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { Dependency.get(NotificationInterruptionStateProvider.class); private final Context mContext; - private final NotificationRowContentBinder mRowContentBinder; private final NotificationMessagingUtil mMessagingUtil; private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = this::logNotificationExpansion; @@ -77,7 +76,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; private HeadsUpManager mHeadsUpManager; - private NotificationRowContentBinder.InflationCallback mInflationCallback; + private NotificationContentInflater.InflationCallback mInflationCallback; private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; @@ -85,13 +84,11 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { public NotificationRowBinderImpl( Context context, - NotificationRowContentBinder rowContentBinder, boolean allowLongPress, KeyguardBypassController keyguardBypassController, StatusBarStateController statusBarStateController, NotificationLogger logger) { mContext = context; - mRowContentBinder = rowContentBinder; mMessagingUtil = new NotificationMessagingUtil(context); mAllowLongPress = allowLongPress; mKeyguardBypassController = keyguardBypassController; @@ -120,7 +117,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mOnAppOpsClickListener = mGutsManager::openGuts; } - public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) { + public void setInflationCallback(NotificationContentInflater.InflationCallback callback) { mInflationCallback = callback; } @@ -159,6 +156,19 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private void bindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row, Runnable onDismissRunnable) { + row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey()); + row.setBypassController(mKeyguardBypassController); + row.setStatusBarStateController(mStatusBarStateController); + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setOnExpandClickListener(mPresenter); + row.setInflationCallback(mInflationCallback); + if (mAllowLongPress) { + row.setLongPressListener(mGutsManager::openGuts); + } + mListContainer.bindRow(row); + getRemoteInputManager().bindRow(row); + // Get the app name. // Note that Notification.Builder#bindHeaderAppName has similar logic // but since this field is used in the guts, it must be accurate. @@ -176,33 +186,15 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } catch (PackageManager.NameNotFoundException e) { // Do nothing } - - row.initialize( - appname, - sbn.getKey(), - mExpansionLogger, - mKeyguardBypassController, - mGroupManager, - mHeadsUpManager, - mRowContentBinder, - mPresenter); - - // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely - row.setStatusBarStateController(mStatusBarStateController); - row.setInflationCallback(mInflationCallback); - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - if (mAllowLongPress) { - row.setLongPressListener(mGutsManager::openGuts); - } - mListContainer.bindRow(row); - getRemoteInputManager().bindRow(row); - + row.setAppName(appname); row.setOnDismissRunnable(onDismissRunnable); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (ENABLE_REMOTE_INPUT) { row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } + row.setAppOpsOnClickListener(mOnAppOpsClickListener); + mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index 9312c2260d46..db107f531e9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; import javax.inject.Inject; @@ -62,6 +63,7 @@ public class KeyguardCoordinator implements Coordinator { private final BroadcastDispatcher mBroadcastDispatcher; private final StatusBarStateController mStatusBarStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final HighPriorityProvider mHighPriorityProvider; @Inject public KeyguardCoordinator( @@ -71,15 +73,16 @@ public class KeyguardCoordinator implements Coordinator { NotificationLockscreenUserManager lockscreenUserManager, BroadcastDispatcher broadcastDispatcher, StatusBarStateController statusBarStateController, - KeyguardUpdateMonitor keyguardUpdateMonitor) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + HighPriorityProvider highPriorityProvider) { mContext = context; mMainHandler = mainThreadHandler; mKeyguardStateController = keyguardStateController; mLockscreenUserManager = lockscreenUserManager; - mBroadcastDispatcher = broadcastDispatcher; mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mHighPriorityProvider = highPriorityProvider; } @Override @@ -151,7 +154,7 @@ public class KeyguardCoordinator implements Coordinator { } if (NotificationUtils.useNewInterruptionModel(mContext) && hideSilentNotificationsOnLockscreen()) { - return entry.isHighPriority(); + return mHighPriorityProvider.isHighPriority(entry); } else { return entry.getRepresentativeEntry() != null && !entry.getRepresentativeEntry().getRanking().isAmbient(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java deleted file mode 100644 index 815e6f7eaa46..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java +++ /dev/null @@ -1,62 +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.systemui.statusbar.notification.collection.provider; -/** - * Caches a computed value until invalidate() is called - * @param <Parent> Object used to computeValue - * @param <Value> type of value to cache until invalidate is called - */ -public abstract class DerivedMember<Parent, Value> { - private Value mValue; - protected abstract Value computeValue(Parent parent); - - /** - * Gets the last cached value, else recomputes the value. - */ - public Value get(Parent parent) { - if (mValue == null) { - mValue = computeValue(parent); - } - return mValue; - } - - /** - * Resets the cached value. - * Next time "get" is called, the value is recomputed. - */ - public void invalidate() { - mValue = null; - } - - /** - * Called when a NotificationEntry's status bar notification has updated. - * Derived members can invalidate here. - */ - public void onSbnUpdated() {} - - /** - * Called when a NotificationEntry's Ranking has updated. - * Derived members can invalidate here. - */ - public void onRankingUpdated() {} - - /** - * Called when a ListEntry's grouping information (parent or children) has changed. - * Derived members can invalidate here. - */ - public void onGroupingUpdated() {} -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java new file mode 100644 index 000000000000..3cc5e62c0bda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -0,0 +1,109 @@ +/* + * 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.provider; + +import android.app.Notification; +import android.app.NotificationManager; + +import com.android.systemui.statusbar.notification.collection.GroupEntry; +import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Determines whether a notification is considered 'high priority'. + * + * Notifications that are high priority are visible on the lock screen/status bar and in the top + * section in the shade. + */ +@Singleton +public class HighPriorityProvider { + private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; + + @Inject + public HighPriorityProvider(PeopleNotificationIdentifier peopleNotificationIdentifier) { + mPeopleNotificationIdentifier = peopleNotificationIdentifier; + } + + /** + * @return true if the ListEntry is high priority, else false + * + * A NotificationEntry is considered high priority if it: + * - has importance greater than or equal to IMPORTANCE_DEFAULT + * OR + * - their importance has NOT been set to a low priority option by the user AND the + * notification fulfills one of the following: + * - has a person associated with it + * - has a media session associated with it + * - has messaging style + * + * A GroupEntry is considered high priority if its representativeEntry (summary) or children are + * high priority + */ + public boolean isHighPriority(ListEntry entry) { + if (entry == null) { + return false; + } + + final NotificationEntry notifEntry = entry.getRepresentativeEntry(); + return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT + || hasHighPriorityCharacteristics(notifEntry) + || hasHighPriorityChild(entry); + } + + + private boolean hasHighPriorityChild(ListEntry entry) { + if (entry instanceof GroupEntry) { + for (NotificationEntry child : ((GroupEntry) entry).getChildren()) { + if (isHighPriority(child)) { + return true; + } + } + } + return false; + } + + private boolean hasHighPriorityCharacteristics(NotificationEntry entry) { + return !hasUserSetImportance(entry) + && (isImportantOngoing(entry) + || entry.getSbn().getNotification().hasMediaSession() + || isPeopleNotification(entry) + || isMessagingStyle(entry)); + } + + private boolean isImportantOngoing(NotificationEntry entry) { + return entry.getSbn().getNotification().isForegroundService() + && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW; + } + + private boolean isMessagingStyle(NotificationEntry entry) { + return Notification.MessagingStyle.class.equals( + entry.getSbn().getNotification().getNotificationStyle()); + } + + private boolean isPeopleNotification(NotificationEntry entry) { + return mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn()); + } + + private boolean hasUserSetImportance(NotificationEntry entry) { + return entry.getRanking().getChannel() != null + && entry.getRanking().getChannel().hasUserSetImportance(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java deleted file mode 100644 index 76e256b9be2d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java +++ /dev/null @@ -1,148 +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.systemui.statusbar.notification.collection.provider; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Person; - -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.phone.NotificationGroupManager; - -import java.util.ArrayList; -import java.util.List; - -/** - * Whether the ListEntry is shown to the user as a high priority notification: visible on - * the lock screen/status bar and in the top section in the shade. - * - * A NotificationEntry is considered high priority if it: - * - has importance greater than or equal to IMPORTANCE_DEFAULT - * OR - * - their importance has NOT been set to a low priority option by the user AND the notification - * fulfills one of the following: - * - has a person associated with it - * - has a media session associated with it - * - has messaging style - * - * A GroupEntry is considered high priority if its representativeEntry (summary) or children are - * high priority - */ -public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> { - // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic - // replaced in GroupEntry and NotifListBuilderImpl - private final NotificationGroupManager mGroupManager; - - - public IsHighPriorityProvider() { - // TODO: (b/145659174) remove - mGroupManager = Dependency.get(NotificationGroupManager.class); - } - - @Override - protected Boolean computeValue(ListEntry entry) { - if (entry == null) { - return false; - } - - return isHighPriority(entry); - } - - private boolean isHighPriority(ListEntry listEntry) { - // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING) - final NotificationEntry notifEntry = listEntry.getRepresentativeEntry(); - return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT - || hasHighPriorityCharacteristics(notifEntry) - || hasHighPriorityChild(listEntry); - - } - - private boolean hasHighPriorityChild(ListEntry entry) { - // TODO: (b/145659174) remove - if (entry instanceof NotificationEntry) { - NotificationEntry notifEntry = (NotificationEntry) entry; - if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) { - List<NotificationEntry> logicalChildren = - mGroupManager.getLogicalChildren(notifEntry.getSbn()); - for (NotificationEntry child : logicalChildren) { - if (child.isHighPriority()) { - return true; - } - } - } - } - - if (entry instanceof GroupEntry) { - for (NotificationEntry child : ((GroupEntry) entry).getChildren()) { - if (child.isHighPriority()) { - return true; - } - } - } - return false; - } - - private boolean hasHighPriorityCharacteristics(NotificationEntry entry) { - return !hasUserSetImportance(entry) - && (isImportantOngoing(entry) - || entry.getSbn().getNotification().hasMediaSession() - || hasPerson(entry) - || isMessagingStyle(entry)); - } - - private boolean isImportantOngoing(NotificationEntry entry) { - return entry.getSbn().getNotification().isForegroundService() - && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW; - } - - private boolean isMessagingStyle(NotificationEntry entry) { - return Notification.MessagingStyle.class.equals( - entry.getSbn().getNotification().getNotificationStyle()); - } - - private boolean hasPerson(NotificationEntry entry) { - // TODO: cache favorite and recent contacts to check contact affinity - Notification notification = entry.getSbn().getNotification(); - ArrayList<Person> people = notification.extras != null - ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST) - : new ArrayList<>(); - return people != null && !people.isEmpty(); - } - - private boolean hasUserSetImportance(NotificationEntry entry) { - return entry.getRanking().getChannel() != null - && entry.getRanking().getChannel().hasUserSetImportance(); - } - - @Override - public void onSbnUpdated() { - invalidate(); - } - - @Override - public void onRankingUpdated() { - invalidate(); - } - - @Override - public void onGroupingUpdated() { - invalidate(); - } -} 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 a8a35d07b3f0..3c247df692f4 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 @@ -65,6 +65,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.Chronometer; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -149,7 +150,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private StatusBarStateController mStatusbarStateController; private KeyguardBypassController mBypassController; private LayoutListener mLayoutListener; - private NotificationRowContentBinder mNotificationContentBinder; + private final NotificationContentInflater mNotificationInflater; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mMaxHeadsUpHeightBeforeN; @@ -463,7 +464,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Inflate views based off the inflation flags set. Inflation happens asynchronously. */ public void inflateViews() { - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, + mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams, false /* forceInflate */, mInflationCallback); } @@ -477,7 +478,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // View should not be reinflated in the future clearInflationFlags(inflationFlag); Runnable freeViewRunnable = - () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag); + () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag); switch (inflationFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, @@ -741,10 +742,23 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsHeadsUp || mHeadsupDisappearRunning; } + + public void setGroupManager(NotificationGroupManager groupManager) { + mGroupManager = groupManager; + mPrivateLayout.setGroupManager(groupManager); + } + public void setRemoteInputController(RemoteInputController r) { mPrivateLayout.setRemoteInputController(r); } + public void setAppName(String appName) { + mAppName = appName; + if (mMenuRow != null && mMenuRow.getMenuView() != null) { + mMenuRow.setAppName(mAppName); + } + } + public void addChildNotification(ExpandableNotificationRow row) { addChildNotification(row, -1); } @@ -838,7 +852,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mIsChildInGroup = isChildInGroup; if (mIsLowPriority) { int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams, + mNotificationInflater.bindContent(mEntry, this, flags, mBindParams, false /* forceInflate */, mInflationCallback); } } @@ -1091,6 +1105,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mPrivateLayout.getContractedNotificationHeader(); } + public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { + mOnExpandClickListener = onExpandClickListener; + } + public void setLongPressListener(LongPressListener longPressListener) { mLongPressListener = longPressListener; } @@ -1113,6 +1131,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + public HeadsUpManager getHeadsUpManager() { return mHeadsUpManager; } @@ -1237,7 +1259,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reInflateViews(); } mEntry.getSbn().clearPackageContext(); - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, + mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams, true /* forceInflate */, mInflationCallback); } @@ -1612,6 +1634,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mBindParams.usesIncreasedHeadsUpHeight = use; } + public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { + mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler); + } + /** * Set callback for notification content inflation * @@ -1626,7 +1652,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNeedsRedaction = needsRedaction; if (needsRedaction) { setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, + mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, mBindParams, false /* forceInflate */, mInflationCallback); } else { clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); @@ -1635,12 +1661,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + @VisibleForTesting + public NotificationContentInflater getNotificationInflater() { + return mNotificationInflater; + } + public interface ExpansionLogger { void logNotificationExpansion(String key, boolean userAction, boolean expanded); } public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); + mNotificationInflater = new NotificationContentInflater(); mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); @@ -1648,30 +1680,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView initDimens(); } - /** - * Initialize row. - */ - public void initialize( - String appName, - String notificationKey, - ExpansionLogger logger, - KeyguardBypassController bypassController, - NotificationGroupManager groupManager, - HeadsUpManager headsUpManager, - NotificationRowContentBinder rowContentBinder, - OnExpandClickListener onExpandClickListener) { - mAppName = appName; - if (mMenuRow != null && mMenuRow.getMenuView() != null) { - mMenuRow.setAppName(mAppName); - } - mLogger = logger; - mLoggingKey = notificationKey; + public void setBypassController(KeyguardBypassController bypassController) { mBypassController = bypassController; - mGroupManager = groupManager; - mPrivateLayout.setGroupManager(groupManager); - mHeadsUpManager = headsUpManager; - mNotificationContentBinder = rowContentBinder; - mOnExpandClickListener = onExpandClickListener; } public void setStatusBarStateController(StatusBarStateController statusBarStateController) { @@ -2910,6 +2920,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return 0; } + public void setExpansionLogger(ExpansionLogger logger, String key) { + mLogger = logger; + mLoggingKey = key; + } + public void onExpandedByGesture(boolean userExpanded) { int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java deleted file mode 100644 index c11c60fcdd04..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java +++ /dev/null @@ -1,75 +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.row; - -import android.widget.RemoteViews; - -import androidx.annotation.Nullable; - -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; - -/** - * Caches {@link RemoteViews} for a notification's content views. - */ -public interface NotifRemoteViewCache { - - /** - * Whether the notification has the remote view cached - * - * @param entry notification - * @param flag inflation flag for content view - * @return true if the remote view is cached - */ - boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag); - - /** - * Get the remote view for the content flag specified. - * - * @param entry notification - * @param flag inflation flag for the content view - * @return the remote view if it is cached, null otherwise - */ - @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag); - - /** - * Cache a remote view for a given content flag on a notification. - * - * @param entry notification - * @param flag inflation flag for the content view - * @param remoteView remote view to store - */ - void putCachedView( - NotificationEntry entry, - @InflationFlag int flag, - RemoteViews remoteView); - - /** - * Remove a cached remote view for a given content flag on a notification. - * - * @param entry notification - * @param flag inflation flag for the content view - */ - void removeCachedView(NotificationEntry entry, @InflationFlag int flag); - - /** - * Clear a notification's remote view cache. - * - * @param entry notification - */ - void clearCache(NotificationEntry entry); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java deleted file mode 100644 index a19099a6ea52..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java +++ /dev/null @@ -1,100 +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.row; - -import android.util.ArrayMap; -import android.util.SparseArray; -import android.widget.RemoteViews; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; - -import java.util.Map; - -import javax.inject.Inject; - -/** - * Implementation of remote view cache that keeps remote views cached for all active notifications. - */ -public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { - private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews = - new ArrayMap<>(); - - @Inject - NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) { - entryManager.addNotificationEntryListener(mEntryListener); - } - - @Override - public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) { - return getCachedView(entry, flag) != null; - } - - @Override - public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) { - return getContentViews(entry).get(flag); - } - - @Override - public void putCachedView( - NotificationEntry entry, - @InflationFlag int flag, - RemoteViews remoteView) { - getContentViews(entry).put(flag, remoteView); - } - - @Override - public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) { - getContentViews(entry).remove(flag); - } - - @Override - public void clearCache(NotificationEntry entry) { - getContentViews(entry).clear(); - } - - private @NonNull SparseArray<RemoteViews> getContentViews(NotificationEntry entry) { - SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); - if (contentViews == null) { - throw new IllegalStateException( - String.format("Remote view cache was never created for notification %s", - entry.getKey())); - } - return contentViews; - } - - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { - @Override - public void onPendingEntryAdded(NotificationEntry entry) { - mNotifCachedContentViews.put(entry, new SparseArray<>()); - } - - @Override - public void onEntryRemoved( - NotificationEntry entry, - @Nullable NotificationVisibility visibility, - boolean removedByUser) { - mNotifCachedContentViews.remove(entry); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index e1a6747b5398..30f22ac5e161 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; @@ -27,6 +26,7 @@ import android.content.Context; import android.os.AsyncTask; import android.os.CancellationSignal; import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; import android.util.Log; import android.view.View; import android.widget.RemoteViews; @@ -35,7 +35,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.Dependency; import com.android.systemui.statusbar.InflationTask; -import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; @@ -50,30 +49,17 @@ import com.android.systemui.util.Assert; import java.util.HashMap; -import javax.inject.Inject; -import javax.inject.Singleton; - /** * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by * asynchronously building the content's {@link RemoteViews} and applying it to the row. */ -@Singleton -@VisibleForTesting(visibility = PACKAGE) public class NotificationContentInflater implements NotificationRowContentBinder { public static final String TAG = "NotifContentInflater"; + private RemoteViews.OnClickHandler mRemoteViewClickHandler; private boolean mInflateSynchronously = false; - private final NotificationRemoteInputManager mRemoteInputManager; - private final NotifRemoteViewCache mRemoteViewCache; - - @Inject - public NotificationContentInflater( - NotifRemoteViewCache remoteViewCache, - NotificationRemoteInputManager remoteInputManager) { - mRemoteViewCache = remoteViewCache; - mRemoteInputManager = remoteInputManager; - } + private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>(); @Override public void bindContent( @@ -90,27 +76,27 @@ public class NotificationContentInflater implements NotificationRowContentBinder return; } - StatusBarNotification sbn = entry.getSbn(); + StatusBarNotification sbn = row.getEntry().getSbn(); // To check if the notification has inline image and preload inline image if necessary. row.getImageResolver().preloadImages(sbn.getNotification()); if (forceInflate) { - mRemoteViewCache.clearCache(entry); + mCachedContentViews.clear(); } AsyncInflationTask task = new AsyncInflationTask( + sbn, mInflateSynchronously, contentToBind, - mRemoteViewCache, - entry, + mCachedContentViews, row, bindParams.isLowPriority, bindParams.isChildInGroup, bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, callback, - mRemoteInputManager.getRemoteViewsOnClickHandler()); + mRemoteViewClickHandler); if (mInflateSynchronously) { task.onPostExecute(task.doInBackground()); } else { @@ -137,15 +123,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(), packageContext, row.getHeadsUpManager(), row.getExistingSmartRepliesAndActions()); - apply( inflateSynchronously, result, reInflateFlags, - mRemoteViewCache, - entry, + mCachedContentViews, row, - mRemoteInputManager.getRemoteViewsOnClickHandler(), + mRemoteViewClickHandler, null); return result; } @@ -165,7 +149,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder int curFlag = 1; while (contentToUnbind != 0) { if ((contentToUnbind & curFlag) != 0) { - freeNotificationView(entry, row, curFlag); + freeNotificationView(row, curFlag); } contentToUnbind &= ~curFlag; curFlag = curFlag << 1; @@ -173,25 +157,34 @@ public class NotificationContentInflater implements NotificationRowContentBinder } /** + * Set click handler for notification remote views + * + * @param remoteViewClickHandler click handler for remote views + */ + public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { + mRemoteViewClickHandler = remoteViewClickHandler; + } + + /** * Frees the content view associated with the inflation flag. Will only succeed if the * view is safe to remove. * * @param inflateFlag the flag corresponding to the content view which should be freed */ - private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row, + private void freeNotificationView(ExpandableNotificationRow row, @InflationFlag int inflateFlag) { switch (inflateFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) { row.getPrivateLayout().setHeadsUpChild(null); - mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); + mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP); row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null); } break; case FLAG_CONTENT_VIEW_PUBLIC: if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) { row.getPublicLayout().setContractedChild(null); - mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC); + mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC); } break; case FLAG_CONTENT_VIEW_CONTRACTED: @@ -252,12 +245,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result; } - private static CancellationSignal apply( + public static CancellationSignal apply( boolean inflateSynchronously, InflationProgress result, @InflationFlag int reInflateFlags, - NotifRemoteViewCache remoteViewCache, - NotificationEntry entry, + ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row, RemoteViews.OnClickHandler remoteViewClickHandler, @Nullable InflationCallback callback) { @@ -269,7 +261,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & flag) != 0) { boolean isNewView = !canReapplyRemoteView(result.newContentView, - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)); + cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -281,8 +273,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newContentView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, - entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews, + row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getContractedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); @@ -293,7 +285,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.newExpandedView != null) { boolean isNewView = !canReapplyRemoteView(result.newExpandedView, - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)); + cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -305,8 +297,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newExpandedView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, - entry, row, isNewView, remoteViewClickHandler, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, + cachedContentViews, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getExpandedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations, @@ -319,7 +311,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.newHeadsUpView != null) { boolean isNewView = !canReapplyRemoteView(result.newHeadsUpView, - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)); + cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -331,8 +323,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newHeadsUpView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, - entry, row, isNewView, remoteViewClickHandler, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, + cachedContentViews, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getHeadsUpChild(), privateLayout.getVisibleWrapper( VISIBLE_TYPE_HEADSUP), runningInflations, @@ -344,7 +336,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & flag) != 0) { boolean isNewView = !canReapplyRemoteView(result.newPublicView, - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)); + cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -356,16 +348,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newPublicView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, - entry, row, isNewView, remoteViewClickHandler, callback, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews, + row, isNewView, remoteViewClickHandler, callback, publicLayout, publicLayout.getContractedChild(), publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); } // Let's try to finish, maybe nobody is even inflating anything - finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry, - row); + finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener( () -> runningInflations.values().forEach(CancellationSignal::cancel)); @@ -378,8 +369,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder final InflationProgress result, final @InflationFlag int reInflateFlags, @InflationFlag int inflationId, - final NotifRemoteViewCache remoteViewCache, - final NotificationEntry entry, + final ArrayMap<Integer, RemoteViews> cachedContentViews, final ExpandableNotificationRow row, boolean isNewView, RemoteViews.OnClickHandler remoteViewClickHandler, @@ -432,8 +422,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder existingWrapper.onReinflated(); } runningInflations.remove(inflationId); - finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, - callback, entry, row); + finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, + callback, row); } @Override @@ -498,11 +488,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder * @return true if the inflation was finished */ private static boolean finishIfDone(InflationProgress result, - @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, + @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews, HashMap<Integer, CancellationSignal> runningInflations, - @Nullable InflationCallback endListener, NotificationEntry entry, - ExpandableNotificationRow row) { + @Nullable InflationCallback endListener, ExpandableNotificationRow row) { Assert.isMainThread(); + NotificationEntry entry = row.getEntry(); NotificationContentView privateLayout = row.getPrivateLayout(); NotificationContentView publicLayout = row.getPublicLayout(); if (runningInflations.isEmpty()) { @@ -510,27 +500,23 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.inflatedContentView != null) { // New view case privateLayout.setContractedChild(result.inflatedContentView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, - result.newContentView); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) { + cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); + } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) { // Reinflation case. Only update if it's still cached (i.e. view has not been // freed while inflating). - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, - result.newContentView); + cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); } } if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { if (result.inflatedExpandedView != null) { privateLayout.setExpandedChild(result.inflatedExpandedView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, - result.newExpandedView); + cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); } else if (result.newExpandedView == null) { privateLayout.setExpandedChild(null); - remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, - result.newExpandedView); + cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null); + } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) { + cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); } if (result.newExpandedView != null) { privateLayout.setExpandedInflatedSmartReplies( @@ -544,14 +530,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { if (result.inflatedHeadsUpView != null) { privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, - result.newHeadsUpView); + cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); } else if (result.newHeadsUpView == null) { privateLayout.setHeadsUpChild(null); - remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, - result.newHeadsUpView); + cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null); + } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) { + cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); } if (result.newHeadsUpView != null) { privateLayout.setHeadsUpInflatedSmartReplies( @@ -564,18 +548,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { if (result.inflatedPublicView != null) { publicLayout.setContractedChild(result.inflatedPublicView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, - result.newPublicView); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, - result.newPublicView); + cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); + } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) { + cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); } } entry.headsUpStatusBarText = result.headsUpStatusBarText; entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { - endListener.onAsyncInflationFinished(entry, reInflateFlags); + endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags); } return true; } @@ -633,7 +615,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress> implements InflationCallback, InflationTask { - private final NotificationEntry mEntry; + private final StatusBarNotification mSbn; private final Context mContext; private final boolean mInflateSynchronously; private final boolean mIsLowPriority; @@ -642,17 +624,17 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; private @InflationFlag int mReInflateFlags; - private final NotifRemoteViewCache mRemoteViewCache; + private final ArrayMap<Integer, RemoteViews> mCachedContentViews; private ExpandableNotificationRow mRow; private Exception mError; private RemoteViews.OnClickHandler mRemoteViewClickHandler; private CancellationSignal mCancellationSignal; private AsyncInflationTask( + StatusBarNotification notification, boolean inflateSynchronously, @InflationFlag int reInflateFlags, - NotifRemoteViewCache cache, - NotificationEntry entry, + ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row, boolean isLowPriority, boolean isChildInGroup, @@ -660,11 +642,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder boolean usesIncreasedHeadsUpHeight, InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler) { - mEntry = entry; mRow = row; + mSbn = notification; mInflateSynchronously = inflateSynchronously; mReInflateFlags = reInflateFlags; - mRemoteViewCache = cache; + mCachedContentViews = cachedContentViews; mContext = mRow.getContext(); mIsLowPriority = isLowPriority; mIsChildInGroup = isChildInGroup; @@ -672,6 +654,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; mRemoteViewClickHandler = remoteViewClickHandler; mCallback = callback; + NotificationEntry entry = row.getEntry(); entry.setInflationTask(this); } @@ -684,13 +667,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override protected InflationProgress doInBackground(Void... params) { try { - final StatusBarNotification sbn = mEntry.getSbn(); final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(mContext, - sbn.getNotification()); + mSbn.getNotification()); - Context packageContext = sbn.getPackageContext(mContext); - Notification notification = sbn.getNotification(); + Context packageContext = mSbn.getPackageContext(mContext); + Notification notification = mSbn.getNotification(); if (notification.isMediaNotification()) { MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, packageContext); @@ -699,7 +681,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext); - return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry, + return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(), mRow.getContext(), packageContext, mRow.getHeadsUpManager(), mRow.getExistingSmartRepliesAndActions()); } catch (Exception e) { @@ -712,15 +694,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder protected void onPostExecute(InflationProgress result) { if (mError == null) { mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags, - mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this); + mCachedContentViews, mRow, mRemoteViewClickHandler, this); } else { handleError(mError); } } private void handleError(Exception e) { - mEntry.onInflationTaskFinished(); - StatusBarNotification sbn = mEntry.getSbn(); + mRow.getEntry().onInflationTaskFinished(); + StatusBarNotification sbn = mRow.getEntry().getSbn(); final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); @@ -754,10 +736,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override public void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags) { - mEntry.onInflationTaskFinished(); + mRow.getEntry().onInflationTaskFinished(); mRow.onNotificationUpdated(); if (mCallback != null) { - mCallback.onAsyncInflationFinished(mEntry, inflatedFlags); + mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags); } // Notify the resolver that the inflation task has finished, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6f2abba128d6..779a224ecb62 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.StatusBar; @@ -81,6 +82,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final Context mContext; private final VisualStabilityManager mVisualStabilityManager; private final AccessibilityManager mAccessibilityManager; + private final HighPriorityProvider mHighPriorityProvider; // Dependencies: private final NotificationLockscreenUserManager mLockscreenUserManager = @@ -109,12 +111,14 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @Inject public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager, Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler, - AccessibilityManager accessibilityManager) { + AccessibilityManager accessibilityManager, + HighPriorityProvider highPriorityProvider) { mContext = context; mVisualStabilityManager = visualStabilityManager; mStatusBarLazy = statusBarLazy; mMainHandler = mainHandler; mAccessibilityManager = accessibilityManager; + mHighPriorityProvider = highPriorityProvider; } public void setUpWithPresenter(NotificationPresenter presenter, @@ -331,8 +335,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx row.getIsNonblockable(), isForBlockingHelper, row.getEntry().getImportance(), - row.getEntry().isHighPriority()); - + mHighPriorityProvider.isHighPriority(row.getEntry())); } /** 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 b4ccb567504a..edfd1b4d3c85 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 @@ -46,6 +46,7 @@ import com.android.systemui.R; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.AlphaOptimizedImageView; import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent; +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import java.util.ArrayList; @@ -269,7 +270,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } mAppOpsItem = createAppOpsItem(mContext); if (mIsUsingBidirectionalSwipe) { - mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority()); + mInfoItem = createInfoItem(mContext, + mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT); } else { mInfoItem = createInfoItem(mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java deleted file mode 100644 index df8653cf2406..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ /dev/null @@ -1,44 +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.row; - -import javax.inject.Singleton; - -import dagger.Binds; -import dagger.Module; - -/** - * Dagger Module containing notification row and view inflation implementations. - */ -@Module -public abstract class NotificationRowModule { - /** - * Provides notification row content binder instance. - */ - @Binds - @Singleton - public abstract NotificationRowContentBinder provideNotificationRowContentBinder( - NotificationContentInflater contentBinderImpl); - - /** - * Provides notification remote view cache instance. - */ - @Binds - @Singleton - public abstract NotifRemoteViewCache provideNotifRemoteViewCache( - NotifRemoteViewCacheImpl cacheImpl); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index 352ba0f75a32..76fdfc6fbabc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -28,6 +28,7 @@ import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.metrics.LogMaker; import android.os.Handler; +import android.service.notification.StatusBarNotification; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -176,27 +177,30 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras .getParcelable(Notification.EXTRA_MEDIA_SESSION); - if (Utils.useQsMediaPlayer(mContext)) { + if (Utils.useQsMediaPlayer(mContext) && token != null) { final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras .getIntArray(Notification.EXTRA_COMPACT_ACTIONS); int tintColor = getNotificationHeader().getOriginalIconColor(); StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class); QuickQSPanel panel = ctrl.getStatusBarView().findViewById( com.android.systemui.R.id.quick_qs_panel); + StatusBarNotification sbn = mRow.getEntry().getSbn(); + Notification notif = sbn.getNotification(); panel.getMediaPlayer().setMediaSession(token, - mRow.getEntry().getSbn().getNotification().getSmallIcon(), + notif.getSmallIcon(), tintColor, mBackgroundColor, mActions, - compactActions); + compactActions, + notif.contentIntent); QSPanel bigPanel = ctrl.getStatusBarView().findViewById( com.android.systemui.R.id.quick_settings_panel); bigPanel.addMediaSession(token, - mRow.getEntry().getSbn().getNotification().getSmallIcon(), + notif.getSmallIcon(), tintColor, mBackgroundColor, mActions, - mRow.getEntry().getSbn()); + sbn); } boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar(); 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 896b6e570da2..fe0739f9088c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; @@ -427,7 +428,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * The notification is still pending inflation but we've decided that we no longer need * the content view (e.g. suppression might have changed and we decided we need to transfer * back). However, there is no way to abort just this inflation if other inflation requests - * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead + * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead * we just flag it as aborted and free when it's inflated. */ boolean mAbortOnInflation; 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 30825ed65eb3..ccc86b1f8c5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -205,7 +205,6 @@ import com.android.systemui.statusbar.notification.collection.init.NewNotifPipel import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.BatteryController; @@ -414,7 +413,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; private final NotificationEntryManager mEntryManager; - private final NotificationRowContentBinder mRowContentBinder; private NotificationListController mNotificationListController; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final NotificationViewHierarchyManager mViewHierarchyManager; @@ -636,7 +634,6 @@ public class StatusBar extends SystemUI implements DemoMode, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationEntryManager notificationEntryManager, - NotificationRowContentBinder notificationRowContentBinder, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -718,7 +715,6 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager = notificationGutsManager; mNotificationLogger = notificationLogger; mEntryManager = notificationEntryManager; - mRowContentBinder = notificationRowContentBinder; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mViewHierarchyManager = notificationViewHierarchyManager; mKeyguardViewMediator = keyguardViewMediator; @@ -1244,7 +1240,6 @@ public class StatusBar extends SystemUI implements DemoMode, final NotificationRowBinderImpl rowBinder = new NotificationRowBinderImpl( mContext, - mRowContentBinder, mAllowNotificationLongPress, mKeyguardBypassController, mStatusBarStateController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java index df741079de29..153ca22933a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java @@ -69,7 +69,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -126,7 +125,6 @@ public class StatusBarModule { NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationEntryManager notificationEntryManager, - NotificationRowContentBinder notificationRowContentBinder, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -209,7 +207,6 @@ public class StatusBarModule { notificationGutsManager, notificationLogger, notificationEntryManager, - notificationRowContentBinder, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 3fdbd3edfcaa..77659df738c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -47,9 +47,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; @@ -75,7 +72,6 @@ public class NotificationTestHelper { public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser()); private static final String GROUP_KEY = "gruKey"; - private static final String APP_NAME = "appName"; private final Context mContext; private int mId; @@ -307,6 +303,9 @@ public class NotificationTestHelper { null /* root */, false /* attachToRoot */); ExpandableNotificationRow row = mRow; + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setAboveShelfChangedListener(aboveShelf -> {}); final NotificationChannel channel = new NotificationChannel( @@ -330,23 +329,6 @@ public class NotificationTestHelper { entry.setRow(row); entry.createIcons(mContext, entry.getSbn()); row.setEntry(entry); - - NotificationContentInflater contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mock(NotificationRemoteInputManager.class)); - contentBinder.setInflateSynchronously(true); - - row.initialize( - APP_NAME, - entry.getKey(), - mock(ExpansionLogger.class), - mock(KeyguardBypassController.class), - mGroupManager, - mHeadsUpManager, - contentBinder, - mock(OnExpandClickListener.class)); - row.setAboveShelfChangedListener(aboveShelf -> { }); - row.setInflationFlags(extraInflationFlags); inflateAndWait(row); @@ -359,6 +341,7 @@ public class NotificationTestHelper { private static void inflateAndWait(ExpandableNotificationRow row) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); + row.getNotificationInflater().setInflateSynchronously(true); NotificationContentInflater.InflationCallback callback = new NotificationContentInflater.InflationCallback() { @Override 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 1c294531ea68..cd33cf922482 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 @@ -83,14 +83,12 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -236,7 +234,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mock(NotificationFilter.class), mNotifLog, mock(NotificationSectionsFeatureManager.class), - mock(PeopleNotificationIdentifier.class)), + mock(PeopleNotificationIdentifier.class), + mock(HighPriorityProvider.class)), mEnvironment, mFeatureFlags ); @@ -244,14 +243,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.addNotificationEntryListener(mEntryListener); mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor); - NotificationRowContentBinder contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mRemoteInputManager); - NotificationRowBinderImpl notificationRowBinder = - new NotificationRowBinderImpl(mContext, - contentBinder, - true, /* allowLongPress */ + new NotificationRowBinderImpl(mContext, true, /* allowLongPress */ mock(KeyguardBypassController.class), mock(StatusBarStateController.class), mock(NotificationLogger.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java deleted file mode 100644 index a06d6c1e593b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java +++ /dev/null @@ -1,148 +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.systemui.statusbar.notification.collection; - -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_MIN; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.RankingBuilder; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class GroupEntryTest extends SysuiTestCase { - @Test - public void testIsHighPriority_addChild() { - // GIVEN a GroupEntry with a lowPrioritySummary and no children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPrioritySummary = createNotifEntry(false); - setSummary(parentEntry, lowPrioritySummary); - assertFalse(parentEntry.isHighPriority()); - - // WHEN we add a high priority child and invalidate derived members - addChild(parentEntry, createNotifEntry(true)); - parentEntry.invalidateDerivedMembers(); - - // THEN the GroupEntry's priority is updated to high even though the summary is still low - // priority - assertTrue(parentEntry.isHighPriority()); - assertFalse(lowPrioritySummary.isHighPriority()); - } - - @Test - public void testIsHighPriority_clearChildren() { - // GIVEN a GroupEntry with a lowPrioritySummary and high priority children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(true)); - addChild(parentEntry, createNotifEntry(true)); - addChild(parentEntry, createNotifEntry(true)); - assertTrue(parentEntry.isHighPriority()); - - // WHEN we clear the children and invalidate derived members - parentEntry.clearChildren(); - parentEntry.invalidateDerivedMembers(); - - // THEN the parentEntry isn't high priority anymore - assertFalse(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_summaryUpdated() { - // GIVEN a GroupEntry with a lowPrioritySummary and no children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPrioritySummary = createNotifEntry(false); - setSummary(parentEntry, lowPrioritySummary); - assertFalse(parentEntry.isHighPriority()); - - // WHEN the summary changes to high priority and invalidates its derived members - lowPrioritySummary.setRanking( - new RankingBuilder() - .setKey(lowPrioritySummary.getKey()) - .setImportance(IMPORTANCE_HIGH) - .build()); - lowPrioritySummary.invalidateDerivedMembers(); - assertTrue(lowPrioritySummary.isHighPriority()); - - // THEN the GroupEntry's priority is updated to high - assertTrue(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_checkChildrenToCalculatePriority() { - // GIVEN: - // GroupEntry = parentEntry, summary = lowPrioritySummary - // NotificationEntry = lowPriorityChild - // NotificationEntry = highPriorityChild - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(true)); - - // THEN the GroupEntry parentEntry is high priority since it has a high priority child - assertTrue(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_childEntryRankingUpdated() { - // GIVEN: - // GroupEntry = parentEntry, summary = lowPrioritySummary - // NotificationEntry = lowPriorityChild - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPriorityChild = createNotifEntry(false); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, lowPriorityChild); - - // WHEN the child entry ranking changes to high priority and invalidates its derived members - lowPriorityChild.setRanking( - new RankingBuilder() - .setKey(lowPriorityChild.getKey()) - .setImportance(IMPORTANCE_HIGH) - .build()); - lowPriorityChild.invalidateDerivedMembers(); - - // THEN the parent entry's high priority value is updated - but not the parent's summary - assertTrue(parentEntry.isHighPriority()); - assertFalse(parentEntry.getSummary().isHighPriority()); - } - - private NotificationEntry createNotifEntry(boolean highPriority) { - return new NotificationEntryBuilder() - .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN) - .build(); - } - - private void setSummary(GroupEntry parent, NotificationEntry summary) { - parent.setSummary(summary); - summary.setParent(parent); - } - - private void addChild(GroupEntry parent, NotificationEntry child) { - parent.addChild(child); - child.setParent(parent); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java new file mode 100644 index 000000000000..93909dc4d330 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -0,0 +1,225 @@ +/* + * 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; + +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_MIN; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; + +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 HighPriorityProviderTest extends SysuiTestCase { + @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; + private HighPriorityProvider mHighPriorityProvider; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mHighPriorityProvider = new HighPriorityProvider(mPeopleNotificationIdentifier); + } + + @Test + public void highImportance() { + // GIVEN notification has high importance + final NotificationEntry entry = new NotificationEntryBuilder() + .setImportance(IMPORTANCE_HIGH) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void peopleNotification() { + // GIVEN notification is low importance and is a people notification + final Notification notification = new Notification.Builder(mContext, "test") + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void messagingStyle() { + // GIVEN notification is low importance but has messaging style + final Notification notification = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void lowImportanceForeground() { + // GIVEN notification is low importance and is associated with a foreground service + final Notification notification = mock(Notification.class); + when(notification.isForegroundService()).thenReturn(true); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void minImportanceForeground() { + // GIVEN notification is low importance and is associated with a foreground service + final Notification notification = mock(Notification.class); + when(notification.isForegroundService()).thenReturn(true); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_MIN) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it does NOT have high priority + assertFalse(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void userChangeTrumpsHighPriorityCharacteristics() { + // GIVEN notification has high priority characteristics but the user changed the importance + // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN) + final Notification notification = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + final NotificationChannel channel = new NotificationChannel("a", "a", + IMPORTANCE_LOW); + channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setChannel(channel) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true); + + // THEN it does NOT have high priority + assertFalse(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void testIsHighPriority_summaryUpdated() { + // GIVEN a GroupEntry with a lowPrioritySummary and no children + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + final NotificationEntry lowPrioritySummary = createNotifEntry(false); + setSummary(parentEntry, lowPrioritySummary); + assertFalse(mHighPriorityProvider.isHighPriority(parentEntry)); + + // WHEN the summary changes to high priority + lowPrioritySummary.setRanking( + new RankingBuilder() + .setKey(lowPrioritySummary.getKey()) + .setImportance(IMPORTANCE_HIGH) + .build()); + assertTrue(mHighPriorityProvider.isHighPriority(lowPrioritySummary)); + + // THEN the GroupEntry's priority is updated to high + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + } + + @Test + public void testIsHighPriority_checkChildrenToCalculatePriority() { + // GIVEN: + // GroupEntry = parentEntry, summary = lowPrioritySummary + // NotificationEntry = lowPriorityChild + // NotificationEntry = highPriorityChild + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + setSummary(parentEntry, createNotifEntry(false)); + addChild(parentEntry, createNotifEntry(false)); + addChild(parentEntry, createNotifEntry(true)); + + // THEN the GroupEntry parentEntry is high priority since it has a high priority child + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + } + + @Test + public void testIsHighPriority_childEntryRankingUpdated() { + // GIVEN: + // GroupEntry = parentEntry, summary = lowPrioritySummary + // NotificationEntry = lowPriorityChild + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + final NotificationEntry lowPriorityChild = createNotifEntry(false); + setSummary(parentEntry, createNotifEntry(false)); + addChild(parentEntry, lowPriorityChild); + + // WHEN the child entry ranking changes to high priority + lowPriorityChild.setRanking( + new RankingBuilder() + .setKey(lowPriorityChild.getKey()) + .setImportance(IMPORTANCE_HIGH) + .build()); + + // THEN the parent entry's high priority value is updated - but not the parent's summary + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary())); + } + + private NotificationEntry createNotifEntry(boolean highPriority) { + return new NotificationEntryBuilder() + .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN) + .build(); + } + + private void setSummary(GroupEntry parent, NotificationEntry summary) { + parent.setSummary(summary); + summary.setParent(parent); + } + + private void addChild(GroupEntry parent, NotificationEntry child) { + parent.addChild(child); + child.setParent(parent); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 39ae68a40291..5b0b66849027 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -21,8 +21,6 @@ import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; @@ -92,44 +90,6 @@ public class NotificationEntryTest extends SysuiTestCase { } @Test - public void testIsHighPriority_notificationUpdates() { - // GIVEN a notification with high importance - final NotificationEntry entryHigh = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // WHEN we get the value for the high priority entry, we're caching the high priority value - assertTrue(entryHigh.isHighPriority()); - - // WHEN we change the ranking and derived members (high priority) are invalidated - entryHigh.setRanking( - new RankingBuilder() - .setKey(entryHigh.getKey()) - .setImportance(IMPORTANCE_MIN) - .build()); - entryHigh.invalidateDerivedMembers(); - - // THEN the priority is recalculated and is now low - assertFalse(entryHigh.isHighPriority()); - - // WHEN the sbn is updated to have messaging style (high priority characteristic) - // AND the entry invalidates its derived members - final Notification notification = - new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - final StatusBarNotification sbn = entryHigh.getSbn(); - entryHigh.setSbn(new StatusBarNotification( - sbn.getPackageName(), sbn.getPackageName(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0)); - entryHigh.invalidateDerivedMembers(); - - // THEN the priority is recalculated and is now high - assertTrue(entryHigh.isHighPriority()); - } - - @Test public void testIsExemptFromDndVisualSuppression_foreground() { mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index 10450fa82cde..e27319103525 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING @@ -62,7 +63,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { mock(NotificationFilter::class.java), mock(NotifLog::class.java), mock(NotificationSectionsFeatureManager::class.java), - personNotificationIdentifier + personNotificationIdentifier, + HighPriorityProvider(personNotificationIdentifier) ) } @@ -182,7 +184,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { filter: NotificationFilter, notifLog: NotifLog, sectionsFeatureManager: NotificationSectionsFeatureManager, - peopleNotificationIdentifier: PeopleNotificationIdentifier + peopleNotificationIdentifier: PeopleNotificationIdentifier, + highPriorityProvider: HighPriorityProvider ) : NotificationRankingManager( mediaManager, groupManager, @@ -190,7 +193,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { filter, notifLog, sectionsFeatureManager, - peopleNotificationIdentifier + peopleNotificationIdentifier, + highPriorityProvider ) { fun applyTestRankingMap(r: RankingMap) { rankingMap = r diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java index 979b8a906ef0..f921cf969e61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.collection.NotifListBuilderIm 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.NotifFilter; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; @@ -66,6 +67,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private NotifListBuilderImpl mNotifListBuilder; private NotificationEntry mEntry; @@ -78,7 +80,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { mKeyguardCoordinator = new KeyguardCoordinator( mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager, mBroadcastDispatcher, mStatusBarStateController, - mKeyguardUpdateMonitor); + mKeyguardUpdateMonitor, mHighPriorityProvider); mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java deleted file mode 100644 index 6fa1a89515c3..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java +++ /dev/null @@ -1,184 +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.systemui.statusbar.notification.collection.provider; - -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.app.NotificationManager.IMPORTANCE_MIN; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.Person; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class IsHighPriorityProviderTest extends SysuiTestCase { - private IsHighPriorityProvider mIsHighPriorityProvider; - - @Before - public void setup() { - mIsHighPriorityProvider = new IsHighPriorityProvider(); - } - - @Test - public void testCache() { - // GIVEN a notification with high importance - final NotificationEntry entryHigh = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // GIVEN notification with min importance - final NotificationEntry entryMin = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_MIN) - .build(); - - // WHEN we get the value for the high priority entry - assertTrue(mIsHighPriorityProvider.get(entryHigh)); - - // THEN the value is cached, so even when passed an entryMin, we still get high priority - assertTrue(mIsHighPriorityProvider.get(entryMin)); - - // UNTIL the provider is invalidated - mIsHighPriorityProvider.invalidate(); - - // THEN the priority is recalculated - assertFalse(mIsHighPriorityProvider.get(entryMin)); - } - - @Test - public void highImportance() { - // GIVEN notification has high importance - final NotificationEntry entry = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void peopleNotification() { - // GIVEN notification is low importance but has a person associated with it - final Notification notification = new Notification.Builder(mContext, "test") - .addPerson( - new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build()) - .build(); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_LOW) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void messagingStyle() { - // GIVEN notification is low importance but has messaging style - final Notification notification = new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void lowImportanceForeground() { - // GIVEN notification is low importance and is associated with a foreground service - final Notification notification = mock(Notification.class); - when(notification.isForegroundService()).thenReturn(true); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_LOW) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void minImportanceForeground() { - // GIVEN notification is low importance and is associated with a foreground service - final Notification notification = mock(Notification.class); - when(notification.isForegroundService()).thenReturn(true); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_MIN) - .build(); - - // THEN it does NOT have high priority - assertFalse(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void userChangeTrumpsHighPriorityCharacteristics() { - // GIVEN notification has high priority characteristics but the user changed the importance - // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN) - final Notification notification = new Notification.Builder(mContext, "test") - .addPerson( - new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build()) - .setStyle(new Notification.MessagingStyle("")) - .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) - .build(); - - final NotificationChannel channel = new NotificationChannel("a", "a", - IMPORTANCE_LOW); - channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setChannel(channel) - .build(); - - // THEN it does NOT have high priority - assertFalse(mIsHighPriorityProvider.get(entry)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java deleted file mode 100644 index d7214f3b9228..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java +++ /dev/null @@ -1,112 +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.row; - -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.testing.AndroidTestingRunner; -import android.widget.RemoteViews; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; - -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; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class NotifRemoteViewCacheImplTest extends SysuiTestCase { - - private NotifRemoteViewCacheImpl mNotifRemoteViewCache; - private NotificationEntry mEntry; - private NotificationEntryListener mEntryListener; - @Mock private RemoteViews mRemoteViews; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mEntry = new NotificationEntryBuilder().build(); - - NotificationEntryManager entryManager = mock(NotificationEntryManager.class); - mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager); - ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = - ArgumentCaptor.forClass(NotificationEntryListener.class); - verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); - mEntryListener = entryListenerCaptor.getValue(); - } - - @Test - public void testPutCachedView() { - // GIVEN an initialized cache for an entry. - mEntryListener.onPendingEntryAdded(mEntry); - - // WHEN a notification's cached remote views is put in. - mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); - - // THEN the remote view is cached. - assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); - assertEquals( - "Cached remote view is not the one we put in.", - mRemoteViews, - mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); - } - - @Test - public void testRemoveCachedView() { - // GIVEN a cache with a cached view. - mEntryListener.onPendingEntryAdded(mEntry); - mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); - - // WHEN we remove the cached view. - mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED); - - // THEN the remote view is not cached. - assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); - } - - @Test - public void testClearCache() { - // GIVEN a non-empty cache. - mEntryListener.onPendingEntryAdded(mEntry); - mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); - mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews); - - // WHEN we clear the cache. - mNotifRemoteViewCache.clearCache(mEntry); - - // THEN the cache is empty. - assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); - assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index cb9da6a40cb9..f916fe5ad7fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -22,16 +22,11 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.app.Notification; import android.content.Context; @@ -40,17 +35,16 @@ import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import android.util.ArrayMap; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; -import android.widget.TextView; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.InflationTask; -import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; @@ -63,8 +57,6 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import java.util.HashMap; import java.util.concurrent.CountDownLatch; @@ -81,11 +73,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { private Notification.Builder mBuilder; private ExpandableNotificationRow mRow; - @Mock private NotifRemoteViewCache mCache; - @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); mBuilder = new Notification.Builder(mContext).setSmallIcon( R.drawable.ic_person) .setContentTitle("Title") @@ -94,9 +83,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow( mBuilder.build()); mRow = spy(row); - mNotificationInflater = new NotificationContentInflater( - mCache, - mock(NotificationRemoteInputManager.class)); + mNotificationInflater = new NotificationContentInflater(); } @Test @@ -187,9 +174,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { result, FLAG_CONTENT_VIEW_EXPANDED, 0, - mock(NotifRemoteViewCache.class), - mRow.getEntry(), - mRow, + new ArrayMap() /* cachedContentViews */, mRow, true /* isNewView */, (v, p, r) -> true, new InflationCallback() { @Override @@ -259,71 +244,6 @@ public class NotificationContentInflaterTest extends SysuiTestCase { NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView)); } - @Test - public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception { - // GIVEN a cached view. - RemoteViews contractedRemoteView = mBuilder.createContentView(); - when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) - .thenReturn(true); - when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) - .thenReturn(contractedRemoteView); - - // GIVEN existing bound view with same layout id. - View view = contractedRemoteView.apply(mContext, null /* parent */); - mRow.getPrivateLayout().setContractedChild(view); - - // WHEN inflater inflates - inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); - - // THEN the view should be re-used - assertEquals("Binder inflated a new view even though the old one was cached and usable.", - view, mRow.getPrivateLayout().getContractedChild()); - } - - @Test - public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception { - // GIVEN a cached remote view. - RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView(); - when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) - .thenReturn(true); - when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) - .thenReturn(contractedRemoteView); - - // GIVEN existing bound view with different layout id. - View view = new TextView(mContext); - mRow.getPrivateLayout().setContractedChild(view); - - // WHEN inflater inflates - inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); - - // THEN the view should be a new view - assertNotEquals("Binder (somehow) used the same view when inflating.", - view, mRow.getPrivateLayout().getContractedChild()); - } - - @Test - public void testInflationCachesCreatedRemoteView() throws Exception { - // WHEN inflater inflates - inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); - - // THEN inflater informs cache of the new remote view - verify(mCache).putCachedView( - eq(mRow.getEntry()), - eq(FLAG_CONTENT_VIEW_CONTRACTED), - any()); - } - - @Test - public void testUnbindRemovesCachedRemoteView() { - // WHEN inflated unbinds content - mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP); - - // THEN inflated informs cache to remove remote view - verify(mCache).removeCachedView( - eq(mRow.getEntry()), - eq(FLAG_CONTENT_VIEW_HEADS_UP)); - } - private static void inflateAndWait(NotificationContentInflater inflater, @InflationFlag int contentToInflate, ExpandableNotificationRow row) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index ccc9496368e9..4e27770982e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -70,6 +70,7 @@ import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.StatusBar; @@ -113,6 +114,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private StatusBar mStatusBar; @Mock private AccessibilityManager mAccessibilityManager; + @Mock private HighPriorityProvider mHighPriorityProvider; @Before public void setUp() { @@ -128,7 +130,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager, - () -> mStatusBar, mHandler, mAccessibilityManager); + () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider); mGutsManager.setUpWithPresenter(mPresenter, mStackScroller, mCheckSaveListener, mOnSettingsClickListener); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); @@ -391,6 +393,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { .build(); when(row.getIsNonblockable()).thenReturn(false); + when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true); StatusBarNotification statusBarNotification = entry.getSbn(); mGutsManager.initializeNotificationInfo(row, notificationInfoView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index 003d80376c40..518b6703391e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -267,8 +267,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(notifRow.getVisibility()).thenReturn(View.VISIBLE); - when(notifRow.getEntry().isHighPriority()) - .thenReturn(children[i] == ChildType.HIPRI); when(notifRow.getEntry().getBucket()).thenReturn( children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT); when(notifRow.getParent()).thenReturn(mNssl); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 39f037cfa70d..ea8d4ee20aab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; @@ -165,7 +166,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mock(NotificationFilter.class), mock(NotifLog.class), mock(NotificationSectionsFeatureManager.class), - mock(PeopleNotificationIdentifier.class) + mock(PeopleNotificationIdentifier.class), + mock(HighPriorityProvider.class) ), mock(NotificationEntryManager.KeyguardEnvironment.class), mock(FeatureFlags.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 3da87ea2fb56..7e485f45e5e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -122,7 +122,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; @@ -248,7 +247,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private DismissCallbackRegistry mDismissCallbackRegistry; @Mock private ScreenPinningRequest mScreenPinningRequest; @Mock private NotificationEntryManager mEntryManager; - @Mock private NotificationContentInflater mNotificationContentInflater; @Mock private LockscreenLockIconController mLockscreenLockIconController; @Mock private StatusBarNotificationActivityStarter.Builder mStatusBarNotificationActivityStarterBuilder; @@ -358,7 +356,6 @@ public class StatusBarTest extends SysuiTestCase { mNotificationGutsManager, notificationLogger, mEntryManager, - mNotificationContentInflater, mNotificationInterruptionStateProvider, mNotificationViewHierarchyManager, mKeyguardViewMediator, diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml index 87a8c3f5c68a..e99c2c529bd2 100644 --- a/packages/Tethering/AndroidManifest.xml +++ b/packages/Tethering/AndroidManifest.xml @@ -33,6 +33,7 @@ <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> + <uses-permission android:name="android.permission.TETHER_PRIVILEGED" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index fe3f51700df9..5692a6fc5c80 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,9 +16,12 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -35,8 +38,10 @@ import android.net.util.PrefixUtils; import android.net.util.SharedLog; import android.os.Handler; import android.util.Log; +import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import com.android.internal.util.StateMachine; import java.util.HashMap; @@ -77,11 +82,25 @@ public class UpstreamNetworkMonitor { public static final int EVENT_ON_LINKPROPERTIES = 2; public static final int EVENT_ON_LOST = 3; public static final int NOTIFY_LOCAL_PREFIXES = 10; + // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default + // disabled. + @VisibleForTesting + public static final int TYPE_NONE = -1; private static final int CALLBACK_LISTEN_ALL = 1; private static final int CALLBACK_DEFAULT_INTERNET = 2; private static final int CALLBACK_MOBILE_REQUEST = 3; + private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray(); + static { + sLegacyTypeToTransport.put(TYPE_MOBILE, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_DUN, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_WIFI, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_BLUETOOTH, NetworkCapabilities.TRANSPORT_BLUETOOTH); + sLegacyTypeToTransport.put(TYPE_ETHERNET, NetworkCapabilities.TRANSPORT_ETHERNET); + } + private final Context mContext; private final SharedLog mLog; private final StateMachine mTarget; @@ -202,7 +221,7 @@ public class UpstreamNetworkMonitor { final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI; final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder() - .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType)) + .setCapabilities(networkCapabilitiesForType(legacyType)) .build(); // The existing default network and DUN callbacks will be notified. @@ -354,16 +373,6 @@ public class UpstreamNetworkMonitor { notifyTarget(EVENT_ON_LINKPROPERTIES, network); } - private void handleSuspended(Network network) { - if (!network.equals(mTetheringUpstreamNetwork)) return; - mLog.log("SUSPENDED current upstream: " + network); - } - - private void handleResumed(Network network) { - if (!network.equals(mTetheringUpstreamNetwork)) return; - mLog.log("RESUMED current upstream: " + network); - } - private void handleLost(Network network) { // There are few TODOs within ConnectivityService's rematching code // pertaining to spurious onLost() notifications. @@ -453,20 +462,6 @@ public class UpstreamNetworkMonitor { } @Override - public void onNetworkSuspended(Network network) { - if (mCallbackType == CALLBACK_LISTEN_ALL) { - handleSuspended(network); - } - } - - @Override - public void onNetworkResumed(Network network) { - if (mCallbackType == CALLBACK_LISTEN_ALL) { - handleResumed(network); - } - } - - @Override public void onLost(Network network) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = null; @@ -510,7 +505,7 @@ public class UpstreamNetworkMonitor { for (int type : preferredTypes) { NetworkCapabilities nc; try { - nc = ConnectivityManager.networkCapabilitiesForType(type); + nc = networkCapabilitiesForType(type); } catch (IllegalArgumentException iae) { Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + type); continue; @@ -572,4 +567,28 @@ public class UpstreamNetworkMonitor { return null; } + + /** + * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance. + * This function is used for deprecated legacy type and be disabled by default. + */ + @VisibleForTesting + public static NetworkCapabilities networkCapabilitiesForType(int type) { + final NetworkCapabilities nc = new NetworkCapabilities(); + + // Map from type to transports. + final int notFound = -1; + final int transport = sLegacyTypeToTransport.get(type, notFound); + Preconditions.checkArgument(transport != notFound, "unknown legacy type: " + type); + nc.addTransportType(transport); + + if (type == TYPE_MOBILE_DUN) { + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN); + // DUN is restricted network, see NetworkCapabilities#FORCE_RESTRICTED_CAPABILITIES. + nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + } else { + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + return nc; + } } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index c90abbbedb5f..5ed75bf26f8b 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -18,13 +18,14 @@ package com.android.server.connectivity.tethering; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static com.android.server.connectivity.tethering.UpstreamNetworkMonitor.TYPE_NONE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -538,13 +539,15 @@ public class UpstreamNetworkMonitorTest { mUNM.selectPreferredUpstreamType(preferredTypes)); verify(mEntitleMgr, times(1)).maybeRunProvisioning(); } + private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) { if (legacyType == TYPE_NONE) { assertTrue(ns == null); return; } - final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType); + final NetworkCapabilities nc = + UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType); assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities)); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 6a6e2b2f3467..58d3489a8cc8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -35,6 +35,7 @@ import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.AlertDialog; import android.app.PendingIntent; +import android.app.RemoteAction; import android.appwidget.AppWidgetManagerInternal; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; @@ -148,6 +149,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // their capabilities are ready. private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000; + static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction"; + static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction"; private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE = "registerUiTestAutomationService"; @@ -253,6 +256,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @VisibleForTesting + AccessibilityManagerService( + Context context, + PackageManager packageManager, + AccessibilitySecurityPolicy securityPolicy, + SystemActionPerformer systemActionPerformer, + AccessibilityWindowManager a11yWindowManager, + AccessibilityDisplayListener a11yDisplayListener) { + mContext = context; + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); + mMainHandler = new MainHandler(mContext.getMainLooper()); + mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); + mPackageManager = packageManager; + mSecurityPolicy = securityPolicy; + mSystemActionPerformer = systemActionPerformer; + mA11yWindowManager = a11yWindowManager; + mA11yDisplayListener = a11yDisplayListener; + init(); + } + /** * Creates a new instance. * @@ -260,21 +284,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ public AccessibilityManagerService(Context context) { mContext = context; - mPackageManager = mContext.getPackageManager(); - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); - mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); mMainHandler = new MainHandler(mContext.getMainLooper()); + mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); + mPackageManager = mContext.getPackageManager(); + mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService); mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); - mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager); - mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); + init(); + } + private void init() { + mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager); registerBroadcastReceivers(); new AccessibilityContentObserver(mMainHandler).register( - context.getContentResolver()); + mContext.getContentResolver()); } @Override @@ -623,6 +650,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub event.recycle(); } + /** + * This is the implementation of AccessibilityManager system API. + * System UI calls into this method through AccessibilityManager system API to register a + * system action. + */ + @Override + public void registerSystemAction(RemoteAction action, int actionId) { + mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY, + FUNCTION_REGISTER_SYSTEM_ACTION); + mSystemActionPerformer.registerSystemAction(actionId, action); + } + + /** + * This is the implementation of AccessibilityManager system API. + * System UI calls into this method through AccessibilityManager system API to unregister a + * system action. + */ + @Override + public void unregisterSystemAction(int actionId) { + mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY, + FUNCTION_UNREGISTER_SYSTEM_ACTION); + mSystemActionPerformer.unregisterSystemAction(actionId); + } + @Override public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) { synchronized (mLock) { diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index 17549268503e..11dcfefd7e3b 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -157,8 +157,13 @@ public class SystemActionPerformer { /** * This method is called to register a system action. If a system action is already registered * with the given id, the existing system action will be overwritten. + * + * This method is supposed to be package internal since this class is meant to be used by + * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public + * to be mocked. */ - void registerSystemAction(int id, RemoteAction action) { + @VisibleForTesting + public void registerSystemAction(int id, RemoteAction action) { synchronized (mSystemActionLock) { mRegisteredSystemActions.put(id, action); } @@ -170,8 +175,13 @@ public class SystemActionPerformer { /** * This method is called to unregister a system action previously registered through * registerSystemAction. + * + * This method is supposed to be package internal since this class is meant to be used by + * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public + * to be mocked. */ - void unregisterSystemAction(int id) { + @VisibleForTesting + public void unregisterSystemAction(int id) { synchronized (mSystemActionLock) { mRegisteredSystemActions.remove(id); } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 95cd8fc47944..f34b5e71ad7b 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -112,7 +112,6 @@ import com.android.server.autofill.ui.PendingUi; import com.android.server.inputmethod.InputMethodManagerInternal; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -311,20 +310,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull private final InputMethodManagerInternal mInputMethodManagerInternal; - @GuardedBy("mLock") - @Nullable - private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture; - - @GuardedBy("mLock") - @Nullable - private CompletableFuture<IInlineSuggestionsResponseCallback> - mInlineSuggestionsResponseCallbackFuture; - @Nullable private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback; - private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; - /** * Receiver of assist data from the app's {@link Activity}. */ @@ -336,7 +324,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + "mForAugmentedAutofillOnly: %s", mForAugmentedAutofillOnly); return; } - if (mCurrentViewId == null) { + // Keeps to prevent it is cleared on multiple threads. + final AutofillId currentViewId = mCurrentViewId; + if (currentViewId == null) { Slog.w(TAG, "No current view id - session might have finished"); return; } @@ -410,7 +400,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mContexts == null) { mContexts = new ArrayList<>(1); } - mContexts.add(new FillContext(requestId, structure, mCurrentViewId)); + mContexts.add(new FillContext(requestId, structure, currentViewId)); cancelCurrentRequestLocked(); @@ -422,7 +412,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ false); - final InlineSuggestionsRequest suggestionsRequest = getInlineSuggestionsRequest(); + final InlineSuggestionsRequest suggestionsRequest = + mInlineSuggestionsRequestCallback != null + ? mInlineSuggestionsRequestCallback.getRequest() : null; request = new FillRequest(requestId, contexts, mClientState, flags, suggestionsRequest); @@ -620,13 +612,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState, int newState, int flags) { if (isInlineSuggestionsEnabled()) { - mSuggestionsRequestFuture = new CompletableFuture<>(); - mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>(); - - if (mInlineSuggestionsRequestCallback == null) { - mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(this); - } - + mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(); mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback); } @@ -636,10 +622,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private static final class InlineSuggestionsRequestCallbackImpl extends IInlineSuggestionsRequestCallback.Stub { - private final WeakReference<Session> mSession; + private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; + + private final CompletableFuture<InlineSuggestionsRequest> mRequest; + private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback; - private InlineSuggestionsRequestCallbackImpl(Session session) { - mSession = new WeakReference<>(session); + private InlineSuggestionsRequestCallbackImpl() { + mRequest = new CompletableFuture<>(); + mResponseCallback = new CompletableFuture<>(); } @Override @@ -648,13 +638,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Log.d(TAG, "inline suggestions request unsupported, " + "falling back to regular autofill"); } - final Session session = mSession.get(); - if (session != null) { - synchronized (session.mLock) { - session.mSuggestionsRequestFuture.cancel(true); - session.mInlineSuggestionsResponseCallbackFuture.cancel(true); - } - } + mRequest.cancel(true); + mResponseCallback.cancel(true); } @Override @@ -663,13 +648,36 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) { Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); } - final Session session = mSession.get(); - if (session != null) { - synchronized (session.mLock) { - session.mSuggestionsRequestFuture.complete(request); - session.mInlineSuggestionsResponseCallbackFuture.complete(callback); - } + mRequest.complete(request); + mResponseCallback.complete(callback); + } + + @Nullable + private InlineSuggestionsRequest getRequest() { + try { + return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions request in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions request cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return null; + } + + @Nullable + private IInlineSuggestionsResponseCallback getResponseCallback() { + try { + return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions callback cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); } + return null; } } @@ -2678,22 +2686,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Returns whether we made a request to show inline suggestions. */ private boolean requestShowInlineSuggestions(FillResponse response) { - IInlineSuggestionsResponseCallback inlineContentCallback = null; - synchronized (mLock) { - if (mInlineSuggestionsResponseCallbackFuture != null) { - try { - inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get( - INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions callback cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - } - + final IInlineSuggestionsResponseCallback inlineContentCallback = + mInlineSuggestionsRequestCallback != null + ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; if (inlineContentCallback == null) { Log.w(TAG, "Session input method callback is not set yet"); return false; @@ -3015,10 +3010,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId); - final InlineSuggestionsRequest inlineSuggestionsRequest = getInlineSuggestionsRequest(); + final InlineSuggestionsRequest inlineSuggestionsRequest = + mInlineSuggestionsRequestCallback != null + ? mInlineSuggestionsRequestCallback.getRequest() : null; final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback = - getInlineSuggestionsResponseCallback(); - + mInlineSuggestionsRequestCallback != null + ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId, currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback); @@ -3028,40 +3025,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return mAugmentedAutofillDestroyer; } - @Nullable - private InlineSuggestionsRequest getInlineSuggestionsRequest() { - if (mSuggestionsRequestFuture != null) { - try { - return mSuggestionsRequestFuture.get( - INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions request in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions request cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - return null; - } - - @Nullable - private IInlineSuggestionsResponseCallback getInlineSuggestionsResponseCallback() { - if (mInlineSuggestionsResponseCallbackFuture != null) { - try { - return mInlineSuggestionsResponseCallbackFuture.get( - INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions callback cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - return null; - } - @GuardedBy("mLock") private void cancelAugmentedAutofillLocked() { final RemoteAugmentedAutofillService remoteService = mService diff --git a/services/core/Android.bp b/services/core/Android.bp index a1f57cb51188..b2fba730fac1 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -165,9 +165,3 @@ prebuilt_etc { name: "protolog.conf.json.gz", src: ":services.core.json.gz", } - -platform_compat_config { - name: "services-core-platform-compat-config", - src: ":services.core.unboosted", -} - diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java index bbcfdc63f3f1..32cdc41472c9 100644 --- a/services/core/java/com/android/server/GnssManagerService.java +++ b/services/core/java/com/android/server/GnssManagerService.java @@ -47,7 +47,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocationManagerServiceUtils.LinkedListener; import com.android.server.LocationManagerServiceUtils.LinkedListenerBase; -import com.android.server.location.AbstractLocationProvider; import com.android.server.location.CallerIdentity; import com.android.server.location.GnssBatchingProvider; import com.android.server.location.GnssCapabilitiesProvider; @@ -116,11 +115,9 @@ public class GnssManagerService { private final Handler mHandler; public GnssManagerService(LocationManagerService locationManagerService, - Context context, - AbstractLocationProvider.LocationProviderManager gnssProviderManager, - LocationUsageLogger locationUsageLogger) { - this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager, - FgThread.getHandler().getLooper()), locationUsageLogger); + Context context, LocationUsageLogger locationUsageLogger) { + this(locationManagerService, context, + new GnssLocationProvider(context, FgThread.getHandler()), locationUsageLogger); } // Can use this constructor to inject GnssLocationProvider for testing diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 0fc5340846f2..32128d5f26f8 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -23,8 +23,6 @@ import static android.location.LocationManager.NETWORK_PROVIDER; import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.os.PowerManager.locationPowerSaveModeToString; -import static com.android.internal.util.Preconditions.checkState; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -74,7 +72,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; -import android.provider.Settings; import android.stats.location.LocationStatsEnums; import android.text.TextUtils; import android.util.EventLog; @@ -92,6 +89,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.location.AbstractLocationProvider; +import com.android.server.location.AbstractLocationProvider.State; import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.CallerIdentity; import com.android.server.location.GeocoderProxy; @@ -105,6 +103,7 @@ import com.android.server.location.LocationRequestStatistics.PackageStatistics; import com.android.server.location.LocationSettingsStore; import com.android.server.location.LocationUsageLogger; import com.android.server.location.MockProvider; +import com.android.server.location.MockableLocationProvider; import com.android.server.location.PassiveProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -121,6 +120,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; /** @@ -196,6 +197,8 @@ public class LocationManagerService extends ILocationManager.Stub { private final LocationSettingsStore mSettingsStore; private final LocationUsageLogger mLocationUsageLogger; + private final PassiveLocationProviderManager mPassiveManager; + private AppOpsManager mAppOps; private PackageManager mPackageManager; private PowerManager mPowerManager; @@ -205,21 +208,17 @@ public class LocationManagerService extends ILocationManager.Stub { private GeofenceManager mGeofenceManager; private LocationFudger mLocationFudger; private GeocoderProxy mGeocodeProvider; - @Nullable - private GnssManagerService mGnssManagerService; - private PassiveProvider mPassiveProvider; // track passive provider for special cases + @Nullable private GnssManagerService mGnssManagerService; + @GuardedBy("mLock") private String mExtraLocationControllerPackage; - private boolean mExtraLocationControllerPackageEnabled; - - // list of currently active providers @GuardedBy("mLock") - private final ArrayList<LocationProviderManager> mProviders = new ArrayList<>(); + private boolean mExtraLocationControllerPackageEnabled; - // list of non-mock providers, so that when mock providers replace real providers, they can be - // later re-replaced - @GuardedBy("mLock") - private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>(); + // @GuardedBy("mLock") + // hold lock for write or to prevent write, no lock for read + private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = + new CopyOnWriteArrayList<>(); @GuardedBy("mLock") private final HashMap<Object, Receiver> mReceivers = new HashMap<>(); @@ -238,9 +237,9 @@ public class LocationManagerService extends ILocationManager.Stub { private final HashMap<String, Location> mLastLocationCoarseInterval = new HashMap<>(); - // current active user on the device - other users are denied location data - private int mCurrentUserId = UserHandle.USER_SYSTEM; - private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM}; + // current active user on the device + private int mCurrentUserId; + private int[] mCurrentUserProfiles; @GuardedBy("mLock") @PowerManager.LocationPowerSaveMode @@ -252,6 +251,17 @@ public class LocationManagerService extends ILocationManager.Stub { mSettingsStore = new LocationSettingsStore(mContext, mHandler); mLocationUsageLogger = new LocationUsageLogger(); + mCurrentUserId = UserHandle.USER_NULL; + mCurrentUserProfiles = new int[]{UserHandle.USER_NULL}; + + // set up passive provider - we do this early because it has no dependencies on system + // services or external code that isn't ready yet, and because this allows the variable to + // be final. other more complex providers are initialized later, when system services are + // ready + mPassiveManager = new PassiveLocationProviderManager(); + mProviderManagers.add(mPassiveManager); + mPassiveManager.setRealProvider(new PassiveProvider(mContext)); + // Let the package manager query which are the default location // providers as they get certain permissions granted by default. PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService( @@ -415,15 +425,15 @@ public class LocationManagerService extends ILocationManager.Stub { for (Receiver receiver : mReceivers.values()) { receiver.updateMonitoring(true); } - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onPermissionsChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @@ -442,16 +452,16 @@ public class LocationManagerService extends ILocationManager.Stub { mBatterySaverMode = newLocationMode; - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onScreenStateChangedLocked() { if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } } @@ -466,8 +476,8 @@ public class LocationManagerService extends ILocationManager.Stub { intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId)); mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - for (LocationProviderManager p : mProviders) { - p.onUseableChangedLocked(userId); + for (LocationProviderManager manager : mProviderManagers) { + manager.onUseableChangedLocked(userId); } } @@ -521,22 +531,22 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private void onBackgroundThrottleIntervalChangedLocked() { - for (LocationProviderManager provider : mProviders) { - applyRequirementsLocked(provider); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onBackgroundThrottleWhitelistChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("lock") private void onIgnoreSettingsWhitelistChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @@ -623,22 +633,11 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private void initializeProvidersLocked() { - // create a passive location provider, which is always enabled - LocationProviderManager passiveProviderManager = new LocationProviderManager( - PASSIVE_PROVIDER); - addProviderLocked(passiveProviderManager); - mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager); - passiveProviderManager.attachLocked(mPassiveProvider); - if (GnssManagerService.isGnssSupported()) { - // Create a gps location provider manager - LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER); - mRealProviders.add(gnssProviderManager); - addProviderLocked(gnssProviderManager); - - mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager, - mLocationUsageLogger); - gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider()); + mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger); + LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER); + mProviderManagers.add(gnssManager); + gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider()); } /* @@ -662,37 +661,31 @@ public class LocationManagerService extends ILocationManager.Stub { ensureFallbackFusedProviderPresentLocked(pkgs); - // bind to network provider - LocationProviderManager networkProviderManager = new LocationProviderManager( - NETWORK_PROVIDER); LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( mContext, - networkProviderManager, NETWORK_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableNetworkLocationOverlay, com.android.internal.R.string.config_networkLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (networkProvider != null) { - mRealProviders.add(networkProviderManager); - addProviderLocked(networkProviderManager); - networkProviderManager.attachLocked(networkProvider); + LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER); + mProviderManagers.add(networkManager); + networkManager.setRealProvider(networkProvider); } else { Slog.w(TAG, "no network location provider found"); } // bind to fused provider - LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER); LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind( mContext, - fusedProviderManager, FUSED_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableFusedLocationOverlay, com.android.internal.R.string.config_fusedLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (fusedProvider != null) { - mRealProviders.add(fusedProviderManager); - addProviderLocked(fusedProviderManager); - fusedProviderManager.attachLocked(fusedProvider); + LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER); + mProviderManagers.add(fusedManager); + fusedManager.setRealProvider(fusedProvider); } else { Slog.e(TAG, "no fused location provider found", new IllegalStateException("Location service needs a fused location provider")); @@ -754,10 +747,7 @@ public class LocationManagerService extends ILocationManager.Stub { Boolean.parseBoolean(fragments[7]) /* supportsBearing */, Integer.parseInt(fragments[8]) /* powerRequirement */, Integer.parseInt(fragments[9]) /* accuracy */); - LocationProviderManager testProviderManager = new LocationProviderManager(name); - addProviderLocked(testProviderManager); - testProviderManager.attachLocked( - new MockProvider(mContext, testProviderManager, properties)); + addTestProvider(name, properties, mContext.getOpPackageName()); } } @@ -771,231 +761,202 @@ public class LocationManagerService extends ILocationManager.Stub { Log.d(TAG, "foreground user is changing to " + userId); } - int oldUserId = userId; + int oldUserId = mCurrentUserId; mCurrentUserId = userId; onUserProfilesChangedLocked(); // let providers know the current user has changed - for (LocationProviderManager p : mProviders) { - p.onUseableChangedLocked(oldUserId); - p.onUseableChangedLocked(mCurrentUserId); + for (LocationProviderManager manager : mProviderManagers) { + // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility + mSettingsStore.setLocationProviderAllowed(manager.getName(), + manager.isUseable(mCurrentUserId), mCurrentUserId); + + manager.onUseableChangedLocked(oldUserId); + manager.onUseableChangedLocked(mCurrentUserId); } } /** * Location provider manager, manages a LocationProvider. */ - class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager { + class LocationProviderManager implements MockableLocationProvider.Listener { private final String mName; - // remember to clear binder identity before invoking any provider operation - @GuardedBy("mLock") - @Nullable - protected AbstractLocationProvider mProvider; + // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary + protected final MockableLocationProvider mProvider; @GuardedBy("mLock") - private SparseArray<Boolean> mUseable; // combined state for each user id - @GuardedBy("mLock") - private boolean mEnabled; // state of provider - - @GuardedBy("mLock") - @Nullable - private ProviderProperties mProperties; + private final SparseArray<Boolean> mUseable; // combined state for each user id private LocationProviderManager(String name) { mName = name; - - mProvider = null; mUseable = new SparseArray<>(1); - mEnabled = false; - mProperties = null; - - // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-" + mName, - mCurrentUserId); - } - - @GuardedBy("mLock") - public void attachLocked(AbstractLocationProvider provider) { - Objects.requireNonNull(provider); - checkState(mProvider == null); - if (D) { - Log.d(TAG, mName + " provider attached"); - } - - mProvider = provider; - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - onUseableChangedLocked(mCurrentUserId); + // initialize last since this lets our reference escape + mProvider = new MockableLocationProvider(mContext, mLock, this); } public String getName() { return mName; } - @GuardedBy("mLock") - public List<String> getPackagesLocked() { - if (mProvider == null) { - return Collections.emptyList(); - } else { - // safe to not clear binder context since this doesn't call into the real provider - return mProvider.getProviderPackages(); - } + public boolean hasProvider() { + return mProvider.getProvider() != null; } - public boolean isMock() { - return false; + public void setRealProvider(AbstractLocationProvider provider) { + mProvider.setRealProvider(provider); } - @GuardedBy("mLock") - public boolean isPassiveLocked() { - return mProvider == mPassiveProvider; + public void setMockProvider(@Nullable MockProvider provider) { + mProvider.setMockProvider(provider); + } + + public Set<String> getPackages() { + return mProvider.getState().providerPackageNames; } - @GuardedBy("mLock") @Nullable - public ProviderProperties getPropertiesLocked() { - return mProperties; + public ProviderProperties getProperties() { + return mProvider.getState().properties; } - public void setRequest(ProviderRequest request, WorkSource workSource) { - // move calls going to providers onto a different thread to avoid deadlock - mHandler.post(() -> { - synchronized (mLock) { - if (mProvider != null) { - mProvider.onSetRequest(request, workSource); - } + public void setMockProviderEnabled(boolean enabled) { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); } - }); + + mProvider.setMockProviderEnabled(enabled); + } } - public void sendExtraCommand(String command, Bundle extras) { - int uid = Binder.getCallingUid(); - int pid = Binder.getCallingPid(); + public void setMockProviderLocation(Location location) { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); + } - // move calls going to providers onto a different thread to avoid deadlock - mHandler.post(() -> { - synchronized (mLock) { - if (mProvider != null) { - mProvider.onSendExtraCommand(uid, pid, command, extras); - } + String locationProvider = location.getProvider(); + if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) { + // The location has an explicit provider that is different from the mock + // provider name. The caller may be trying to fool us via b/33091107. + EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), + mName + "!=" + locationProvider); } - }); - } - @GuardedBy("mLock") - public void dumpLocked(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { - pw.print(mName + " provider"); - if (isMock()) { - pw.print(" [mock]"); + mProvider.setMockProviderLocation(location); } - pw.println(":"); + } - pw.increaseIndent(); + public List<LocationRequest> getMockProviderRequests() { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); + } - pw.println("useable=" + isUseableLocked(mCurrentUserId)); - if (!isUseableLocked(mCurrentUserId)) { - pw.println("attached=" + (mProvider != null)); - pw.println("enabled=" + mEnabled); + return mProvider.getCurrentRequest().locationRequests; } + } - pw.println("properties=" + mProperties); + public void setRequest(ProviderRequest request) { + mProvider.setRequest(request); + } - if (mProvider != null) { - // in order to be consistent with other provider APIs, this should be run on the - // location thread... but this likely isn't worth it just for dumping info. - long identity = Binder.clearCallingIdentity(); - try { - mProvider.dump(fd, pw, args); - } finally { - Binder.restoreCallingIdentity(identity); + public void sendExtraCommand(int uid, int pid, String command, Bundle extras) { + mProvider.sendExtraCommand(uid, pid, command, extras); + } + + public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { + synchronized (mLock) { + pw.print(mName + " provider"); + if (mProvider.isMock()) { + pw.print(" [mock]"); + } + pw.println(":"); + + pw.increaseIndent(); + + pw.println("useable=" + isUseable(mCurrentUserId)); + if (!isUseable(mCurrentUserId)) { + pw.println("enabled=" + mProvider.getState().enabled); } + + pw.println("properties=" + mProvider.getState().properties); } + mProvider.dump(fd, pw, args); + pw.decreaseIndent(); } + @GuardedBy("mLock") @Override public void onReportLocation(Location location) { - // likelihood of a 0,0 bug is far greater than this being a valid location - if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) { - Slog.w(TAG, "blocking 0,0 location from " + mName + " provider"); - return; + // don't validate mock locations + if (!location.isFromMockProvider()) { + if (location.getLatitude() == 0 && location.getLongitude() == 0) { + Slog.w(TAG, "blocking 0,0 location from " + mName + " provider"); + return; + } } - synchronized (mLock) { - handleLocationChangedLocked(location, this); - } + handleLocationChangedLocked(location, this); } + @GuardedBy("mLock") @Override public void onReportLocation(List<Location> locations) { if (mGnssManagerService == null) { return; } - synchronized (mLock) { - LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER); - if (gpsProvider == null || !gpsProvider.isUseableLocked()) { - Slog.w(TAG, "reportLocationBatch() called without user permission"); - return; - } - mGnssManagerService.onReportLocation(locations); + if (!GPS_PROVIDER.equals(mName) || !isUseable()) { + Slog.w(TAG, "reportLocationBatch() called without user permission"); + return; } - } - @Override - public void onSetEnabled(boolean enabled) { - synchronized (mLock) { - if (enabled == mEnabled) { - return; - } - - if (D) { - Log.d(TAG, mName + " provider enabled is now " + mEnabled); - } - - mEnabled = enabled; - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - onUseableChangedLocked(mCurrentUserId); - } + mGnssManagerService.onReportLocation(locations); } + @GuardedBy("mLock") @Override - public void onSetProperties(ProviderProperties properties) { - synchronized (mLock) { - mProperties = properties; + public void onStateChanged(State oldState, State newState) { + if (oldState.enabled != newState.enabled) { + // it would be more correct to call this for all users, but we know this can + // only affect the current user since providers are disabled for non-current + // users + onUseableChangedLocked(mCurrentUserId); } } @GuardedBy("mLock") - public boolean isUseableLocked() { - return isUseableLocked(mCurrentUserId); + public boolean isUseable() { + return isUseable(mCurrentUserId); } @GuardedBy("mLock") - public boolean isUseableLocked(int userId) { - return mUseable.get(userId, Boolean.FALSE); + public boolean isUseable(int userId) { + synchronized (mLock) { + return mUseable.get(userId, Boolean.FALSE); + } } @GuardedBy("mLock") public void onUseableChangedLocked(int userId) { + if (userId == UserHandle.USER_NULL) { + // only used during initialization - we don't care about the null user + return; + } + // if any property that contributes to "useability" here changes state, it MUST result // in a direct or indrect call to onUseableChangedLocked. this allows the provider to // guarantee that it will always eventually reach the correct state. - boolean useable = mProvider != null && mProviders.contains(this) - && isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId) - && mEnabled; + boolean useable = isCurrentProfileLocked(userId) + && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled; - if (useable == isUseableLocked(userId)) { + if (useable == isUseable(userId)) { return; } mUseable.put(userId, useable); @@ -1007,11 +968,7 @@ public class LocationManagerService extends ILocationManager.Stub { // fused and passive provider never get public updates for legacy reasons if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) { // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - (useable ? "+" : "-") + mName, - userId); + mSettingsStore.setLocationProviderAllowed(mName, useable, userId); Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION); intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName); @@ -1031,53 +988,38 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private class MockLocationProvider extends LocationProviderManager { + class PassiveLocationProviderManager extends LocationProviderManager { - private ProviderRequest mCurrentRequest; - - private MockLocationProvider(String name) { - super(name); + private PassiveLocationProviderManager() { + super(PASSIVE_PROVIDER); } @Override - public void attachLocked(AbstractLocationProvider provider) { - checkState(provider instanceof MockProvider); - super.attachLocked(provider); - } - - public boolean isMock() { - return true; + public void setRealProvider(AbstractLocationProvider provider) { + Preconditions.checkArgument(provider instanceof PassiveProvider); + super.setRealProvider(provider); } - @GuardedBy("mLock") - public void setEnabledLocked(boolean enabled) { - if (mProvider != null) { - long identity = Binder.clearCallingIdentity(); - try { - ((MockProvider) mProvider).setEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); - } + @Override + public void setMockProvider(@Nullable MockProvider provider) { + if (provider != null) { + throw new IllegalArgumentException("Cannot mock the passive provider"); } } - @GuardedBy("mLock") - public void setLocationLocked(Location location) { - if (mProvider != null) { + public void updateLocation(Location location) { + synchronized (mLock) { + PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider(); + Preconditions.checkState(passiveProvider != null); + long identity = Binder.clearCallingIdentity(); try { - ((MockProvider) mProvider).setLocation(location); + passiveProvider.updateLocation(location); } finally { Binder.restoreCallingIdentity(identity); } } } - - @Override - public void setRequest(ProviderRequest request, WorkSource workSource) { - super.setRequest(request, workSource); - mCurrentRequest = request; - } } /** @@ -1181,17 +1123,17 @@ public class LocationManagerService extends ILocationManager.Stub { // See if receiver has any enabled update records. Also note if any update records // are high power (has a high power provider with an interval under a threshold). for (UpdateRecord updateRecord : mUpdateRecords.values()) { - LocationProviderManager provider = getLocationProviderLocked( + LocationProviderManager manager = getLocationProviderManager( updateRecord.mProvider); - if (provider == null) { + if (manager == null) { continue; } - if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) { + if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) { continue; } requestingLocation = true; - ProviderProperties properties = provider.getPropertiesLocked(); + ProviderProperties properties = manager.getProperties(); if (properties != null && properties.mPowerRequirement == Criteria.POWER_HIGH && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { @@ -1432,7 +1374,7 @@ public class LocationManagerService extends ILocationManager.Stub { String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback( + return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback( callback, packageName, featureId, listenerIdentifier); } @@ -1443,7 +1385,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) { - return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos, + return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos, wakeOnFifoFull, packageName); } @@ -1454,35 +1396,14 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean stopGnssBatch() { - return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch(); + return mGnssManagerService != null && mGnssManagerService.stopGnssBatch(); } - @GuardedBy("mLock") - private void addProviderLocked(LocationProviderManager provider) { - Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null); - - mProviders.add(provider); - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - provider.onUseableChangedLocked(mCurrentUserId); - } - - @GuardedBy("mLock") - private void removeProviderLocked(LocationProviderManager provider) { - if (mProviders.remove(provider)) { - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - provider.onUseableChangedLocked(mCurrentUserId); - } - } - - @GuardedBy("mLock") @Nullable - private LocationProviderManager getLocationProviderLocked(String providerName) { - for (LocationProviderManager provider : mProviders) { - if (providerName.equals(provider.getName())) { - return provider; + private LocationProviderManager getLocationProviderManager(String providerName) { + for (LocationProviderManager manager : mProviderManagers) { + if (providerName.equals(manager.getName())) { + return manager; } } @@ -1531,12 +1452,12 @@ public class LocationManagerService extends ILocationManager.Stub { // network and fused providers are ok with COARSE or FINE return RESOLUTION_LEVEL_COARSE; } else { - for (LocationProviderManager lp : mProviders) { + for (LocationProviderManager lp : mProviderManagers) { if (!lp.getName().equals(provider)) { continue; } - ProviderProperties properties = lp.getPropertiesLocked(); + ProviderProperties properties = lp.getProperties(); if (properties != null) { if (properties.mRequiresSatellite) { // provider requiring satellites require FINE permission @@ -1587,11 +1508,9 @@ public class LocationManagerService extends ILocationManager.Stub { case RESOLUTION_LEVEL_COARSE: return AppOpsManager.OPSTR_COARSE_LOCATION; case RESOLUTION_LEVEL_FINE: - return AppOpsManager.OPSTR_FINE_LOCATION; + // fall through case RESOLUTION_LEVEL_NONE: - // The client is not allowed to get any location, so both FINE and COARSE ops will - // be denied. Pick the most restrictive one to be safe. - return AppOpsManager.OPSTR_FINE_LOCATION; + // fall through default: // Use the most restrictive ops if not sure. return AppOpsManager.OPSTR_FINE_LOCATION; @@ -1629,17 +1548,14 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public List<String> getAllProviders() { - synchronized (mLock) { - ArrayList<String> providers = new ArrayList<>(mProviders.size()); - for (LocationProviderManager provider : mProviders) { - String name = provider.getName(); - if (FUSED_PROVIDER.equals(name)) { - continue; - } - providers.add(name); + ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + if (FUSED_PROVIDER.equals(manager.getName())) { + continue; } - return providers; + providers.add(manager.getName()); } + return providers; } /** @@ -1651,21 +1567,21 @@ public class LocationManagerService extends ILocationManager.Stub { public List<String> getProviders(Criteria criteria, boolean enabledOnly) { int allowedResolutionLevel = getCallerAllowedResolutionLevel(); synchronized (mLock) { - ArrayList<String> providers = new ArrayList<>(mProviders.size()); - for (LocationProviderManager provider : mProviders) { - String name = provider.getName(); + ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + String name = manager.getName(); if (FUSED_PROVIDER.equals(name)) { continue; } if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) { continue; } - if (enabledOnly && !provider.isUseableLocked()) { + if (enabledOnly && !manager.isUseable()) { continue; } if (criteria != null && !android.location.LocationProvider.propertiesMeetCriteria( - name, provider.getPropertiesLocked(), criteria)) { + name, manager.getProperties(), criteria)) { continue; } providers.add(name); @@ -1702,12 +1618,12 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void updateProviderUseableLocked(LocationProviderManager provider) { - boolean useable = provider.isUseableLocked(); + private void updateProviderUseableLocked(LocationProviderManager manager) { + boolean useable = manager.isUseable(); ArrayList<Receiver> deadReceivers = null; - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); if (records != null) { for (UpdateRecord record : records) { if (!isCurrentProfileLocked( @@ -1721,7 +1637,7 @@ public class LocationManagerService extends ILocationManager.Stub { } // Sends a notification message to the receiver - if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) { + if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) { if (deadReceivers == null) { deadReceivers = new ArrayList<>(); } @@ -1736,26 +1652,25 @@ public class LocationManagerService extends ILocationManager.Stub { } } - applyRequirementsLocked(provider); + applyRequirementsLocked(manager); } @GuardedBy("mLock") private void applyRequirementsLocked(String providerName) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider != null) { - applyRequirementsLocked(provider); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") - private void applyRequirementsLocked(LocationProviderManager provider) { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); - WorkSource worksource = new WorkSource(); - ProviderRequest providerRequest = new ProviderRequest(); + private void applyRequirementsLocked(LocationProviderManager manager) { + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); + ProviderRequest.Builder providerRequest = new ProviderRequest.Builder(); // if provider is not active, it should not respond to requests - if (mProviders.contains(provider) && records != null && !records.isEmpty()) { + if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) { long backgroundThrottleInterval; long identity = Binder.clearCallingIdentity(); @@ -1765,6 +1680,8 @@ public class LocationManagerService extends ILocationManager.Stub { Binder.restoreCallingIdentity(identity); } + ArrayList<LocationRequest> requests = new ArrayList<>(records.size()); + final boolean isForegroundOnlyMode = mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY; final boolean shouldThrottleRequests = @@ -1772,7 +1689,7 @@ public class LocationManagerService extends ILocationManager.Stub { == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF && !mPowerManager.isInteractive(); // initialize the low power mode to true and set to false if any of the records requires - providerRequest.lowPowerMode = true; + providerRequest.setLowPowerMode(true); for (UpdateRecord record : records) { if (!isCurrentProfileLocked( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { @@ -1787,10 +1704,10 @@ public class LocationManagerService extends ILocationManager.Stub { } final boolean isBatterySaverDisablingLocation = shouldThrottleRequests || (isForegroundOnlyMode && !record.mIsForegroundUid); - if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) { + if (!manager.isUseable() || isBatterySaverDisablingLocation) { if (isSettingsExemptLocked(record)) { - providerRequest.locationSettingsIgnored = true; - providerRequest.lowPowerMode = false; + providerRequest.setLocationSettingsIgnored(true); + providerRequest.setLowPowerMode(false); } else { continue; } @@ -1801,7 +1718,7 @@ public class LocationManagerService extends ILocationManager.Stub { // if we're forcing location, don't apply any throttling - if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked( + if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked( record.mReceiver.mCallerIdentity)) { if (!record.mIsForegroundUid) { interval = Math.max(interval, backgroundThrottleInterval); @@ -1813,23 +1730,25 @@ public class LocationManagerService extends ILocationManager.Stub { } record.mRequest = locationRequest; - providerRequest.locationRequests.add(locationRequest); + requests.add(locationRequest); if (!locationRequest.isLowPowerMode()) { - providerRequest.lowPowerMode = false; + providerRequest.setLowPowerMode(false); } - if (interval < providerRequest.interval) { - providerRequest.reportLocation = true; - providerRequest.interval = interval; + if (interval < providerRequest.getInterval()) { + providerRequest.setInterval(interval); } } - if (providerRequest.reportLocation) { + providerRequest.setLocationRequests(requests); + + if (providerRequest.getInterval() < Long.MAX_VALUE) { // calculate who to blame for power // This is somewhat arbitrary. We pick a threshold interval // that is slightly higher that the minimum interval, and // spread the blame across all applications with a request // under that threshold. - long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; + // TODO: overflow + long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2; for (UpdateRecord record : records) { if (isCurrentProfileLocked( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { @@ -1837,18 +1756,18 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't assign battery blame for update records whose // client has no permission to receive location data. - if (!providerRequest.locationRequests.contains(locationRequest)) { + if (!providerRequest.getLocationRequests().contains(locationRequest)) { continue; } if (locationRequest.getInterval() <= thresholdInterval) { if (record.mReceiver.mWorkSource != null && isValidWorkSource(record.mReceiver.mWorkSource)) { - worksource.add(record.mReceiver.mWorkSource); + providerRequest.getWorkSource().add(record.mReceiver.mWorkSource); } else { // Assign blame to caller if there's no WorkSource associated with // the request or if it's invalid. - worksource.add( + providerRequest.getWorkSource().add( record.mReceiver.mCallerIdentity.mUid, record.mReceiver.mCallerIdentity.mPackageName); } @@ -1858,7 +1777,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } - provider.setRequest(providerRequest, worksource); + manager.setRequest(providerRequest.build()); } /** @@ -2198,8 +2117,8 @@ public class LocationManagerService extends ILocationManager.Stub { throw new IllegalArgumentException("provider name must not be null"); } - LocationProviderManager provider = getLocationProviderLocked(name); - if (provider == null) { + LocationProviderManager manager = getLocationProviderManager(name); + if (manager == null) { throw new IllegalArgumentException("provider doesn't exist: " + name); } @@ -2217,7 +2136,7 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) { + if (!manager.isUseable() && !isSettingsExemptLocked(record)) { // Notify the listener that updates are currently disabled - but only if the request // does not ignore location settings receiver.callProviderEnabledLocked(name, false); @@ -2320,8 +2239,8 @@ public class LocationManagerService extends ILocationManager.Stub { // or use the fused provider String name = request.getProvider(); if (name == null) name = LocationManager.FUSED_PROVIDER; - LocationProviderManager provider = getLocationProviderLocked(name); - if (provider == null) return null; + LocationProviderManager manager = getLocationProviderManager(name); + if (manager == null) return null; // only the current user or location providers may get location this way if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage( @@ -2329,7 +2248,7 @@ public class LocationManagerService extends ILocationManager.Stub { return null; } - if (!provider.isUseableLocked()) { + if (!manager.isUseable()) { return null; } @@ -2450,19 +2369,19 @@ public class LocationManagerService extends ILocationManager.Stub { "Access Fine Location permission not granted to inject Location"); synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(location.getProvider()); - if (provider == null || !provider.isUseableLocked()) { + LocationProviderManager manager = getLocationProviderManager(location.getProvider()); + if (manager == null || !manager.isUseable()) { return false; } // NOTE: If last location is already available, location is not injected. If // provider's normal source (like a GPS chipset) have already provided an output // there is no need to inject this location. - if (mLastLocation.get(provider.getName()) != null) { + if (mLastLocation.get(manager.getName()) != null) { return false; } - updateLastLocationLocked(location, provider.getName()); + updateLastLocationLocked(location, manager.getName()); return true; } } @@ -2511,7 +2430,7 @@ public class LocationManagerService extends ILocationManager.Stub { packageName, request, /* hasListener= */ false, - intent != null, + true, geofence, mActivityManager.getPackageImportance(packageName)); } @@ -2542,7 +2461,7 @@ public class LocationManagerService extends ILocationManager.Stub { packageName, /* LocationRequest= */ null, /* hasListener= */ false, - intent != null, + true, geofence, mActivityManager.getPackageImportance(packageName)); } @@ -2555,7 +2474,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName, String featureId) { - return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback( + return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback( listener, packageName, featureId); } @@ -2569,9 +2488,8 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false - : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId, - listenerIdentifier); + return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener( + listener, packageName, featureId, listenerIdentifier); } @Override @@ -2586,8 +2504,8 @@ public class LocationManagerService extends ILocationManager.Stub { public void injectGnssMeasurementCorrections( GnssMeasurementCorrections measurementCorrections, String packageName) { if (mGnssManagerService != null) { - mGnssManagerService.injectGnssMeasurementCorrections( - measurementCorrections, packageName); + mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections, + packageName); } } @@ -2602,9 +2520,8 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false - : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName, - featureId, listenerIdentifier); + return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener( + listener, packageName, featureId, listenerIdentifier); } @Override @@ -2634,9 +2551,10 @@ public class LocationManagerService extends ILocationManager.Stub { LocationStatsEnums.API_SEND_EXTRA_COMMAND, providerName); - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider != null) { - provider.sendExtraCommand(command, extras); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command, + extras); } mLocationUsageLogger.logLocationApiUsage( @@ -2650,43 +2568,37 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean sendNiResponse(int notifId, int userResponse) { - return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId, + return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId, userResponse); } @Override public ProviderProperties getProviderProperties(String providerName) { - synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider == null) { - return null; - } - return provider.getPropertiesLocked(); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager == null) { + return null; } + return manager.getProperties(); } @Override public boolean isProviderPackage(String packageName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, Manifest.permission.READ_DEVICE_CONFIG + " permission required"); - synchronized (mLock) { - for (LocationProviderManager provider : mProviders) { - if (provider.getPackagesLocked().contains(packageName)) { - return true; - } + for (LocationProviderManager manager : mProviderManagers) { + if (manager.getPackages().contains(packageName)) { + return true; } - return false; } + return false; } @Override public List<String> getProviderPackages(String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, Manifest.permission.READ_DEVICE_CONFIG + " permission required"); - synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - return provider == null ? Collections.emptyList() : provider.getPackagesLocked(); - } + LocationProviderManager manager = getLocationProviderManager(providerName); + return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages()); } @Override @@ -2753,8 +2665,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (FUSED_PROVIDER.equals(providerName)) return false; synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - return provider != null && provider.isUseableLocked(userId); + LocationProviderManager manager = getLocationProviderManager(providerName); + return manager != null && manager.isUseable(userId); } } @@ -2792,37 +2704,39 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void handleLocationChangedLocked(Location location, LocationProviderManager provider) { - if (!mProviders.contains(provider)) { + private void handleLocationChangedLocked(Location location, LocationProviderManager manager) { + if (!mProviderManagers.contains(manager)) { + Log.w(TAG, "received location from unknown provider: " + manager.getName()); return; } if (!location.isComplete()) { - Log.w(TAG, "Dropping incomplete location: " + location); + Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: " + + location); return; } - // only notify passive provider and update last location for locations that come from - // useable providers - if (provider.isUseableLocked()) { - if (!provider.isPassiveLocked()) { - mPassiveProvider.updateLocation(location); - } + // notify passive provider + if (manager != mPassiveManager) { + mPassiveManager.updateLocation(new Location(location)); } if (D) Log.d(TAG, "incoming location: " + location); long now = SystemClock.elapsedRealtime(); - if (provider.isUseableLocked()) { - updateLastLocationLocked(location, provider.getName()); + + + // only update last location for locations that come from useable providers + if (manager.isUseable()) { + updateLastLocationLocked(location, manager.getName()); } // Update last known coarse interval location if enough time has passed. Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get( - provider.getName()); + manager.getName()); if (lastLocationCoarseInterval == null) { lastLocationCoarseInterval = new Location(location); - if (provider.isUseableLocked()) { - mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); + if (manager.isUseable()) { + mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval); } } long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos() @@ -2837,7 +2751,7 @@ public class LocationManagerService extends ILocationManager.Stub { lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); // Skip if there are no UpdateRecords for this provider. - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); if (records == null || records.size() == 0) return; // Fetch coarse location @@ -2854,7 +2768,7 @@ public class LocationManagerService extends ILocationManager.Stub { Receiver receiver = r.mReceiver; boolean receiverDead = false; - if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) { + if (!manager.isUseable() && !isSettingsExemptLocked(r)) { continue; } @@ -2949,7 +2863,7 @@ public class LocationManagerService extends ILocationManager.Stub { for (UpdateRecord r : deadUpdateRecords) { r.disposeLocked(true); } - applyRequirementsLocked(provider); + applyRequirementsLocked(manager); } } @@ -3006,143 +2920,99 @@ public class LocationManagerService extends ILocationManager.Stub { // Mock Providers - private boolean canCallerAccessMockLocation(String opPackageName) { - return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), - opPackageName) == AppOpsManager.MODE_ALLOWED; - } - @Override - public void addTestProvider(String name, ProviderProperties properties, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void addTestProvider(String provider, ProviderProperties properties, + String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - if (PASSIVE_PROVIDER.equals(name)) { - throw new IllegalArgumentException("Cannot mock the passive location provider"); - } - synchronized (mLock) { - long identity = Binder.clearCallingIdentity(); - try { - LocationProviderManager oldProvider = getLocationProviderLocked(name); - if (oldProvider != null) { - removeProviderLocked(oldProvider); - } - - MockLocationProvider mockProviderManager = new MockLocationProvider(name); - addProviderLocked(mockProviderManager); - mockProviderManager.attachLocked( - new MockProvider(mContext, mockProviderManager, properties)); - } finally { - Binder.restoreCallingIdentity(identity); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + manager = new LocationProviderManager(provider); + mProviderManagers.add(manager); } + + manager.setMockProvider(new MockProvider(mContext, properties)); } } @Override - public void removeTestProvider(String name, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void removeTestProvider(String provider, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } synchronized (mLock) { - long identity = Binder.clearCallingIdentity(); - try { - LocationProviderManager testProvider = getLocationProviderLocked(name); - if (testProvider == null || !testProvider.isMock()) { - return; - } - - removeProviderLocked(testProvider); - - // reinstate real provider if available - LocationProviderManager realProvider = null; - for (LocationProviderManager provider : mRealProviders) { - if (name.equals(provider.getName())) { - realProvider = provider; - break; - } - } + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + return; + } - if (realProvider != null) { - addProviderLocked(realProvider); - } - } finally { - Binder.restoreCallingIdentity(identity); + manager.setMockProvider(null); + if (!manager.hasProvider()) { + mProviderManagers.remove(manager); + mLastLocation.remove(manager.getName()); + mLastLocationCoarseInterval.remove(manager.getName()); } } } @Override - public void setTestProviderLocation(String providerName, Location location, - String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void setTestProviderLocation(String provider, Location location, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - String locationProvider = location.getProvider(); - if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) { - // The location has an explicit provider that is different from the mock - // provider name. The caller may be trying to fool us via b/33091107. - EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), - providerName + "!=" + location.getProvider()); - } - - ((MockLocationProvider) testProvider).setLocationLocked(location); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + manager.setMockProviderLocation(location); } @Override - public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void setTestProviderEnabled(String provider, boolean enabled, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - ((MockLocationProvider) testProvider).setEnabledLocked(enabled); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + manager.setMockProviderEnabled(enabled); } @Override @NonNull - public List<LocationRequest> getTestProviderCurrentRequests(String providerName, - String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public List<LocationRequest> getTestProviderCurrentRequests(String provider, + String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return Collections.emptyList(); } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - MockLocationProvider provider = (MockLocationProvider) testProvider; - if (provider.mCurrentRequest == null) { - return Collections.emptyList(); - } - List<LocationRequest> requests = new ArrayList<>(); - for (LocationRequest request : provider.mCurrentRequest.locationRequests) { - requests.add(new LocationRequest(request)); - } - return requests; + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + return manager.getMockProviderRequests(); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); @@ -3226,25 +3096,27 @@ public class LocationManagerService extends ILocationManager.Stub { mLocationFudger.dump(fd, ipw, args); ipw.decreaseIndent(); } + } - ipw.println("Location Settings:"); - ipw.increaseIndent(); - mSettingsStore.dump(fd, ipw, args); - ipw.decreaseIndent(); + ipw.println("Location Settings:"); + ipw.increaseIndent(); + mSettingsStore.dump(fd, ipw, args); + ipw.decreaseIndent(); - ipw.println("Location Providers:"); - ipw.increaseIndent(); - for (LocationProviderManager provider : mProviders) { - provider.dumpLocked(fd, ipw, args); - } - ipw.decreaseIndent(); + ipw.println("Location Providers:"); + ipw.increaseIndent(); + for (LocationProviderManager manager : mProviderManagers) { + manager.dump(fd, ipw, args); } + ipw.decreaseIndent(); - if (mGnssManagerService != null) { - ipw.println("GNSS:"); - ipw.increaseIndent(); - mGnssManagerService.dump(fd, ipw, args); - ipw.decreaseIndent(); + synchronized (mLock) { + if (mGnssManagerService != null) { + ipw.println("GNSS:"); + ipw.increaseIndent(); + mGnssManagerService.dump(fd, ipw, args); + ipw.decreaseIndent(); + } } } } diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index deff440aa0a6..7b4fd37f01c9 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -199,13 +199,14 @@ public class PackageWatchdog { mSystemClock = clock; mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS; loadFromFile(); + sPackageWatchdog = this; } /** Creates or gets singleton instance of PackageWatchdog. */ public static PackageWatchdog getInstance(Context context) { synchronized (PackageWatchdog.class) { if (sPackageWatchdog == null) { - sPackageWatchdog = new PackageWatchdog(context); + new PackageWatchdog(context); } return sPackageWatchdog; } diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index fb1a962c0ef3..3dafc64391fd 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -18,8 +18,12 @@ package com.android.server; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.VersionedPackage; import android.os.Build; import android.os.Environment; import android.os.FileUtils; @@ -36,8 +40,12 @@ import android.util.Slog; import android.util.SparseArray; import android.util.StatsLog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.server.PackageWatchdog.FailureReasons; +import com.android.server.PackageWatchdog.PackageHealthObserver; +import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import com.android.server.am.SettingsToPropertiesMapper; import com.android.server.utils.FlagNamespaceUtils; @@ -79,19 +87,30 @@ public class RescueParty { @VisibleForTesting static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS; @VisibleForTesting - static final long PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS; - @VisibleForTesting static final String TAG = "RescueParty"; + private static final String NAME = "rescue-party-observer"; + + private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start"; private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; + private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT + | ApplicationInfo.FLAG_SYSTEM; + + /** Threshold for boot loops */ private static final Threshold sBoot = new BootThreshold(); /** Threshold for app crash loops */ private static SparseArray<Threshold> sApps = new SparseArray<>(); + /** Register the Rescue Party observer as a Package Watchdog health observer */ + public static void registerHealthObserver(Context context) { + PackageWatchdog.getInstance(context).registerHealthObserver( + RescuePartyObserver.getInstance(context)); + } + private static boolean isDisabled() { // Check if we're explicitly enabled for testing if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) { @@ -135,24 +154,6 @@ public class RescueParty { } /** - * Take note of a persistent app or apex module crash. If we notice too many of these - * events happening in rapid succession, we'll send out a rescue party. - */ - public static void noteAppCrash(Context context, int uid) { - if (isDisabled()) return; - Threshold t = sApps.get(uid); - if (t == null) { - t = new AppThreshold(uid); - sApps.put(uid, t); - } - if (t.incrementAndTest()) { - t.reset(); - incrementRescueLevel(t.uid); - executeRescueLevel(context); - } - } - - /** * Check if we're currently attempting to reboot for a factory reset. */ public static boolean isAttemptingFactoryReset() { @@ -171,11 +172,6 @@ public class RescueParty { @VisibleForTesting static void resetAllThresholds() { sBoot.reset(); - - for (int i = 0; i < sApps.size(); i++) { - Threshold appThreshold = sApps.get(sApps.keyAt(i)); - appThreshold.reset(); - } } @VisibleForTesting @@ -243,6 +239,28 @@ public class RescueParty { FlagNamespaceUtils.NAMESPACE_NO_PACKAGE); } + private static int mapRescueLevelToUserImpact(int rescueLevel) { + switch(rescueLevel) { + case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: + case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: + return PackageHealthObserverImpact.USER_IMPACT_LOW; + case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: + case LEVEL_FACTORY_RESET: + return PackageHealthObserverImpact.USER_IMPACT_HIGH; + default: + return PackageHealthObserverImpact.USER_IMPACT_NONE; + } + } + + private static int getPackageUid(Context context, String packageName) { + try { + return context.getPackageManager().getPackageUid(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + // Since UIDs are always >= 0, this value means the UID could not be determined. + return -1; + } + } + private static void resetAllSettings(Context context, int mode) throws Exception { // Try our best to reset all settings possible, and once finished // rethrow any exception that we encountered @@ -271,6 +289,89 @@ public class RescueParty { } /** + * Handle mitigation action for package failures. This observer will be register to Package + * Watchdog and will receive calls about package failures. This observer is persistent so it + * may choose to mitigate failures for packages it has not explicitly asked to observe. + */ + public static class RescuePartyObserver implements PackageHealthObserver { + + private Context mContext; + + @GuardedBy("RescuePartyObserver.class") + static RescuePartyObserver sRescuePartyObserver; + + private RescuePartyObserver(Context context) { + mContext = context; + } + + /** Creates or gets singleton instance of RescueParty. */ + public static RescuePartyObserver getInstance(Context context) { + synchronized (RescuePartyObserver.class) { + if (sRescuePartyObserver == null) { + sRescuePartyObserver = new RescuePartyObserver(context); + } + return sRescuePartyObserver; + } + } + + @Override + public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, + @FailureReasons int failureReason) { + if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH + || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { + int rescueLevel = MathUtils.constrain( + SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1, + LEVEL_NONE, LEVEL_FACTORY_RESET); + return mapRescueLevelToUserImpact(rescueLevel); + } else { + return PackageHealthObserverImpact.USER_IMPACT_NONE; + } + } + + @Override + public boolean execute(@Nullable VersionedPackage failedPackage, + @FailureReasons int failureReason) { + if (isDisabled()) { + return false; + } + if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH + || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { + int triggerUid = getPackageUid(mContext, failedPackage.getPackageName()); + incrementRescueLevel(triggerUid); + executeRescueLevel(mContext); + return true; + } else { + return false; + } + } + + @Override + public boolean isPersistent() { + return true; + } + + @Override + public boolean mayObservePackage(String packageName) { + PackageManager pm = mContext.getPackageManager(); + try { + // A package is a Mainline module if this is non-null + if (pm.getModuleInfo(packageName, 0) != null) { + return true; + } + ApplicationInfo info = pm.getApplicationInfo(packageName, 0); + return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + @Override + public String getName() { + return NAME; + } + } + + /** * Threshold that can be triggered if a number of events occur within a * window of time. */ @@ -349,27 +450,6 @@ public class RescueParty { } } - /** - * Specialization of {@link Threshold} for monitoring app crashes. It stores - * counters in memory. - */ - private static class AppThreshold extends Threshold { - private int count; - private long start; - - public AppThreshold(int uid) { - // We're interested in TRIGGER_COUNT events in any - // PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS second period; apps crash pretty quickly - // so we can keep a tight leash on them. - super(uid, TRIGGER_COUNT, PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS); - } - - @Override public int getCount() { return count; } - @Override public void setCount(int count) { this.count = count; } - @Override public long getStart() { return start; } - @Override public void setStart(long start) { this.start = start; } - } - private static int[] getAllUserIds() { int[] userIds = { UserHandle.USER_SYSTEM }; try { diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 5e48dcf91676..8071f52037e6 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -33,8 +33,6 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.ModuleInfo; -import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; @@ -56,7 +54,6 @@ import com.android.internal.app.ProcessMap; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.server.PackageWatchdog; -import com.android.server.RescueParty; import com.android.server.wm.WindowProcessController; import java.io.FileDescriptor; @@ -423,28 +420,6 @@ class AppErrors { } if (r != null) { - boolean isApexModule = false; - try { - for (String androidPackage : r.getPackageList()) { - ModuleInfo moduleInfo = mContext.getPackageManager().getModuleInfo( - androidPackage, /*flags=*/ 0); - if (moduleInfo != null) { - isApexModule = true; - break; - } - } - } catch (IllegalStateException | PackageManager.NameNotFoundException e) { - // Call to PackageManager#getModuleInfo() can result in NameNotFoundException or - // IllegalStateException. In case they are thrown, there isn't much we can do - // other than proceed with app crash handling. - } - - if (r.isPersistent() || isApexModule) { - // If a persistent app or apex module is stuck in a crash loop, the device isn't - // very usable, so we want to consider sending out a rescue party. - RescueParty.noteAppCrash(mContext, r.uid); - } - mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(), PackageWatchdog.FAILURE_REASON_APP_CRASH); } diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java index 31d4816e5d66..17a4b9c6c170 100644 --- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java +++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java @@ -39,6 +39,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -153,14 +154,19 @@ public class IntegrityFileManager { throws IOException, RuleParseException { synchronized (RULES_LOCK) { // Try to identify indexes from the index file. - List<RuleIndexRange> ruleReadingIndexes = - mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata); + List<RuleIndexRange> ruleReadingIndexes; + try { + ruleReadingIndexes = + mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata); + } catch (Exception e) { + Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e); + ruleReadingIndexes = Collections.emptyList(); + } - // Read the rules based on the index information. - // TODO(b/145493956): Provide the identified indexes to the rule reader. + // Read the rules based on the index information when available. try (FileInputStream inputStream = new FileInputStream(new File(mRulesDir, RULES_FILE))) { - List<Rule> rules = mRuleParser.parse(inputStream); + List<Rule> rules = mRuleParser.parse(inputStream, ruleReadingIndexes); return rules; } } diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java new file mode 100644 index 000000000000..e555e3e5746e --- /dev/null +++ b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java @@ -0,0 +1,69 @@ +/* + * 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.integrity.model; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An input stream that tracks the total number read bytes since construction and allows moving + * fast forward to a certain byte any time during the execution. + * + * This class is used for efficient reading of rules based on the rule indexing. + */ +public class BitTrackedInputStream extends BitInputStream { + + private static int sReadBitsCount; + + /** Constructor with byte array. */ + public BitTrackedInputStream(byte[] inputStream) { + super(inputStream); + sReadBitsCount = 0; + } + + /** Constructor with input stream. */ + public BitTrackedInputStream(InputStream inputStream) { + super(inputStream); + sReadBitsCount = 0; + } + + /** Obtains an integer value of the next {@code numOfBits}. */ + @Override + public int getNext(int numOfBits) throws IOException { + sReadBitsCount += numOfBits; + return super.getNext(numOfBits); + } + + /** Returns the current cursor position showing the number of bits that are read. */ + public int getReadBitsCount() { + return sReadBitsCount; + } + + /** + * Sets the cursor to the specified byte location. + * + * Note that the integer parameter specifies the location in bytes -- not bits. + */ + public void setCursorToByteLocation(int byteLocation) throws IOException { + int bitCountToRead = byteLocation * 8 - sReadBitsCount; + if (bitCountToRead < 0) { + throw new IllegalStateException("The byte position is already read."); + } + super.getNext(bitCountToRead); + sReadBitsCount = byteLocation * 8; + } +} diff --git a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java index 62815a9806ee..f575599e1c49 100644 --- a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java +++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.integrity.serializer; +package com.android.server.integrity.model; import java.io.IOException; import java.io.OutputStream; diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index cbb6e4e8e06f..e744326c49db 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -37,71 +37,103 @@ import android.content.integrity.CompoundFormula; import android.content.integrity.Formula; import android.content.integrity.Rule; -import com.android.server.integrity.model.BitInputStream; +import com.android.server.integrity.model.BitTrackedInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** A helper class to parse rules into the {@link Rule} model from Binary representation. */ public class RuleBinaryParser implements RuleParser { - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - @Override public List<Rule> parse(byte[] ruleBytes) throws RuleParseException { try { - BitInputStream bitInputStream = new BitInputStream(ruleBytes); - return parseRules(bitInputStream); + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(ruleBytes); + return parseRules(bitTrackedInputStream, /* indexRanges= */ Collections.emptyList()); } catch (Exception e) { throw new RuleParseException(e.getMessage(), e); } } @Override - public List<Rule> parse(InputStream inputStream) throws RuleParseException { + public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges) + throws RuleParseException { try { - BitInputStream bitInputStream = new BitInputStream(inputStream); - return parseRules(bitInputStream); + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(inputStream); + return parseRules(bitTrackedInputStream, indexRanges); } catch (Exception e) { throw new RuleParseException(e.getMessage(), e); } } - private List<Rule> parseRules(BitInputStream bitInputStream) throws IOException { - List<Rule> parsedRules = new ArrayList<>(); + private List<Rule> parseRules( + BitTrackedInputStream bitTrackedInputStream, + List<RuleIndexRange> indexRanges) + throws IOException { // Read the rule binary file format version. - bitInputStream.getNext(FORMAT_VERSION_BITS); + bitTrackedInputStream.getNext(FORMAT_VERSION_BITS); + + return indexRanges.isEmpty() + ? parseAllRules(bitTrackedInputStream) + : parseIndexedRules(bitTrackedInputStream, indexRanges); + } + + private List<Rule> parseAllRules(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + List<Rule> parsedRules = new ArrayList<>(); + + while (bitTrackedInputStream.hasNext()) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(bitTrackedInputStream)); + } + } + + return parsedRules; + } + + private List<Rule> parseIndexedRules( + BitTrackedInputStream bitTrackedInputStream, List<RuleIndexRange> indexRanges) + throws IOException { + List<Rule> parsedRules = new ArrayList<>(); + + for (RuleIndexRange range : indexRanges) { + // Skip the rules that are not in the range. + bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex()); - while (bitInputStream.hasNext()) { - if (bitInputStream.getNext(SIGNAL_BIT) == 1) { - parsedRules.add(parseRule(bitInputStream)); + // Read the rules until we reach the end index. + while (bitTrackedInputStream.hasNext() + && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(bitTrackedInputStream)); + } } } return parsedRules; } - private Rule parseRule(BitInputStream bitInputStream) throws IOException { - Formula formula = parseFormula(bitInputStream); - int effect = bitInputStream.getNext(EFFECT_BITS); + private Rule parseRule(BitTrackedInputStream bitTrackedInputStream) throws IOException { + Formula formula = parseFormula(bitTrackedInputStream); + int effect = bitTrackedInputStream.getNext(EFFECT_BITS); - if (bitInputStream.getNext(SIGNAL_BIT) != 1) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) != 1) { throw new IllegalArgumentException("A rule must end with a '1' bit."); } return new Rule(formula, effect); } - private Formula parseFormula(BitInputStream bitInputStream) throws IOException { - int separator = bitInputStream.getNext(SEPARATOR_BITS); + private Formula parseFormula(BitTrackedInputStream bitTrackedInputStream) throws IOException { + int separator = bitTrackedInputStream.getNext(SEPARATOR_BITS); switch (separator) { case ATOMIC_FORMULA_START: - return parseAtomicFormula(bitInputStream); + return parseAtomicFormula(bitTrackedInputStream); case COMPOUND_FORMULA_START: - return parseCompoundFormula(bitInputStream); + return parseCompoundFormula(bitTrackedInputStream); case COMPOUND_FORMULA_END: return null; default: @@ -110,37 +142,40 @@ public class RuleBinaryParser implements RuleParser { } } - private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException { - int connector = bitInputStream.getNext(CONNECTOR_BITS); + private CompoundFormula parseCompoundFormula(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + int connector = bitTrackedInputStream.getNext(CONNECTOR_BITS); List<Formula> formulas = new ArrayList<>(); - Formula parsedFormula = parseFormula(bitInputStream); + Formula parsedFormula = parseFormula(bitTrackedInputStream); while (parsedFormula != null) { formulas.add(parsedFormula); - parsedFormula = parseFormula(bitInputStream); + parsedFormula = parseFormula(bitTrackedInputStream); } return new CompoundFormula(connector, formulas); } - private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException { - int key = bitInputStream.getNext(KEY_BITS); - int operator = bitInputStream.getNext(OPERATOR_BITS); + private AtomicFormula parseAtomicFormula(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + int key = bitTrackedInputStream.getNext(KEY_BITS); + int operator = bitTrackedInputStream.getNext(OPERATOR_BITS); switch (key) { case AtomicFormula.PACKAGE_NAME: case AtomicFormula.APP_CERTIFICATE: case AtomicFormula.INSTALLER_NAME: case AtomicFormula.INSTALLER_CERTIFICATE: - boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; - int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); - String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue); + boolean isHashedValue = bitTrackedInputStream.getNext(IS_HASHED_BITS) == 1; + int valueSize = bitTrackedInputStream.getNext(VALUE_SIZE_BITS); + String stringValue = getStringValue(bitTrackedInputStream, valueSize, + isHashedValue); return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue); case AtomicFormula.VERSION_CODE: - int intValue = getIntValue(bitInputStream); + int intValue = getIntValue(bitTrackedInputStream); return new AtomicFormula.IntAtomicFormula(key, operator, intValue); case AtomicFormula.PRE_INSTALLED: - boolean booleanValue = getBooleanValue(bitInputStream); + boolean booleanValue = getBooleanValue(bitTrackedInputStream); return new AtomicFormula.BooleanAtomicFormula(key, booleanValue); default: throw new IllegalArgumentException(String.format("Unknown key: %d", key)); diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java index 81783d5c7324..a8e9f6134759 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java @@ -28,5 +28,6 @@ public interface RuleParser { List<Rule> parse(byte[] ruleBytes) throws RuleParseException; /** Parse rules from an input stream. */ - List<Rule> parse(InputStream inputStream) throws RuleParseException; + List<Rule> parse(InputStream inputStream, List<RuleIndexRange> ruleIndexRanges) + throws RuleParseException; } diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java index d405583442bd..497be8424286 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java @@ -62,7 +62,8 @@ public final class RuleXmlParser implements RuleParser { } @Override - public List<Rule> parse(InputStream inputStream) throws RuleParseException { + public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges) + throws RuleParseException { try { XmlPullParser xmlPullParser = Xml.newPullParser(); xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name()); diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index b8791c3c3489..f964d4cf2724 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -42,6 +42,7 @@ import android.content.integrity.Rule; import com.android.internal.util.Preconditions; import com.android.server.integrity.IntegrityUtils; import com.android.server.integrity.model.BitOutputStream; +import com.android.server.integrity.model.ByteTrackedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -109,7 +110,8 @@ public class RuleBinarySerializer implements RuleSerializer { } private void serializeRuleFileMetadata(Optional<Integer> formatVersion, - ByteTrackedOutputStream outputStream) throws IOException { + ByteTrackedOutputStream outputStream) + throws IOException { int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION); BitOutputStream bitOutputStream = new BitOutputStream(); diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java index ccfc98e2291b..ed6a759409d4 100644 --- a/services/core/java/com/android/server/location/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -16,11 +16,11 @@ package com.android.server.location; +import android.annotation.Nullable; import android.content.Context; import android.location.Location; import android.os.Binder; import android.os.Bundle; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -29,127 +29,336 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.UnaryOperator; /** - * Location Manager's interface for location providers. Always starts as disabled. + * Base class for all location providers. * * @hide */ public abstract class AbstractLocationProvider { /** - * Interface for communicating from a location provider back to the location service. + * Interface for listening to location providers. */ - public interface LocationProviderManager { + public interface Listener { /** - * May be called to inform the location service of a change in this location provider's - * enabled/disabled state. + * Called when a provider's state changes. May be invoked from any thread. Will be + * invoked with a cleared binder identity. */ - void onSetEnabled(boolean enabled); + void onStateChanged(State oldState, State newState); /** - * May be called to inform the location service of a change in this location provider's - * properties. + * Called when a provider has a new location available. May be invoked from any thread. Will + * be invoked with a cleared binder identity. */ - void onSetProperties(ProviderProperties properties); + void onReportLocation(Location location); /** - * May be called to inform the location service that this provider has a new location - * available. + * Called when a provider has a new location available. May be invoked from any thread. Will + * be invoked with a cleared binder identity. */ - void onReportLocation(Location location); + void onReportLocation(List<Location> locations); + } + + /** + * Holds a representation of the public state of a provider. + */ + public static final class State { /** - * May be called to inform the location service that this provider has a new location - * available. + * Default state value for a location provider that is disabled with no properties and an + * empty provider package list. */ - void onReportLocation(List<Location> locations); + public static final State EMPTY_STATE = new State(false, null, + Collections.emptySet()); + + /** + * The provider's enabled state. + */ + public final boolean enabled; + + /** + * The provider's properties. + */ + @Nullable public final ProviderProperties properties; + + /** + * The provider's package name list - provider packages may be afforded special privileges. + */ + public final Set<String> providerPackageNames; + + private State(boolean enabled, ProviderProperties properties, + Set<String> providerPackageNames) { + this.enabled = enabled; + this.properties = properties; + this.providerPackageNames = Objects.requireNonNull(providerPackageNames); + } + + private State withEnabled(boolean enabled) { + if (enabled == this.enabled) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + private State withProperties(ProviderProperties properties) { + if (properties.equals(this.properties)) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + private State withProviderPackageNames(Set<String> providerPackageNames) { + if (providerPackageNames.equals(this.providerPackageNames)) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof State)) { + return false; + } + State state = (State) o; + return enabled == state.enabled && properties == state.properties + && providerPackageNames.equals(state.providerPackageNames); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, properties, providerPackageNames); + } + } + + // combines listener and state information so that they can be updated atomically with respect + // to each other and an ordering established. + private static class InternalState { + @Nullable public final Listener listener; + public final State state; + + private InternalState(@Nullable Listener listener, State state) { + this.listener = listener; + this.state = state; + } + + private InternalState withListener(Listener listener) { + if (listener == this.listener) { + return this; + } else { + return new InternalState(listener, state); + } + } + + private InternalState withState(State state) { + if (state.equals(this.state)) { + return this; + } else { + return new InternalState(listener, state); + } + } + + private InternalState withState(UnaryOperator<State> operator) { + return withState(operator.apply(state)); + } } protected final Context mContext; - private final LocationProviderManager mLocationProviderManager; + protected final Executor mExecutor; + + // we use a lock-free implementation to update state to ensure atomicity between updating the + // provider state and setting the listener, so that the state updates a listener sees are + // consistent with when the listener was set (a listener should not see any updates that occur + // before it was set, and should not miss any updates that occur after it was set). + private final AtomicReference<InternalState> mInternalState; - protected AbstractLocationProvider( - Context context, LocationProviderManager locationProviderManager) { + protected AbstractLocationProvider(Context context, Executor executor) { + this(context, executor, Collections.singleton(context.getPackageName())); + } + + protected AbstractLocationProvider(Context context, Executor executor, + Set<String> packageNames) { mContext = context; - mLocationProviderManager = locationProviderManager; + mExecutor = executor; + mInternalState = new AtomicReference<>( + new InternalState(null, State.EMPTY_STATE.withProviderPackageNames(packageNames))); } /** - * Call this method to report a change in provider enabled/disabled status. May be called from - * any thread. + * Sets the listener and returns the state at the moment the listener was set. The listener can + * expect to receive all state updates from after this point. */ - protected void setEnabled(boolean enabled) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onSetEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); + State setListener(@Nullable Listener listener) { + return mInternalState.updateAndGet( + internalState -> internalState.withListener(listener)).state; + } + + /** + * Retrieves the state of the provider. + */ + State getState() { + return mInternalState.get().state; + } + + /** + * Sets the state of the provider to the new state. + */ + void setState(State newState) { + InternalState oldInternalState = mInternalState.getAndUpdate( + internalState -> internalState.withState(newState)); + if (newState.equals(oldInternalState.state)) { + return; + } + + // we know that we only updated the state, so the listener for the old state is the same as + // the listener for the new state. + if (oldInternalState.listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + oldInternalState.listener.onStateChanged(oldInternalState.state, newState); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void setState(UnaryOperator<State> operator) { + InternalState oldInternalState = mInternalState.getAndUpdate( + internalState -> internalState.withState(operator)); + + // recreate the new state from our knowledge of the old state - unfortunately may result in + // an extra allocation, but oh well... + State newState = operator.apply(oldInternalState.state); + + if (newState.equals(oldInternalState.state)) { + return; + } + + // we know that we only updated the state, so the listener for the old state is the same as + // the listener for the new state. + if (oldInternalState.listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + oldInternalState.listener.onStateChanged(oldInternalState.state, newState); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Call this method to report a change in provider properties. May be called from - * any thread. + * The current enabled state of this provider. + */ + protected boolean isEnabled() { + return mInternalState.get().state.enabled; + } + + /** + * The current provider properties of this provider. + */ + @Nullable + protected ProviderProperties getProperties() { + return mInternalState.get().state.properties; + } + + /** + * The current package set of this provider. + */ + protected Set<String> getProviderPackages() { + return mInternalState.get().state.providerPackageNames; + } + + /** + * Call this method to report a change in provider enabled/disabled status. + */ + protected void setEnabled(boolean enabled) { + setState(state -> state.withEnabled(enabled)); + } + + /** + * Call this method to report a change in provider properties. */ protected void setProperties(ProviderProperties properties) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onSetProperties(properties); - } finally { - Binder.restoreCallingIdentity(identity); - } + setState(state -> state.withProperties(properties)); + } + + /** + * Call this method to report a change in provider packages. + */ + protected void setPackageNames(Set<String> packageNames) { + setState(state -> state.withProviderPackageNames(packageNames)); } /** - * Call this method to report a new location. May be called from any thread. + * Call this method to report a new location. */ protected void reportLocation(Location location) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onReportLocation(location); - } finally { - Binder.restoreCallingIdentity(identity); + Listener listener = mInternalState.get().listener; + if (listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + listener.onReportLocation(location); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Call this method to report a new location. May be called from any thread. + * Call this method to report a new location. */ protected void reportLocation(List<Location> locations) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onReportLocation(locations); - } finally { - Binder.restoreCallingIdentity(identity); + Listener listener = mInternalState.get().listener; + if (listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + listener.onReportLocation(locations); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Invoked by the location service to return a list of packages currently associated with this - * provider. May be called from any thread. + * Sets a new request and worksource for the provider. */ - public List<String> getProviderPackages() { - return Collections.singletonList(mContext.getPackageName()); + public final void setRequest(ProviderRequest request) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onSetRequest(request)); } /** - * Invoked by the location service to deliver a new request for fulfillment to the provider. - * Replaces any previous requests completely. Will always be invoked from the location service - * thread with a cleared binder identity. + * Always invoked on the provider executor. */ - public abstract void onSetRequest(ProviderRequest request, WorkSource source); + protected abstract void onSetRequest(ProviderRequest request); + + /** + * Sends an extra command to the provider for it to interpret as it likes. + */ + public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras)); + } /** - * Invoked by the location service to deliver a custom command to this provider. Will always be - * invoked from the location service thread with a cleared binder identity. + * Always invoked on the provider executor. */ - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {} + protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} /** - * Invoked by the location service to dump debug or log information. May be invoked from any - * thread. + * Dumps debug or log information. May be invoked from any thread. */ public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args); } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index d8561b697caa..15cf190952d1 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -43,6 +43,7 @@ import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; @@ -113,8 +114,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private static final ProviderProperties PROPERTIES = new ProviderProperties( - true, true, false, false, true, true, true, - Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); + /* requiresNetwork = */false, + /* requiresSatellite = */true, + /* requiresCell = */false, + /* hasMonetaryCost = */false, + /* supportAltitude = */true, + /* supportsSpeed = */true, + /* supportsBearing = */true, + Criteria.POWER_HIGH, + Criteria.ACCURACY_FINE); // these need to match GnssPositionMode enum in IGnss.hal private static final int GPS_POSITION_MODE_STANDALONE = 0; @@ -616,13 +624,12 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } - public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager, - Looper looper) { - super(context, locationProviderManager); + public GnssLocationProvider(Context context, Handler handler) { + super(context, new HandlerExecutor(handler)); ensureInitialized(); - mLooper = looper; + mLooper = handler.getLooper(); // Create a wake lock mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -639,7 +646,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context, - GnssLocationProvider.this::onNetworkAvailable, looper); + GnssLocationProvider.this::onNetworkAvailable, mLooper); // App ops service to keep track of who is accessing the GPS mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -649,7 +656,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements BatteryStats.SERVICE_NAME)); // Construct internal handler - mHandler = new ProviderHandler(looper); + mHandler = new ProviderHandler(mLooper); // Load GPS configuration and register listeners in the background: // some operations, such as opening files and registering broadcast receivers, can take a @@ -693,10 +700,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements }; mGnssMetrics = new GnssMetrics(mBatteryStats); - mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this); + mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this); GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper = new GnssSatelliteBlacklistHelper(mContext, - looper, this); + mLooper, this); mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist); mGnssBatchingProvider = new GnssBatchingProvider(); mGnssGeofenceProvider = new GnssGeofenceProvider(); @@ -1047,8 +1054,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { - sendMessage(SET_REQUEST, 0, new GpsRequest(request, source)); + public void onSetRequest(ProviderRequest request) { + sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource)); } private void handleSetRequest(ProviderRequest request, WorkSource source) { @@ -1185,7 +1192,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) { + public void onExtraCommand(int uid, int pid, String command, Bundle extras) { long identity = Binder.clearCallingIdentity(); try { @@ -2064,10 +2071,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } /** - * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager, - * Looper)}. - * It is in charge of loading properties and registering for events that will be posted to - * this handler. + * This method is bound to the constructor. It is in charge of loading properties and + * registering for events that will be posted to this handler. */ private void handleInitialize() { // class_init_native() already initializes the GNSS service handle during class loading. diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index 694f14904668..8a149afa6238 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -23,12 +23,13 @@ import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; -import android.os.WorkSource; +import android.util.ArraySet; import android.util.Log; -import com.android.internal.annotations.GuardedBy; import com.android.internal.location.ILocationProvider; import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderProperties; @@ -39,10 +40,8 @@ import com.android.server.ServiceWatcher; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; /** * Proxy for ILocationProvider implementations. @@ -52,59 +51,64 @@ public class LocationProviderProxy extends AbstractLocationProvider { private static final String TAG = "LocationProviderProxy"; private static final boolean D = LocationManagerService.D; - // used to ensure that updates to mProviderPackages are atomic - private final Object mProviderPackagesLock = new Object(); - - // used to ensure that updates to mRequest and mWorkSource are atomic - private final Object mRequestLock = new Object(); + private static final int MAX_ADDITIONAL_PACKAGES = 2; private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() { // executed on binder thread @Override public void onSetAdditionalProviderPackages(List<String> packageNames) { - LocationProviderProxy.this.onSetAdditionalProviderPackages(packageNames); + int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()) + 1; + ArraySet<String> allPackages = new ArraySet<>(maxCount); + allPackages.add(mServiceWatcher.getCurrentPackageName()); + for (String packageName : packageNames) { + if (packageNames.size() >= maxCount) { + return; + } + + try { + mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY); + allPackages.add(packageName); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: " + + packageName); + } + } + + setPackageNames(allPackages); } // executed on binder thread @Override public void onSetEnabled(boolean enabled) { - LocationProviderProxy.this.setEnabled(enabled); + setEnabled(enabled); } // executed on binder thread @Override public void onSetProperties(ProviderProperties properties) { - LocationProviderProxy.this.setProperties(properties); + setProperties(properties); } // executed on binder thread @Override public void onReportLocation(Location location) { - LocationProviderProxy.this.reportLocation(location); + reportLocation(location); } }; private final ServiceWatcher mServiceWatcher; - @GuardedBy("mProviderPackagesLock") - private final CopyOnWriteArrayList<String> mProviderPackages = new CopyOnWriteArrayList<>(); - - @GuardedBy("mRequestLock") - @Nullable - private ProviderRequest mRequest; - @GuardedBy("mRequestLock") - private WorkSource mWorkSource; + @Nullable private ProviderRequest mRequest; /** * Creates a new LocationProviderProxy and immediately begins binding to the best applicable * service. */ @Nullable - public static LocationProviderProxy createAndBind( - Context context, LocationProviderManager locationProviderManager, String action, + public static LocationProviderProxy createAndBind(Context context, String action, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager, + LocationProviderProxy proxy = new LocationProviderProxy(context, FgThread.getHandler(), action, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId); if (proxy.bind()) { @@ -114,14 +118,13 @@ public class LocationProviderProxy extends AbstractLocationProvider { } } - private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager, - String action, int overlaySwitchResId, int defaultServicePackageNameResId, + private LocationProviderProxy(Context context, Handler handler, String action, + int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - super(context, locationProviderManager); + super(context, new HandlerExecutor(handler), Collections.emptySet()); mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, - FgThread.getHandler()) { + defaultServicePackageNameResId, initialPackageNamesResId, handler) { @Override protected void onBind() { @@ -130,14 +133,11 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Override protected void onUnbind() { - resetProviderPackages(Collections.emptyList()); - setEnabled(false); - setProperties(null); + setState(State.EMPTY_STATE); } }; mRequest = null; - mWorkSource = new WorkSource(); } private boolean bind() { @@ -148,77 +148,34 @@ public class LocationProviderProxy extends AbstractLocationProvider { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher); - resetProviderPackages(Collections.emptyList()); + setPackageNames(Collections.singleton(mServiceWatcher.getCurrentPackageName())); service.setLocationProviderManager(mManager); - synchronized (mRequestLock) { - if (mRequest != null) { - service.setRequest(mRequest, mWorkSource); - } - } - } - - @Override - public List<String> getProviderPackages() { - synchronized (mProviderPackagesLock) { - return mProviderPackages; + if (mRequest != null) { + service.setRequest(mRequest, mRequest.workSource); } } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { - synchronized (mRequestLock) { - mRequest = request; - mWorkSource = source; - } + public void onSetRequest(ProviderRequest request) { mServiceWatcher.runOnBinder(binder -> { + mRequest = request; ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - service.setRequest(request, source); + service.setRequest(request, request.workSource); }); } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("service=" + mServiceWatcher); - synchronized (mProviderPackagesLock) { - if (mProviderPackages.size() > 1) { - pw.println("additional packages=" + mProviderPackages); - } - } - } - - @Override - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) { + public void onExtraCommand(int uid, int pid, String command, Bundle extras) { mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); service.sendExtraCommand(command, extras); }); } - private void onSetAdditionalProviderPackages(List<String> packageNames) { - resetProviderPackages(packageNames); - } - - private void resetProviderPackages(List<String> additionalPackageNames) { - ArrayList<String> permittedPackages = new ArrayList<>(additionalPackageNames.size()); - for (String packageName : additionalPackageNames) { - try { - mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY); - permittedPackages.add(packageName); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: " - + packageName); - } - } - - synchronized (mProviderPackagesLock) { - mProviderPackages.clear(); - String myPackage = mServiceWatcher.getCurrentPackageName(); - if (myPackage != null) { - mProviderPackages.add(myPackage); - mProviderPackages.addAll(permittedPackages); - } - } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("service=" + mServiceWatcher); } } diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java index f625452975c0..0e8720ebb08f 100644 --- a/services/core/java/com/android/server/location/LocationSettingsStore.java +++ b/services/core/java/com/android/server/location/LocationSettingsStore.java @@ -16,6 +16,8 @@ package com.android.server.location; +import static android.location.LocationManager.FUSED_PROVIDER; +import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS; @@ -28,6 +30,7 @@ import android.app.ActivityManager; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; +import android.os.Binder; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -248,6 +251,9 @@ public class LocationSettingsStore { DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS); } + /** + * Retrieve maximum age of the last location. + */ public long getMaxLastLocationAgeMs() { return Settings.Global.getLong( mContext.getContentResolver(), @@ -256,6 +262,29 @@ public class LocationSettingsStore { } /** + * Set a value for the deprecated LOCATION_PROVIDERS_ALLOWED setting. This is used purely for + * backwards compatibility for old clients, and may be removed in the future. + */ + public void setLocationProviderAllowed(String provider, boolean enabled, int userId) { + // fused and passive provider never get public updates for legacy reasons + if (FUSED_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) { + return; + } + + long identity = Binder.clearCallingIdentity(); + try { + // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + (enabled ? "+" : "-") + provider, + userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Dump info for debugging. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java index 472876bfd86a..60c9fc12c201 100644 --- a/services/core/java/com/android/server/location/MockProvider.java +++ b/services/core/java/com/android/server/location/MockProvider.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.annotation.Nullable; import android.content.Context; import android.location.Location; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -34,41 +33,33 @@ import java.io.PrintWriter; */ public class MockProvider extends AbstractLocationProvider { - private boolean mEnabled; @Nullable private Location mLocation; - public MockProvider(Context context, - LocationProviderManager locationProviderManager, ProviderProperties properties) { - super(context, locationProviderManager); - - mEnabled = true; - mLocation = null; - + public MockProvider(Context context, ProviderProperties properties) { + // using a direct executor is only acceptable because this class is so simple it is trivial + // to verify that it does not acquire any locks or re-enter LMS from callbacks + super(context, Runnable::run); setProperties(properties); } /** Sets the enabled state of this mock provider. */ - public void setEnabled(boolean enabled) { - mEnabled = enabled; - super.setEnabled(enabled); + public void setProviderEnabled(boolean enabled) { + setEnabled(enabled); } /** Sets the location to report for this mock provider. */ - public void setLocation(Location l) { - mLocation = new Location(l); - if (!mLocation.isFromMockProvider()) { - mLocation.setIsFromMockProvider(true); - } - if (mEnabled) { - reportLocation(mLocation); - } + public void setProviderLocation(Location l) { + Location location = new Location(l); + location.setIsFromMockProvider(true); + mLocation = location; + reportLocation(location); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("last location=" + mLocation); + pw.println("last mock location=" + mLocation); } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) {} + public void onSetRequest(ProviderRequest request) {} } diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java new file mode 100644 index 000000000000..f50dfe7edbb7 --- /dev/null +++ b/services/core/java/com/android/server/location/MockableLocationProvider.java @@ -0,0 +1,289 @@ +/* + * 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.location; + +import android.annotation.Nullable; +import android.content.Context; +import android.location.Location; +import android.os.Bundle; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.location.ProviderRequest; +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; + +/** + * Represents a location provider that may switch between a mock implementation and a real + * implementation. Requires owners to provide a lock object that will be used internally and held + * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead + * to deadlock. + * + * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can + * be held BOTH when calling into this class AND when receiving a callback from this class is the + * lock given to this class via the constructor. Holding any other lock is ok as long as there is no + * possibility that it can be obtained within both codepaths. + * + * Holding the given lock guarantees atomicity of any operations on this class for the duration. + * + * @hide + */ +public class MockableLocationProvider extends AbstractLocationProvider { + + private final Object mOwnerLock; + + @GuardedBy("mOwnerLock") + @Nullable private AbstractLocationProvider mProvider; + @GuardedBy("mOwnerLock") + @Nullable private AbstractLocationProvider mRealProvider; + @GuardedBy("mOwnerLock") + @Nullable private MockProvider mMockProvider; + + @GuardedBy("mOwnerLock") + private ProviderRequest mRequest; + + /** + * The given lock object will be held any time the listener is invoked, and may also be acquired + * and released during the course of invoking any public methods. Holding the given lock ensures + * that provider state cannot change except as result of an explicit call by the owner of the + * lock into this class. The client is reponsible for ensuring this cannot cause deadlock. + * + * The client should expect that it may being to receive callbacks as soon as this constructor + * is invoked. + */ + public MockableLocationProvider(Context context, Object ownerLock, Listener listener) { + // using a direct executor is acceptable because all inbound calls are delegated to the + // actual provider implementations which will use their own executors + super(context, Runnable::run, Collections.emptySet()); + mOwnerLock = ownerLock; + mRequest = ProviderRequest.EMPTY_REQUEST; + + setListener(listener); + } + + /** + * Returns the current provider implementation. May be null if there is no current + * implementation. + */ + @Nullable + public AbstractLocationProvider getProvider() { + synchronized (mOwnerLock) { + return mProvider; + } + } + + /** + * Sets the real provider implementation, replacing any previous real provider implementation. + * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this + * results in a state change. + */ + public void setRealProvider(@Nullable AbstractLocationProvider provider) { + synchronized (mOwnerLock) { + if (mRealProvider == provider) { + return; + } + + mRealProvider = provider; + if (!isMock()) { + setProviderLocked(mRealProvider); + } + } + } + + /** + * Sets the mock provider implementation, replacing any previous mock provider implementation. + * Mock implementations are always used instead of real implementations if set. May cause an + * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a + * state change. + */ + public void setMockProvider(@Nullable MockProvider provider) { + synchronized (mOwnerLock) { + if (mMockProvider == provider) { + return; + } + + mMockProvider = provider; + if (mMockProvider != null) { + setProviderLocked(mMockProvider); + } else { + setProviderLocked(mRealProvider); + } + } + } + + @GuardedBy("mOwnerLock") + private void setProviderLocked(@Nullable AbstractLocationProvider provider) { + if (mProvider == provider) { + return; + } + + AbstractLocationProvider oldProvider = mProvider; + mProvider = provider; + + if (oldProvider != null) { + // do this after switching the provider - so even if the old provider is using a direct + // executor, if it re-enters this class within setRequest(), it will be ignored + oldProvider.setListener(null); + oldProvider.setRequest(ProviderRequest.EMPTY_REQUEST); + } + + State newState; + if (mProvider != null) { + newState = mProvider.setListener(new ListenerWrapper(mProvider)); + } else { + newState = State.EMPTY_STATE; + } + + ProviderRequest oldRequest = mRequest; + setState(newState); + + if (mProvider != null && oldRequest == mRequest) { + mProvider.setRequest(mRequest); + } + } + + /** + * Returns true if the current active provider implementation is the mock implementation, and + * false otherwise. + */ + public boolean isMock() { + synchronized (mOwnerLock) { + return mMockProvider != null && mProvider == mMockProvider; + } + } + + /** + * Sets the mock provider implementation's enabled state. Will throw an exception if the mock + * provider is not currently the active implementation. + */ + public void setMockProviderEnabled(boolean enabled) { + synchronized (mOwnerLock) { + Preconditions.checkState(isMock()); + mMockProvider.setProviderEnabled(enabled); + } + } + /** + * Sets the mock provider implementation's location. Will throw an exception if the mock + * provider is not currently the active implementation. + */ + public void setMockProviderLocation(Location location) { + synchronized (mOwnerLock) { + Preconditions.checkState(isMock()); + mMockProvider.setProviderLocation(location); + } + } + + @Override + public State getState() { + return super.getState(); + } + + /** + * Returns the current location request. + */ + public ProviderRequest getCurrentRequest() { + synchronized (mOwnerLock) { + return mRequest; + } + } + + protected void onSetRequest(ProviderRequest request) { + synchronized (mOwnerLock) { + if (request == mRequest) { + return; + } + + mRequest = request; + + if (mProvider != null) { + mProvider.setRequest(request); + } + } + } + + protected void onExtraCommand(int uid, int pid, String command, Bundle extras) { + synchronized (mOwnerLock) { + if (mProvider != null) { + mProvider.sendExtraCommand(uid, pid, command, extras); + } + } + } + + /** + * Dumps the current provider implementation. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + AbstractLocationProvider provider; + synchronized (mOwnerLock) { + provider = mProvider; + pw.println("request=" + mRequest); + } + + if (provider != null) { + // dump outside the lock in case the provider wants to acquire its own locks, and since + // the default provider dump behavior does not move things onto the provider thread... + provider.dump(fd, pw, args); + } + } + + // ensures that callbacks from the incorrect provider are never visible to clients - this + // requires holding the owner's lock for the duration of the callback + private class ListenerWrapper implements Listener { + + private final AbstractLocationProvider mListenerProvider; + + private ListenerWrapper(AbstractLocationProvider listenerProvider) { + mListenerProvider = listenerProvider; + } + + @Override + public final void onStateChanged(State oldState, State newState) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + setState(newState); + } + } + + @Override + public final void onReportLocation(Location location) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + reportLocation(location); + } + } + + @Override + public final void onReportLocation(List<Location> locations) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + reportLocation(locations); + } + } + } +} diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index 639b1eb1ed5e..b33877069d70 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.content.Context; import android.location.Criteria; import android.location.Location; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -37,13 +36,22 @@ import java.io.PrintWriter; public class PassiveProvider extends AbstractLocationProvider { private static final ProviderProperties PROPERTIES = new ProviderProperties( - false, false, false, false, false, false, false, - Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); + /* requiresNetwork = */false, + /* requiresSatellite = */false, + /* requiresCell = */false, + /* hasMonetaryCost = */false, + /* supportsAltitude = */false, + /* supportsSpeed = */false, + /* supportsBearing = */false, + Criteria.POWER_LOW, + Criteria.ACCURACY_COARSE); - private boolean mReportLocation; + private volatile boolean mReportLocation; - public PassiveProvider(Context context, LocationProviderManager locationProviderManager) { - super(context, locationProviderManager); + public PassiveProvider(Context context) { + // using a direct executor is only acceptable because this class is so simple it is trivial + // to verify that it does not acquire any locks or re-enter LMS from callbacks + super(context, Runnable::run); mReportLocation = false; @@ -52,7 +60,7 @@ public class PassiveProvider extends AbstractLocationProvider { } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { + public void onSetRequest(ProviderRequest request) { mReportLocation = request.reportLocation; } @@ -63,7 +71,5 @@ public class PassiveProvider extends AbstractLocationProvider { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("report location=" + mReportLocation); - } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {} } diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 9ca302edd204..9c9a4121830f 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -47,11 +47,11 @@ abstract class MediaRoute2Provider { public abstract void requestCreateSession(String packageName, String routeId, String routeType, long requestId); - public abstract void releaseSession(int sessionId); + public abstract void releaseSession(String sessionId); - public abstract void selectRoute(int sessionId, String routeId); - public abstract void deselectRoute(int sessionId, String routeId); - public abstract void transferToRoute(int sessionId, String routeId); + public abstract void selectRoute(String sessionId, String routeId); + public abstract void deselectRoute(String sessionId, String routeId); + public abstract void transferToRoute(String sessionId, String routeId); public abstract void sendControlRequest(String routeId, Intent request); public abstract void requestSetVolume(String routeId, int volume); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 5cc2b16e073a..635983575226 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -86,7 +86,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { if (mConnectionReady) { mActiveConnection.releaseSession(sessionId); updateBinding(); @@ -94,21 +94,21 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(String sessionId, String routeId) { if (mConnectionReady) { mActiveConnection.selectRoute(sessionId, routeId); } } @Override - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(String sessionId, String routeId) { if (mConnectionReady) { mActiveConnection.deselectRoute(sessionId, routeId); } } @Override - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(String sessionId, String routeId) { if (mConnectionReady) { mActiveConnection.transferToRoute(sessionId, routeId); } @@ -302,6 +302,11 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv + mComponentName); return; } + + sessionInfo = new RouteSessionInfo.Builder(sessionInfo) + .setProviderId(getUniqueId()) + .build(); + mCallback.onSessionInfoChanged(this, sessionInfo); } @@ -355,7 +360,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { try { mProvider.releaseSession(sessionId); } catch (RemoteException ex) { @@ -363,7 +368,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(String sessionId, String routeId) { try { mProvider.selectRoute(sessionId, routeId); } catch (RemoteException ex) { @@ -371,7 +376,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(String sessionId, String routeId) { try { mProvider.deselectRoute(sessionId, routeId); } catch (RemoteException ex) { @@ -379,7 +384,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(String sessionId, String routeId) { try { mProvider.transferToRoute(sessionId, routeId); } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index b48243279d9e..487ab5201278 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -16,6 +16,9 @@ package com.android.server.media; +import static android.media.MediaRouter2Utils.getOriginalId; +import static android.media.MediaRouter2Utils.getProviderId; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; @@ -197,6 +200,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -213,6 +219,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -228,6 +237,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -241,6 +253,9 @@ class MediaRouter2ServiceImpl { public void releaseSession(IMediaRouter2Client client, String uniqueSessionId) { Objects.requireNonNull(client, "client must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -607,7 +622,7 @@ class MediaRouter2ServiceImpl { } long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId); if (clientRecord != null && managerRecord.mTrusted) { - //TODO: select category properly + //TODO: select route type properly requestCreateSessionLocked(clientRecord.mClient, route, route.getRouteTypes().get(0), uniqueRequestId); } @@ -1002,8 +1017,7 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), - route.getOriginalId()); + provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } private void deselectRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1019,8 +1033,7 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), - route.getOriginalId()); + provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } private void transferToRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1036,7 +1049,7 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId), + provider.transferToRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } @@ -1068,9 +1081,9 @@ class MediaRouter2ServiceImpl { return false; } - final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId); + final String sessionId = getOriginalId(uniqueSessionId); if (sessionId == null) { - Slog.w(TAG, "Failed to get int session id from unique session id. " + Slog.w(TAG, "Failed to get original session id from unique session id. " + "uniqueSessionId=" + uniqueSessionId); return false; } @@ -1093,14 +1106,14 @@ class MediaRouter2ServiceImpl { return; } - final String providerId = RouteSessionInfo.getProviderId(uniqueSessionId); + final String providerId = getProviderId(uniqueSessionId); if (providerId == null) { Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. " + "uniqueSessionId=" + uniqueSessionId); return; } - final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId); + final String sessionId = getOriginalId(uniqueSessionId); if (sessionId == null) { Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. " + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId); @@ -1146,15 +1159,15 @@ class MediaRouter2ServiceImpl { } String originalRouteId = matchingRequest.mRoute.getId(); - String originalCategory = matchingRequest.mRouteType; + String originalRouteType = matchingRequest.mRouteType; Client2Record client2Record = matchingRequest.mClientRecord; if (!sessionInfo.getSelectedRoutes().contains(originalRouteId) - || !TextUtils.equals(originalCategory, + || !TextUtils.equals(originalRouteType, sessionInfo.getRouteType())) { Slog.w(TAG, "Created session doesn't match the original request." + " originalRouteId=" + originalRouteId - + ", originalCategory=" + originalCategory + ", requestId=" + requestId + + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo); notifySessionCreationFailed(matchingRequest.mClientRecord, toClientRequestId(requestId)); @@ -1164,41 +1177,34 @@ class MediaRouter2ServiceImpl { // Succeeded notifySessionCreated(matchingRequest.mClientRecord, sessionInfo, toClientRequestId(requestId)); - mSessionToClientMap.put(sessionInfo.getUniqueSessionId(), client2Record); + mSessionToClientMap.put(sessionInfo.getId(), client2Record); // TODO: Tell managers for the session creation } private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RouteSessionInfo sessionInfo) { - RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo) - .setProviderId(provider.getUniqueId()) - .build(); Client2Record client2Record = mSessionToClientMap.get( - sessionInfoWithProviderId.getUniqueSessionId()); + sessionInfo.getId()); if (client2Record == null) { - Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId); + Slog.w(TAG, "No matching client found for session=" + sessionInfo); // TODO: Tell managers for the session update return; } - notifySessionInfoChanged(client2Record, sessionInfoWithProviderId); + notifySessionInfoChanged(client2Record, sessionInfo); // TODO: Tell managers for the session update } private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RouteSessionInfo sessionInfo) { - RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo) - .setProviderId(provider.getUniqueId()) - .build(); - Client2Record client2Record = mSessionToClientMap.get( - sessionInfoWithProviderId.getUniqueSessionId()); + Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId()); if (client2Record == null) { - Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId); + Slog.w(TAG, "No matching client found for session=" + sessionInfo); // TODO: Tell managers for the session release return; } - notifySessionReleased(client2Record, sessionInfoWithProviderId); + notifySessionReleased(client2Record, sessionInfo); // TODO: Tell managers for the session release } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index daf603012391..0ea4e63231d4 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -48,8 +48,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { static final String BLUETOOTH_ROUTE_ID = "BLUETOOTH_ROUTE"; // TODO: Move these to a proper place - public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO"; - public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO"; + public static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO"; + public static final String TYPE_LIVE_VIDEO = "android.media.intent.route.TYPE_LIVE_VIDEO"; private final AudioManager mAudioManager; private final IAudioService mAudioService; @@ -97,22 +97,22 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { // Do nothing } @Override - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(String sessionId, String routeId) { //TODO: implement method } @Override - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(String sessionId, String routeId) { //TODO: implement method } @Override - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(String sessionId, String routeId) { //TODO: implement method } @@ -141,8 +141,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addRouteType(CATEGORY_LIVE_AUDIO) - .addRouteType(CATEGORY_LIVE_VIDEO) + .addRouteType(TYPE_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_VIDEO) .build(); AudioRoutesInfo newAudioRoutes = null; @@ -181,8 +181,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addRouteType(CATEGORY_LIVE_AUDIO) - .addRouteType(CATEGORY_LIVE_VIDEO) + .addRouteType(TYPE_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_VIDEO) .build(); if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { @@ -193,7 +193,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mCurAudioRoutesInfo.bluetoothName) .setDescription(mContext.getResources().getText( R.string.bluetooth_a2dp_audio_route_name).toString()) - .addRouteType(CATEGORY_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_AUDIO) .build(); } else { mBluetoothA2dpRoute = null; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a4f4d079df27..728b297b4d8e 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2481,7 +2481,7 @@ public class NotificationManagerService extends SystemService { .setUid(r.sbn.getUid()) .setChannelId(r.getChannel().getId()) .setChannelName(r.getChannel().getName().toString()) - .setPostedTimeMs(r.sbn.getPostTime()) + .setPostedTimeMs(System.currentTimeMillis()) .setTitle(getHistoryTitle(r.getNotification())) .setText(getHistoryText( r.sbn.getPackageContext(getContext()), r.getNotification())) diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index b25e1e2160c3..ed7139991937 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -15,35 +15,45 @@ */ package com.android.server.pm; +import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; +import android.Manifest; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IApplicationThread; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ICrossProfileApps; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.stats.devicepolicy.DevicePolicyEnums; import android.text.TextUtils; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; +import com.android.internal.app.IAppOpsService; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; +import com.android.server.appop.AppOpsService; import com.android.server.wm.ActivityTaskManagerInternal; import java.util.ArrayList; @@ -55,6 +65,9 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { private Context mContext; private Injector mInjector; + private AppOpsService mAppOpsService; + private final DevicePolicyManagerInternal mDpmi; + private final IPackageManager mIpm; public CrossProfileAppsServiceImpl(Context context) { this(context, new InjectorImpl(context)); @@ -64,6 +77,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { CrossProfileAppsServiceImpl(Context context, Injector injector) { mContext = context; mInjector = injector; + mIpm = AppGlobals.getPackageManager(); + mDpmi = LocalServices.getService(DevicePolicyManagerInternal.class); } @Override @@ -153,6 +168,63 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { userId); } + @Override + public boolean canRequestInteractAcrossProfiles(String callingPackage) { + Objects.requireNonNull(callingPackage); + verifyCallingPackage(callingPackage); + + final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked( + callingPackage, mInjector.getCallingUserId()); + if (targetUserProfiles.isEmpty()) { + return false; + } + + if (!hasRequestedAppOpPermission( + AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), callingPackage)) { + return false; + } + return isCrossProfilePackageWhitelisted(callingPackage); + } + + private boolean hasRequestedAppOpPermission(String permission, String packageName) { + try { + String[] packages = mIpm.getAppOpPermissionPackages(permission); + return ArrayUtils.contains(packages, packageName); + } catch (RemoteException exc) { + Slog.e(TAG, "PackageManager dead. Cannot get permission info"); + return false; + } + } + + @Override + public boolean canInteractAcrossProfiles(String callingPackage) { + Objects.requireNonNull(callingPackage); + verifyCallingPackage(callingPackage); + + final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked( + callingPackage, mInjector.getCallingUserId()); + if (targetUserProfiles.isEmpty()) { + return false; + } + + final int callingUid = mInjector.getCallingUid(); + return isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid) + || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid) + || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid) + || AppOpsManager.MODE_ALLOWED == getAppOpsService().noteOperation( + OP_INTERACT_ACROSS_PROFILES, callingUid, callingPackage, /* featureId= */ null, + /*shouldCollectAsyncNotedOp= */false, /*message= */null); + } + + private boolean isCrossProfilePackageWhitelisted(String packageName) { + final long ident = mInjector.clearCallingIdentity(); + try { + return mDpmi.getAllCrossProfilePackages().contains(packageName); + } finally { + mInjector.restoreCallingIdentity(ident); + } + } + private List<UserHandle> getTargetUserProfilesUnchecked( String callingPackage, @UserIdInt int callingUserId) { final long ident = mInjector.clearCallingIdentity(); @@ -239,6 +311,19 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage); } + private static boolean isPermissionGranted(String permission, int uid) { + return PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission( + permission, uid, /* owningUid= */-1, /* exported= */ true); + } + + private AppOpsService getAppOpsService() { + if (mAppOpsService == null) { + IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); + mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b); + } + return mAppOpsService; + } + private static class InjectorImpl implements Injector { private Context mContext; diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java index 0541797523a4..6684e3f8973e 100644 --- a/services/core/java/com/android/server/pm/InstallSource.java +++ b/services/core/java/com/android/server/pm/InstallSource.java @@ -18,6 +18,8 @@ package com.android.server.pm; import android.annotation.Nullable; +import com.android.internal.util.Preconditions; + import java.util.Objects; /** @@ -29,16 +31,27 @@ final class InstallSource { * An instance of InstallSource representing an absence of knowledge of the source of * a package. Used in preference to null. */ - static final InstallSource EMPTY = new InstallSource(null, null, null, false); + static final InstallSource EMPTY = new InstallSource(null, null, null, false, false, null); /** We also memoize this case because it is common - all un-updated system apps. */ - private static final InstallSource EMPTY_ORPHANED = new InstallSource(null, null, null, true); + private static final InstallSource EMPTY_ORPHANED = new InstallSource( + null, null, null, true, false, null); - /** The package that requested the installation, if known. */ + /** + * The package that requested the installation, if known. May not correspond to a currently + * installed package if {@link #isInitiatingPackageUninstalled} is true. + */ @Nullable final String initiatingPackageName; /** + * The signing details of the initiating package, if known. Always null if + * {@link #initiatingPackageName} is null. + */ + @Nullable + final PackageSignatures initiatingPackageSignatures; + + /** * The package on behalf of which the initiating package requested the installation, if any. * For example if a downloaded APK is installed via the Package Installer this could be the * app that performed the download. This value is provided by the initiating package and not @@ -57,76 +70,120 @@ final class InstallSource { /** Indicates if the package that was the installerPackageName has been uninstalled. */ final boolean isOrphaned; + /** + * Indicates if the package in initiatingPackageName has been uninstalled. Always false if + * {@link #initiatingPackageName} is null. + */ + final boolean isInitiatingPackageUninstalled; + + static InstallSource create(@Nullable String initiatingPackageName, + @Nullable String originatingPackageName, @Nullable String installerPackageName) { + return create(initiatingPackageName, originatingPackageName, installerPackageName, + false, false); + } + static InstallSource create(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled) { return createInternal( intern(initiatingPackageName), intern(originatingPackageName), intern(installerPackageName), - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, null); } private static InstallSource createInternal(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled, + @Nullable PackageSignatures initiatingPackageSignatures) { if (initiatingPackageName == null && originatingPackageName == null - && installerPackageName == null) { + && installerPackageName == null && initiatingPackageSignatures == null + && !isInitiatingPackageUninstalled) { return isOrphaned ? EMPTY_ORPHANED : EMPTY; } return new InstallSource(initiatingPackageName, originatingPackageName, - installerPackageName, isOrphaned); + installerPackageName, isOrphaned, isInitiatingPackageUninstalled, + initiatingPackageSignatures + ); } private InstallSource(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled, + @Nullable PackageSignatures initiatingPackageSignatures) { + if (initiatingPackageName == null) { + Preconditions.checkArgument(initiatingPackageSignatures == null); + Preconditions.checkArgument(!isInitiatingPackageUninstalled); + } this.initiatingPackageName = initiatingPackageName; this.originatingPackageName = originatingPackageName; this.installerPackageName = installerPackageName; this.isOrphaned = isOrphaned; + this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled; + this.initiatingPackageSignatures = initiatingPackageSignatures; } /** - * Return an InstallSource the same as this one except with the specified installerPackageName. + * Return an InstallSource the same as this one except with the specified + * {@link #installerPackageName}. */ - InstallSource setInstallerPackage(String installerPackageName) { + InstallSource setInstallerPackage(@Nullable String installerPackageName) { if (Objects.equals(installerPackageName, this.installerPackageName)) { return this; } return createInternal(initiatingPackageName, originatingPackageName, - intern(installerPackageName), isOrphaned); + intern(installerPackageName), isOrphaned, isInitiatingPackageUninstalled, + initiatingPackageSignatures + ); } /** - * Return an InstallSource the same as this one except with the specified value for isOrphaned. + * Return an InstallSource the same as this one except with the specified value for + * {@link #isOrphaned}. */ InstallSource setIsOrphaned(boolean isOrphaned) { if (isOrphaned == this.isOrphaned) { return this; } return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures); } /** - * Return an InstallSource the same as this one except it does not refer to the specified - * installer package name (which is being uninstalled). + * Return an InstallSource the same as this one except with the specified + * {@link #initiatingPackageSignatures}. */ - InstallSource removeInstallerPackage(String packageName) { + InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) { + if (signatures == initiatingPackageSignatures) { + return this; + } + return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, + isOrphaned, isInitiatingPackageUninstalled, signatures); + } + + /** + * Return an InstallSource the same as this one updated to reflect that the specified installer + * package name has been uninstalled. + */ + InstallSource removeInstallerPackage(@Nullable String packageName) { if (packageName == null) { return this; } boolean modified = false; - String initiatingPackageName = this.initiatingPackageName; + boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled; String originatingPackageName = this.originatingPackageName; String installerPackageName = this.installerPackageName; boolean isOrphaned = this.isOrphaned; - if (packageName.equals(initiatingPackageName)) { - initiatingPackageName = null; - modified = true; + if (packageName.equals(this.initiatingPackageName)) { + if (!isInitiatingPackageUninstalled) { + // In this case we deliberately do not clear the package name (and signatures). + // We allow an app to retrieve details of its own install initiator even after + // it has been uninstalled. + isInitiatingPackageUninstalled = true; + modified = true; + } } if (packageName.equals(originatingPackageName)) { originatingPackageName = null; @@ -141,8 +198,9 @@ final class InstallSource { if (!modified) { return this; } + return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures); } @Nullable diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index e2dfa126225f..c43f23454ca6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -635,7 +635,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } InstallSource installSource = InstallSource.create(installerPackageName, - originatingPackageName, requestedInstallerPackageName, false); + originatingPackageName, requestedInstallerPackageName); session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid, installSource, params, createdMillis, diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4dcdf7ea0ecc..97a2d43e6183 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1475,7 +1475,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mInstallerUid = uid; - mInstallSource = InstallSource.create(packageName, null, packageName, false); + mInstallSource = InstallSource.create(packageName, null, packageName); } } catch (PackageManager.NameNotFoundException e) { onSessionTransferStatus(statusReceiver, packageName, @@ -3138,7 +3138,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } InstallSource installSource = InstallSource.create(installInitiatingPackageName, - installOriginatingPackageName, installerPackageName, false); + installOriginatingPackageName, installerPackageName); return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, installSource, params, createdMillis, stageDir, stageCid, fileInfosArray, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6bd9c4847a77..a9571d97200c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -15158,10 +15158,10 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); final String pkgName = pkg.getPackageName(); - final InstallSource installSource = installArgs.installSource; - final String installerPackageName = installSource.installerPackageName; final int[] installedForUsers = res.origUsers; final int installReason = installArgs.installReason; + InstallSource installSource = installArgs.installSource; + final String installerPackageName = installSource.installerPackageName; if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getCodePath()); synchronized (mLock) { @@ -15207,6 +15207,14 @@ public class PackageManagerService extends IPackageManager.Stub ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); } + if (installSource.initiatingPackageName != null) { + final PackageSetting ips = mSettings.mPackages.get( + installSource.initiatingPackageName); + if (ips != null) { + installSource = installSource.setInitiatingPackageSignatures( + ips.signatures); + } + } ps.setInstallSource(installSource); mSettings.addInstallerPackageNames(installSource); @@ -19976,19 +19984,25 @@ public class PackageManagerService extends IPackageManager.Stub } } - // All installSource strings are interned, so == is ok here - if (installSource.initiatingPackageName == installSource.installerPackageName) { - // The installer and initiator will often be the same, and when they are - // we can skip doing the same check again. - initiatingPackageName = installerPackageName; + if (installSource.isInitiatingPackageUninstalled) { + // TODO(b/146555198) Allow the app itself to see the info + // (at least for non-instant apps) + initiatingPackageName = null; } else { - initiatingPackageName = installSource.initiatingPackageName; - final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName); - if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { - initiatingPackageName = null; + // All installSource strings are interned, so == is ok here + if (installSource.initiatingPackageName == installSource.installerPackageName) { + // The installer and initiator will often be the same, and when they are + // we can skip doing the same check again. + initiatingPackageName = installerPackageName; + } else { + initiatingPackageName = installSource.initiatingPackageName; + final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName); + if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { + initiatingPackageName = null; + } } - } + originatingPackageName = installSource.originatingPackageName; if (originatingPackageName != null) { final PackageSetting ps = mSettings.mPackages.get(originatingPackageName); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index f9a336166825..ec84b51577f9 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2819,6 +2819,9 @@ public final class Settings { if (installSource.initiatingPackageName != null) { serializer.attribute(null, "installInitiator", installSource.initiatingPackageName); } + if (installSource.isInitiatingPackageUninstalled) { + serializer.attribute(null, "installInitiatorUninstalled", "true"); + } if (installSource.originatingPackageName != null) { serializer.attribute(null, "installOriginator", installSource.originatingPackageName); } @@ -2836,6 +2839,11 @@ public final class Settings { pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); + if (installSource.initiatingPackageSignatures != null) { + installSource.initiatingPackageSignatures.writeXml( + serializer, "install-initiator-sigs", mPastSignatures); + } + writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates()); writeSigningKeySetLPr(serializer, pkg.keySetData); @@ -3571,6 +3579,7 @@ public final class Settings { String isOrphaned = null; String installOriginatingPackageName = null; String installInitiatingPackageName = null; + String installInitiatorUninstalled = null; String volumeUuid = null; String categoryHintString = null; String updateAvailable = null; @@ -3616,6 +3625,8 @@ public final class Settings { isOrphaned = parser.getAttributeValue(null, "isOrphaned"); installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator"); installOriginatingPackageName = parser.getAttributeValue(null, "installOriginator"); + installInitiatorUninstalled = parser.getAttributeValue(null, + "installInitiatorUninstalled"); volumeUuid = parser.getAttributeValue(null, "volumeUuid"); categoryHintString = parser.getAttributeValue(null, "categoryHint"); if (categoryHintString != null) { @@ -3772,7 +3783,8 @@ public final class Settings { packageSetting.uidError = "true".equals(uidError); InstallSource installSource = InstallSource.create( installInitiatingPackageName, installOriginatingPackageName, - installerPackageName, "true".equals(isOrphaned)); + installerPackageName, "true".equals(isOrphaned), + "true".equals(installInitiatorUninstalled)); packageSetting.installSource = installSource; packageSetting.volumeUuid = volumeUuid; packageSetting.categoryHint = categoryHint; @@ -3849,6 +3861,11 @@ public final class Settings { mKeySetRefs.put(id, 1); } packageSetting.keySetData.addDefinedKeySet(id, alias); + } else if (tagName.equals("install-initiator-sigs")) { + final PackageSignatures signatures = new PackageSignatures(); + signatures.readXml(parser, mPastSignatures); + packageSetting.installSource = + packageSetting.installSource.setInitiatingPackageSignatures(signatures); } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) { readDomainVerificationLPw(parser, packageSetting); } else { diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index e7269a6d9498..a86c8d7545b1 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -58,6 +58,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.IntPair; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; @@ -68,7 +69,7 @@ import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; /** * This is a permission policy that governs over all permission mechanism @@ -280,7 +281,7 @@ public final class PermissionPolicyService extends SystemService { if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")"); // Now call into the permission controller to apply policy around permissions - final CountDownLatch latch = new CountDownLatch(1); + final AndroidFuture<Boolean> future = new AndroidFuture<>(); // We need to create a local manager that does not schedule work on the main // there as we are on the main thread and want to block until the work is @@ -290,22 +291,22 @@ public final class PermissionPolicyService extends SystemService { getUserContext(getContext(), UserHandle.of(userId)), FgThread.getHandler()); permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions( - FgThread.getExecutor(), - (Boolean success) -> { - if (!success) { + FgThread.getExecutor(), successful -> { + if (successful) { + future.complete(null); + } else { // We are in an undefined state now, let us crash and have // rescue party suggest a wipe to recover to a good one. - final String message = "Error granting/upgrading runtime permissions"; + final String message = "Error granting/upgrading runtime permissions" + + " for user " + userId; Slog.wtf(LOG_TAG, message); - throw new IllegalStateException(message); + future.completeExceptionally(new IllegalStateException(message)); } - latch.countDown(); - } - ); + }); try { - latch.await(); - } catch (InterruptedException e) { - /* ignore */ + future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); } permissionControllerManager.updateUserSensitive(); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index fd871bdc1ba2..d63165adb2f8 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -83,6 +83,7 @@ import static com.android.server.wm.Task.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT; +import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -2079,7 +2080,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { ArrayList<ActivityRecord> readyToStopActivities = null; for (int i = mStoppingActivities.size() - 1; i >= 0; --i) { final ActivityRecord s = mStoppingActivities.get(i); - final boolean animating = s.isAnimating(TRANSITION); + final boolean animating = s.isAnimating(TRANSITION | PARENTS); if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible + " animating=" + animating + " finishing=" + s.finishing); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 09111d07e8c1..014cb76c0064 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -436,10 +436,9 @@ public class AppTransition implements Dump { mNextAppTransition = TRANSIT_UNSET; mNextAppTransitionFlags = 0; setAppTransitionState(APP_STATE_RUNNING); - final AnimationAdapter topOpeningAnim = - (topOpeningApp != null && topOpeningApp.getAnimatingContainer() != null) - ? topOpeningApp.getAnimatingContainer().getAnimation() - : null; + final WindowContainer wc = + topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null; + final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null; int redoLayout = notifyAppTransitionStartingLocked(transit, topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index cefef37a1363..8ac212f7ef03 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -777,7 +777,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * otherwise. */ boolean isWaitingForTransitionStart() { - return getActivity(app -> app.isWaitingForTransitionStart()) != null; + return false; } /** diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp index 380ee942af98..cdbe77a3d64c 100644 --- a/services/devicepolicy/Android.bp +++ b/services/devicepolicy/Android.bp @@ -18,8 +18,3 @@ java_library_static { "compat-changeid-annotation-processor", ], } - -platform_compat_config { - name: "services-devicepolicy-platform-compat-config", - src: ":services.devicepolicy", -} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2a08f5c2de12..d5ff2802499c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11723,6 +11723,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return mStateCache; } + @Override + public List<String> getAllCrossProfilePackages() { + return DevicePolicyManagerService.this.getAllCrossProfilePackages(); + } + } private Intent createShowAdminSupportIntent(ComponentName admin, int userId) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index bfec51c92651..b6a8ca447213 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -751,6 +751,7 @@ public final class SystemServer { // Now that we have the bare essentials of the OS up and running, take // note that we just booted, which might send out a rescue party if // we're stuck in a runtime restart loop. + RescueParty.registerHealthObserver(mSystemContext); RescueParty.noteBoot(mSystemContext); // Manages LEDs and display backlight so we need it to bring up the display. diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 0d6020c3d06d..30d89d31ba64 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -32,6 +32,7 @@ import static org.mockito.ArgumentMatchers.isNull; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.VersionedPackage; import android.os.RecoverySystem; import android.os.SystemProperties; import android.os.UserHandle; @@ -39,6 +40,8 @@ import android.provider.DeviceConfig; import android.provider.Settings; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.PackageWatchdog.PackageHealthObserverImpact; +import com.android.server.RescueParty.RescuePartyObserver; import com.android.server.am.SettingsToPropertiesMapper; import com.android.server.utils.FlagNamespaceUtils; @@ -57,13 +60,15 @@ import java.util.HashMap; * Test RescueParty. */ public class RescuePartyTest { - private static final int PERSISTENT_APP_UID = 12; private static final long CURRENT_NETWORK_TIME_MILLIS = 0L; private static final String FAKE_NATIVE_NAMESPACE1 = "native1"; private static final String FAKE_NATIVE_NAMESPACE2 = "native2"; private static final String[] FAKE_RESET_NATIVE_NAMESPACES = {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2}; + private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1); + private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; + private MockitoSession mSession; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -182,25 +187,25 @@ public class RescuePartyTest { @Test public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() { - notePersistentAppCrash(RescueParty.TRIGGER_COUNT); + notePersistentAppCrash(); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - notePersistentAppCrash(RescueParty.TRIGGER_COUNT); + notePersistentAppCrash(); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - notePersistentAppCrash(RescueParty.TRIGGER_COUNT); + notePersistentAppCrash(); verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - notePersistentAppCrash(RescueParty.TRIGGER_COUNT); + notePersistentAppCrash(); verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); assertEquals(RescueParty.LEVEL_FACTORY_RESET, @@ -221,20 +226,6 @@ public class RescuePartyTest { } @Test - public void testPersistentAppCrashDetectionWithWrongInterval() { - notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1); - - // last persistent app crash is just outside of the boot loop detection window - doReturn(CURRENT_NETWORK_TIME_MILLIS - + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS + 1) - .when(() -> RescueParty.getElapsedRealtime()); - notePersistentAppCrash(/*numTimes=*/1); - - assertEquals(RescueParty.LEVEL_NONE, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test public void testBootLoopDetectionWithProperInterval() { noteBoot(RescueParty.TRIGGER_COUNT - 1); @@ -249,21 +240,6 @@ public class RescuePartyTest { } @Test - public void testPersistentAppCrashDetectionWithProperInterval() { - notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1); - - // last persistent app crash is just inside of the boot loop detection window - doReturn(CURRENT_NETWORK_TIME_MILLIS - + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS) - .when(() -> RescueParty.getElapsedRealtime()); - notePersistentAppCrash(/*numTimes=*/1); - - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); - assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test public void testBootLoopDetectionWithWrongTriggerCount() { noteBoot(RescueParty.TRIGGER_COUNT - 1); assertEquals(RescueParty.LEVEL_NONE, @@ -271,13 +247,6 @@ public class RescuePartyTest { } @Test - public void testPersistentAppCrashDetectionWithWrongTriggerCount() { - notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1); - assertEquals(RescueParty.LEVEL_NONE, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test public void testIsAttemptingFactoryReset() { noteBoot(RescueParty.TRIGGER_COUNT * 4); @@ -319,6 +288,77 @@ public class RescuePartyTest { FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true)); } + @Test + public void testExplicitlyEnablingAndDisablingRescue() { + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); + SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); + assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false); + + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)); + } + + @Test + public void testHealthCheckLevels() { + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + + // Ensure that no action is taken for cases where the failure reason is unknown + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_FACTORY_RESET)); + assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN), + PackageHealthObserverImpact.USER_IMPACT_NONE); + + /* + For the following cases, ensure that the returned user impact corresponds with the user + impact of the next available rescue level, not the current one. + */ + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_NONE)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_LOW); + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_LOW); + + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_HIGH); + + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_HIGH); + + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_FACTORY_RESET)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_HIGH); + } + + @Test + public void testRescueLevelIncrementsWhenExecuted() { + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_NONE)); + observer.execute(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH); + assertEquals(SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, -1), + RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS); + } + private void verifySettingsResets(int resetMode) { verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, resetMode, UserHandle.USER_SYSTEM)); @@ -332,9 +372,8 @@ public class RescuePartyTest { } } - private void notePersistentAppCrash(int numTimes) { - for (int i = 0; i < numTimes; i++) { - RescueParty.noteAppCrash(mMockContext, PERSISTENT_APP_UID); - } + private void notePersistentAppCrash() { + RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage( + "com.package.name", 1), PackageWatchdog.FAILURE_REASON_UNKNOWN); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index d11d98766b01..591c3a385e23 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -85,6 +85,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import org.junit.Before; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java new file mode 100644 index 000000000000..75239db92121 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -0,0 +1,128 @@ +/* + * 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.accessibility; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.Manifest; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.drawable.Icon; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; + +import androidx.test.InstrumentationRegistry; + +import com.android.server.LocalServices; +import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * APCT tests for {@link AccessibilityManagerService}. + */ +public class AccessibilityManagerServiceTest extends AndroidTestCase { + private static final String TAG = "A11Y_MANAGER_SERVICE_TEST"; + private static final int ACTION_ID = 20; + private static final String LABEL = "label"; + private static final String INTENT_ACTION = "TESTACTION"; + private static final String DESCRIPTION = "description"; + private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast( + InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0); + private static final RemoteAction TEST_ACTION = new RemoteAction( + Icon.createWithContentUri("content://test"), + LABEL, + DESCRIPTION, + TEST_PENDING_INTENT); + private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION = + new AccessibilityAction(ACTION_ID, LABEL); + + @Mock private PackageManager mMockPackageManager; + @Mock private WindowManagerInternal mMockWindowManagerService; + @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; + @Mock private SystemActionPerformer mMockSystemActionPerformer; + @Mock private AccessibilityWindowManager mMockA11yWindowManager; + @Mock private AccessibilityDisplayListener mMockA11yDisplayListener; + @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal; + + private AccessibilityManagerService mA11yms; + + @Override + protected void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerService); + LocalServices.addService( + ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal); + mA11yms = new AccessibilityManagerService( + InstrumentationRegistry.getContext(), + mMockPackageManager, + mMockSecurityPolicy, + mMockSystemActionPerformer, + mMockA11yWindowManager, + mMockA11yDisplayListener); + } + + @SmallTest + public void testRegisterSystemActionWithoutPermission() throws Exception { + doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission( + Manifest.permission.MANAGE_ACCESSIBILITY, + AccessibilityManagerService.FUNCTION_REGISTER_SYSTEM_ACTION); + + try { + mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); + fail(); + } catch (SecurityException expected) { + } + verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION); + } + + @SmallTest + public void testRegisterSystemAction() throws Exception { + mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); + verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION); + } + + @SmallTest + public void testUnregisterSystemActionWithoutPermission() throws Exception { + doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission( + Manifest.permission.MANAGE_ACCESSIBILITY, + AccessibilityManagerService.FUNCTION_UNREGISTER_SYSTEM_ACTION); + + try { + mA11yms.unregisterSystemAction(ACTION_ID); + fail(); + } catch (SecurityException expected) { + } + verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID); + } + + @SmallTest + public void testUnregisterSystemAction() throws Exception { + mA11yms.unregisterSystemAction(ACTION_ID); + verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index 99dd9a12eb72..2ce70b6f0889 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -44,6 +44,7 @@ import android.os.UserHandle; import android.testing.DexmakerShareClassLoaderRule; import android.view.Display; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 67075edb0143..9db5a080c093 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -53,6 +53,7 @@ import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java index 44a514f7623c..96ae102e53f3 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java @@ -31,6 +31,8 @@ import android.accessibilityservice.FingerprintGestureController; import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback; import android.accessibilityservice.IAccessibilityServiceConnection; +import com.android.server.accessibility.test.MessageCapturingHandler; + import org.junit.Before; import org.junit.Test; import org.mockito.Mock; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java index de7bc443b8c5..30d00ad716e6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java @@ -31,6 +31,7 @@ import android.hardware.fingerprint.IFingerprintService; import android.view.KeyEvent; import com.android.server.accessibility.FingerprintGestureDispatcher.FingerprintGestureClient; +import com.android.server.accessibility.test.MessageCapturingHandler; import org.junit.After; import org.junit.Before; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java index 23ce483be107..41235560dc91 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java @@ -44,6 +44,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.KeyEventDispatcher.KeyEventFilter; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.policy.WindowManagerPolicy; import org.hamcrest.Description; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java index 322653b4115c..78e651b7a3c1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java @@ -33,6 +33,7 @@ import android.view.KeyEvent; import androidx.test.runner.AndroidJUnit4; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.policy.WindowManagerPolicy; import org.hamcrest.Description; @@ -212,4 +213,4 @@ public class KeyboardInterceptorTest { description.appendText("Matches key event"); } } -}
\ No newline at end of file +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java index 773b8778c7bf..82c6498bd9be 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java @@ -47,6 +47,7 @@ import android.view.MagnificationSpec; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index 36e854ca77cd..1ac4a8ed96d0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -54,6 +54,7 @@ import android.view.accessibility.AccessibilityEvent; import androidx.test.runner.AndroidJUnit4; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.accessibility.utils.MotionEventMatcher; import org.hamcrest.Description; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index 8da927dcb4ab..c8baca610bdc 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java @@ -37,6 +37,7 @@ import android.hardware.display.DisplayManager; import android.os.IBinder; import android.view.accessibility.AccessibilityEvent; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import org.junit.After; diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java new file mode 100644 index 000000000000..1eb5eb51504a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java @@ -0,0 +1,142 @@ +/* + * 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.integrity.model; + +import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; +import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; +import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.utils.TestUtils.getBits; +import static com.android.server.integrity.utils.TestUtils.getBytes; +import static com.android.server.integrity.utils.TestUtils.getValueBits; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.content.integrity.AtomicFormula; +import android.content.integrity.CompoundFormula; +import android.content.integrity.Rule; + +import com.android.server.integrity.parser.BinaryFileOperations; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; + +@RunWith(JUnit4.class) +public class BitTrackedInputStreamTest { + private static final String COMPOUND_FORMULA_START_BITS = + getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); + private static final String COMPOUND_FORMULA_END_BITS = + getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); + private static final String ATOMIC_FORMULA_START_BITS = + getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); + private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); + private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); + private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS); + private static final String DENY = getBits(Rule.DENY, EFFECT_BITS); + + private static final String IS_NOT_HASHED = "0"; + private static final String START_BIT = "1"; + private static final String END_BIT = "1"; + + @Test + public void testBitOperationsCountBitsCorrectly() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + + // Right after construction, the read bits count should be 0. + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0); + + // Get next 10 bits should result with 10 bits read. + bitTrackedInputStream.getNext(10); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(10); + + // When we move the cursor 8 bytes, we should point to 64 bits. + bitTrackedInputStream.setCursorToByteLocation(8); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(64); + + // Read until the end and the total size of the input stream should be available. + while (bitTrackedInputStream.hasNext()) { + bitTrackedInputStream.getNext(1); + } + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(128); + } + + @Test + public void testBitInputStreamOperationsStillWork() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName)); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0); + + // Read until the string parameter. + String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream); + + // Verify that the read bytes are counted. + assertThat(stringValue).isEqualTo(packageName); + assertThat(bitTrackedInputStream.getReadBitsCount()).isGreaterThan(0); + } + + @Test + public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName)); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + + // Read more than two bytes. + bitTrackedInputStream.getNext(20); + + // Ask to move the cursor to the second byte. + assertThrows( + IllegalStateException.class, + () -> bitTrackedInputStream.setCursorToByteLocation(2)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java index 5ecb8b5c8169..c7cc343dd77e 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java @@ -14,12 +14,10 @@ * limitations under the License. */ -package com.android.server.integrity.serializer; +package com.android.server.integrity.model; import static com.google.common.truth.Truth.assertThat; -import com.android.server.integrity.model.BitOutputStream; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 9cc0ed85a044..51f5c755754c 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -48,6 +48,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -97,8 +98,10 @@ public class RuleBinaryParserTest { private static final byte[] DEFAULT_FORMAT_VERSION_BYTES = getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS)); + private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList(); + @Test - public void testBinaryStream_validCompoundFormula() throws Exception { + public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -131,13 +134,13 @@ public class RuleBinaryParserTest { /* isHashedValue= */ false))), Rule.DENY); - List<Rule> rules = binaryParser.parse(inputStream); + List<Rule> rules = binaryParser.parse(inputStream, NO_INDEXING); assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); } @Test - public void testBinaryString_validCompoundFormula_notConnector() throws Exception { + public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -175,7 +178,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validCompoundFormula_andConnector() throws Exception { + public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -223,7 +226,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validCompoundFormula_orConnector() throws Exception { + public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -272,7 +275,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_stringValue() throws Exception { + public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -304,7 +307,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_hashedValue() throws Exception { + public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception { String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; String ruleBits = START_BIT @@ -337,7 +340,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_integerValue() throws Exception { + public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception { int versionCode = 1; String ruleBits = START_BIT @@ -365,7 +368,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_booleanValue() throws Exception { + public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception { String isPreInstalled = "1"; String ruleBits = START_BIT @@ -392,7 +395,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidAtomicFormula() throws Exception { + public void testBinaryString_invalidAtomicFormula_noIndexing() { int versionCode = 1; String ruleBits = START_BIT @@ -415,7 +418,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_withNoRuleList() throws RuleParseException { + public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException { ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length); rule.put(DEFAULT_FORMAT_VERSION_BYTES); RuleParser binaryParser = new RuleBinaryParser(); @@ -426,7 +429,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_withEmptyRule() throws RuleParseException { + public void testBinaryString_withEmptyRule_noIndexing() { String ruleBits = START_BIT; byte[] ruleBytes = getBytes(ruleBits); ByteBuffer rule = @@ -442,7 +445,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception { + public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -478,7 +481,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidOperator() throws Exception { + public void testBinaryString_invalidRule_invalidOperator_noIndexing() { int versionCode = 1; String ruleBits = START_BIT @@ -506,7 +509,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidEffect() throws Exception { + public void testBinaryString_invalidRule_invalidEffect_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -536,7 +539,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidConnector() throws Exception { + public void testBinaryString_invalidRule_invalidConnector_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -566,7 +569,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidKey() throws Exception { + public void testBinaryString_invalidRule_invalidKey_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -596,7 +599,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidSeparator() throws Exception { + public void testBinaryString_invalidRule_invalidSeparator_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -626,7 +629,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidEndMarker() throws Exception { + public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -653,4 +656,65 @@ public class RuleBinaryParserTest { /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit", () -> binaryParser.parse(rule.array())); } + + @Test + public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly() + throws Exception { + String packageName2 = "com.test.2"; + + byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1")); + byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2)); + byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3")); + + ByteBuffer rule = + ByteBuffer.allocate( + DEFAULT_FORMAT_VERSION_BYTES.length + + ruleBytes1.length + + ruleBytes2.length + + ruleBytes3.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes1); + rule.put(ruleBytes2); + rule.put(ruleBytes3); + InputStream inputStream = new ByteArrayInputStream(rule.array()); + + RuleParser binaryParser = new RuleBinaryParser(); + + List<RuleIndexRange> indexRanges = new ArrayList<>(); + indexRanges.add( + new RuleIndexRange( + DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length, + DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length + + ruleBytes2.length)); + List<Rule> rules = binaryParser.parse(inputStream, indexRanges); + + Rule expectedRule = + new Rule( + new CompoundFormula( + CompoundFormula.NOT, + Collections.singletonList( + new AtomicFormula.StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + packageName2, + /* isHashedValue= */ false))), + Rule.DENY); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + private static String getRulesWithPackageName(String packageName) { + return START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + + } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java index a14197b17529..6944aee7fcb9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java @@ -73,7 +73,7 @@ public class RuleXmlParserTest { /* isHashedValue= */ false))), Rule.DENY); - List<Rule> rules = xmlParser.parse(inputStream); + List<Rule> rules = xmlParser.parse(inputStream, Collections.emptyList()); assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); } @@ -623,7 +623,7 @@ public class RuleXmlParserTest { assertExpectException( RuleParseException.class, /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag", - () -> xmlParser.parse(inputStream)); + () -> xmlParser.parse(inputStream, Collections.emptyList())); } private String generateTagWithAttribute( 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 c478ec472e61..15327b6e5463 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -156,8 +156,7 @@ public class PackageInstallerSessionTest { if (isMultiPackage) { params.isMultiPackage = true; } - InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller", - false); + InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller"); return new PackageInstallerSession( /* callback */ null, /* context */null, 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 12ba219d0365..1dd7e64690c7 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -365,8 +365,9 @@ public class AppStandbyControllerTests { public void testSetAppStandbyBucket() throws Exception { // For a known package, standby bucket should be set properly reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); // For an unknown package, standby bucket should not be set, hence NEVER is returned @@ -374,7 +375,7 @@ public class AppStandbyControllerTests { mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID); isPackageInstalled = false; // Mock package is not installed mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); isPackageInstalled = true; // Reset mocked variable for other tests assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN)); } @@ -468,12 +469,13 @@ public class AppStandbyControllerTests { @Test public void testPredictionTimedout() throws Exception { // Set it to timeout or usage, so that prediction can override it + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); // Fast forward 12 hours @@ -497,29 +499,31 @@ public class AppStandbyControllerTests { @Test public void testOverrides() throws Exception { // Can force to NEVER + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED, 1 * HOUR_MS); + REASON_MAIN_FORCED); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't override FORCED reason mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_FORCED, 1 * HOUR_MS); + REASON_MAIN_FORCED); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, - REASON_MAIN_PREDICTED, 1 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't override NEVER + mInjector.mElapsedRealtime = 2 * HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_DEFAULT, 2 * HOUR_MS); + REASON_MAIN_DEFAULT); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, 2 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't set to NEVER mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_USAGE, 2 * HOUR_MS); + REASON_MAIN_USAGE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_PREDICTED, 2 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); } @@ -530,7 +534,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // bucketing works after timeout @@ -554,15 +558,17 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, - REASON_MAIN_PREDICTED, 1000); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); + mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_WORKING_SET); + mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_FREQUENT); } @@ -582,18 +588,18 @@ public class AppStandbyControllerTests { // Still in ACTIVE after first USER_INTERACTION times out mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // Both timed out, so NOTIFICATION_SEEN timeout should be effective mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_WORKING_SET); mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_RARE); } @@ -625,7 +631,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100; // Make sure app is in NEVER bucket mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED, mInjector.mElapsedRealtime); + REASON_MAIN_FORCED); mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_NEVER); @@ -670,7 +676,7 @@ public class AppStandbyControllerTests { // Predict to ACTIVE mInjector.mElapsedRealtime += 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // CheckIdleStates should not change the prediction @@ -687,7 +693,7 @@ public class AppStandbyControllerTests { // Predict to FREQUENT mInjector.mElapsedRealtime = RARE_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_FREQUENT); // Add a short timeout event diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index c900f386b438..8397aa485595 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1568,44 +1568,16 @@ public class UsageStatsService extends SystemService implements } @Override - public void setAppStandbyBucket(String packageName, - int bucket, int userId) { + public void setAppStandbyBucket(String packageName, int bucket, int userId) { getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE, "No permission to change app standby state"); - if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE - || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) { - throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket); - } final int callingUid = Binder.getCallingUid(); - try { - userId = ActivityManager.getService().handleIncomingUser( - Binder.getCallingPid(), callingUid, userId, false, true, - "setAppStandbyBucket", null); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID; - final boolean systemCaller = UserHandle.isCore(callingUid); - final int reason = systemCaller - ? UsageStatsManager.REASON_MAIN_FORCED - : UsageStatsManager.REASON_MAIN_PREDICTED; + final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); try { - final int packageUid = mPackageManagerInternal.getPackageUid(packageName, - PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId); - // Caller cannot set their own standby state - if (packageUid == callingUid) { - throw new IllegalArgumentException("Cannot set your own standby bucket"); - } - if (packageUid < 0) { - throw new IllegalArgumentException( - "Cannot set standby bucket for non existent package (" + packageName - + ")"); - } - mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason, - SystemClock.elapsedRealtime(), shellCaller); + mAppStandby.setAppStandbyBucket(packageName, bucket, userId, + callingUid, callingPid); } finally { Binder.restoreCallingIdentity(token); } @@ -1643,37 +1615,11 @@ public class UsageStatsService extends SystemService implements "No permission to change app standby state"); final int callingUid = Binder.getCallingUid(); - try { - userId = ActivityManager.getService().handleIncomingUser( - Binder.getCallingPid(), callingUid, userId, false, true, - "setAppStandbyBucket", null); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID; - final int reason = shellCaller - ? UsageStatsManager.REASON_MAIN_FORCED - : UsageStatsManager.REASON_MAIN_PREDICTED; + final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); try { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - List<AppStandbyInfo> bucketList = appBuckets.getList(); - for (AppStandbyInfo bucketInfo : bucketList) { - final String packageName = bucketInfo.mPackageName; - final int bucket = bucketInfo.mStandbyBucket; - if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE - || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) { - throw new IllegalArgumentException( - "Cannot set the standby bucket to " + bucket); - } - // Caller cannot set their own standby state - if (mPackageManagerInternal.getPackageUid(packageName, - PackageManager.MATCH_ANY_USER, userId) == callingUid) { - throw new IllegalArgumentException("Cannot set your own standby bucket"); - } - mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason, - elapsedRealtime, shellCaller); - } + mAppStandby.setAppStandbyBuckets(appBuckets.getList(), userId, + callingUid, callingPid); } finally { Binder.restoreCallingIdentity(token); } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 9cf4803966c6..c4c1e21e7c41 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -49,6 +49,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -882,7 +883,8 @@ public class TelecomManager { */ public TelecomManager(Context context, ITelecomService telecomServiceImpl) { Context appContext = context.getApplicationContext(); - if (appContext != null) { + if (appContext != null && Objects.equals(context.getFeatureId(), + appContext.getFeatureId())) { mContext = appContext; } else { mContext = context; @@ -916,7 +918,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e); @@ -1113,7 +1115,8 @@ public class TelecomManager { public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() { try { if (isServiceConnected()) { - return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName()); + return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e); @@ -1138,8 +1141,8 @@ public class TelecomManager { boolean includeDisabledAccounts) { try { if (isServiceConnected()) { - return getTelecomService().getCallCapablePhoneAccounts( - includeDisabledAccounts, mContext.getOpPackageName()); + return getTelecomService().getCallCapablePhoneAccounts(includeDisabledAccounts, + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" + @@ -1442,7 +1445,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().isVoiceMailNumber(accountHandle, number, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#isVoiceMailNumber.", e); @@ -1464,7 +1467,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getVoiceMailNumber(accountHandle, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e); @@ -1485,7 +1488,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getLine1Number(accountHandle, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#getLine1Number.", e); @@ -1506,7 +1509,8 @@ public class TelecomManager { public boolean isInCall() { try { if (isServiceConnected()) { - return getTelecomService().isInCall(mContext.getOpPackageName()); + return getTelecomService().isInCall(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling isInCall().", e); @@ -1531,7 +1535,8 @@ public class TelecomManager { public boolean isInManagedCall() { try { if (isServiceConnected()) { - return getTelecomService().isInManagedCall(mContext.getOpPackageName()); + return getTelecomService().isInManagedCall(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling isInManagedCall().", e); @@ -1711,7 +1716,8 @@ public class TelecomManager { public boolean isTtySupported() { try { if (isServiceConnected()) { - return getTelecomService().isTtySupported(mContext.getOpPackageName()); + return getTelecomService().isTtySupported(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get TTY supported state.", e); @@ -1735,7 +1741,8 @@ public class TelecomManager { public @TtyMode int getCurrentTtyMode() { try { if (isServiceConnected()) { - return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName()); + return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e); @@ -1925,7 +1932,8 @@ public class TelecomManager { ITelecomService service = getTelecomService(); if (service != null) { try { - service.showInCallScreen(showDialpad, mContext.getOpPackageName()); + service.showInCallScreen(showDialpad, mContext.getOpPackageName(), + mContext.getFeatureId()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#showCallScreen", e); } @@ -1988,7 +1996,7 @@ public class TelecomManager { } try { service.placeCall(address, extras == null ? new Bundle() : extras, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#placeCall", e); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 204c37e9aa38..c54da6b4d527 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -35,12 +35,13 @@ interface ITelecomService { * * @param showDialpad if true, make the dialpad visible initially. */ - void showInCallScreen(boolean showDialpad, String callingPackage); + void showInCallScreen(boolean showDialpad, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getDefaultOutgoingPhoneAccount */ - PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage); + PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount @@ -56,12 +57,13 @@ interface ITelecomService { * @see TelecomServiceImpl#getCallCapablePhoneAccounts */ List<PhoneAccountHandle> getCallCapablePhoneAccounts( - boolean includeDisabledAccounts, String callingPackage); + boolean includeDisabledAccounts, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getSelfManagedPhoneAccounts */ - List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage); + List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage, + String callingFeatureId); /** * @see TelecomManager#getPhoneAccountsSupportingScheme @@ -123,17 +125,19 @@ interface ITelecomService { * @see TelecomServiceImpl#isVoiceMailNumber */ boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number, - String callingPackage); + String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getVoiceMailNumber */ - String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage); + String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getLine1Number */ - String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage); + String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getDefaultPhoneApp @@ -172,12 +176,12 @@ interface ITelecomService { /** * @see TelecomServiceImpl#isInCall */ - boolean isInCall(String callingPackage); + boolean isInCall(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#isInManagedCall */ - boolean isInManagedCall(String callingPackage); + boolean isInManagedCall(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#isRinging @@ -229,12 +233,12 @@ interface ITelecomService { /** * @see TelecomServiceImpl#isTtySupported */ - boolean isTtySupported(String callingPackage); + boolean isTtySupported(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getCurrentTtyMode */ - int getCurrentTtyMode(String callingPackage); + int getCurrentTtyMode(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#addNewIncomingCall @@ -249,7 +253,7 @@ interface ITelecomService { /** * @see TelecomServiceImpl#placeCall */ - void placeCall(in Uri handle, in Bundle extras, String callingPackage); + void placeCall(in Uri handle, in Bundle extras, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#enablePhoneAccount diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index f39981fdf25d..aaafee29e24a 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index eb02ea6f5e40..368f8f1dab2e 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -25,7 +25,7 @@ import android.content.res.Resources; import android.os.RemoteException; import android.permission.IPermissionManager; import android.provider.Settings; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index 22cbdaa0f133..a36ff9341275 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.text.TextUtils; import android.util.SparseIntArray; diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java index 2f3194214be6..45a563c09394 100644 --- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java +++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch; import com.android.internal.telephony.HbpcdLookup.MccIdd; diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index b30258906368..afb9b6f52bdb 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -40,7 +40,7 @@ import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.PackageChangeReceiver; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.Log; diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index 06c08f56aa1f..06c37288a1a6 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -24,7 +24,7 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.text.TextUtils; diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 80a55b2a1147..4109ca6bd7d0 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -28,7 +28,7 @@ import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; diff --git a/telephony/java/android/service/carrier/CarrierIdentifier.java b/telephony/java/android/service/carrier/CarrierIdentifier.java index af5bf7475f95..7957c258b54f 100644 --- a/telephony/java/android/service/carrier/CarrierIdentifier.java +++ b/telephony/java/android/service/carrier/CarrierIdentifier.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import com.android.internal.telephony.uicc.IccUtils; diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index 9753d8be4065..097041f672ac 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.content.Context; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 4d5713259a0f..6a622378dac7 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -3123,7 +3125,6 @@ public class CarrierConfigManager { * EAP-AKA: "0" * EAP-SIM: "1" * EAP-AKA_PRIME: "6" - * @hide */ public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool"; @@ -3398,12 +3399,17 @@ public class CarrierConfigManager { /** Prefix of all Ims.KEY_* constants. */ public static final String KEY_PREFIX = "ims."; - //TODO: Add configs related to IMS. + /** + * Delay in milliseconds to turn off wifi when IMS is registered over wifi. + */ + public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = + KEY_PREFIX + "wifi_off_deferring_time_int"; private Ims() {} private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); + defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_INT, 0); return defaults; } } diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java index 84be4e8b9ba4..719ba8d98773 100644 --- a/telephony/java/android/telephony/CbGeoUtils.java +++ b/telephony/java/android/telephony/CbGeoUtils.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.text.TextUtils; diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java index 534546921ebf..8e703fee3126 100644 --- a/telephony/java/android/telephony/CellIdentity.java +++ b/telephony/java/android/telephony/CellIdentity.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java index 2b1387c313ae..acb21f450243 100644 --- a/telephony/java/android/telephony/CellInfoCdma.java +++ b/telephony/java/android/telephony/CellInfoCdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java index 4f7c7a93d19b..79a9d44f36a2 100644 --- a/telephony/java/android/telephony/CellInfoGsm.java +++ b/telephony/java/android/telephony/CellInfoGsm.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java index 6d19261c8932..fed3ebf4af03 100644 --- a/telephony/java/android/telephony/CellInfoLte.java +++ b/telephony/java/android/telephony/CellInfoLte.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java index f1305f5ca768..58ff8c9558d9 100644 --- a/telephony/java/android/telephony/CellInfoTdscdma.java +++ b/telephony/java/android/telephony/CellInfoTdscdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java index ee5fec838d2d..33f6a555414c 100644 --- a/telephony/java/android/telephony/CellInfoWcdma.java +++ b/telephony/java/android/telephony/CellInfoWcdma.java @@ -18,7 +18,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.util.Objects; diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index 199843905854..cab3b0cd3c47 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -20,7 +20,7 @@ import android.annotation.IntRange; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.util.Objects; diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index a9f3487a0880..28052aa93486 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index a6ba9c279ae3..2ef2a52977ff 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index d28d750c6011..4d67bcf536cf 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.IntRange; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java index f4a3dbb37988..3bd9d5810136 100644 --- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.annotation.NonNull; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index 34b13858f104..535e9520074d 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.annotation.StringDef; import android.compat.annotation.UnsupportedAppUsage; diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java index b10649c7208f..a6dedf761636 100644 --- a/telephony/java/android/telephony/NetworkScan.java +++ b/telephony/java/android/telephony/NetworkScan.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.os.RemoteException; diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java index 8c5e10788b89..844289ce75d4 100644 --- a/telephony/java/android/telephony/NetworkService.java +++ b/telephony/java/android/telephony/NetworkService.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java index 89b96654451e..214ab41ae4f2 100644 --- a/telephony/java/android/telephony/NetworkServiceCallback.java +++ b/telephony/java/android/telephony/NetworkServiceCallback.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 6e86a4211834..2f9e6ac0f9ff 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 66feb7bac25d..2c62d0667e19 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index 3350a3371504..1f7d55f2758a 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index c0bc29d26c6c..c217b8b83c26 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 2c0a1c94dc8d..c24eeb74f6cd 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 321753bc1776..4510fede4e8e 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 607450b76917..843c0656efc3 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5868,7 +5868,10 @@ public class TelephonyManager { * @param AID Application id. See ETSI 102.221 and 101.220. * @param p2 P2 parameter (described in ISO 7816-4). * @return an IccOpenLogicalChannelResponse object. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) { return iccOpenLogicalChannel(getSubId(), AID, p2); } @@ -5899,7 +5902,10 @@ public class TelephonyManager { * @param p2 P2 parameter (described in ISO 7816-4). * @return an IccOpenLogicalChannelResponse object. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) { try { ITelephony telephony = getITelephony(); @@ -5927,7 +5933,10 @@ public class TelephonyManager { * iccOpenLogicalChannel. * @return true if the channel was closed successfully. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) { @@ -5954,7 +5963,10 @@ public class TelephonyManager { * @param channel is the channel id to be closed as returned by a successful * iccOpenLogicalChannel. * @return true if the channel was closed successfully. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public boolean iccCloseLogicalChannel(int channel) { return iccCloseLogicalChannel(getSubId(), channel); } @@ -5973,7 +5985,10 @@ public class TelephonyManager { * iccOpenLogicalChannel. * @return true if the channel was closed successfully. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public boolean iccCloseLogicalChannel(int subId, int channel) { try { ITelephony telephony = getITelephony(); @@ -6009,7 +6024,10 @@ 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 Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi @Nullable @@ -6047,7 +6065,10 @@ public class TelephonyManager { * @param data Data to be sent with the APDU. * @return The APDU response from the ICC card with the status appended at * the end. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1, int p2, int p3, String data) { return iccTransmitApduLogicalChannel(getSubId(), channel, cla, @@ -6076,7 +6097,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduLogicalChannel(int subId, int channel, int cla, int instruction, int p1, int p2, int p3, String data) { try { @@ -6112,7 +6136,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi @NonNull @@ -6148,7 +6175,10 @@ public class TelephonyManager { * @param data Data to be sent with the APDU. * @return The APDU response from the ICC card with the status appended at * the end. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduBasicChannel(int cla, int instruction, int p1, int p2, int p3, String data) { return iccTransmitApduBasicChannel(getSubId(), cla, @@ -6175,7 +6205,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduBasicChannel(int subId, int cla, int instruction, int p1, int p2, int p3, String data) { try { @@ -6203,7 +6236,10 @@ public class TelephonyManager { * @param p3 P3 value of the APDU command. * @param filePath * @return The APDU response. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3, String filePath) { return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath); @@ -6225,7 +6261,10 @@ public class TelephonyManager { * @param filePath * @return The APDU response. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2, int p3, String filePath) { try { @@ -6251,7 +6290,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card in hexadecimal format * with the last 4 bytes being the status word. If the command fails, * returns an empty string. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String sendEnvelopeWithStatus(String content) { return sendEnvelopeWithStatus(getSubId(), content); } @@ -6271,7 +6313,10 @@ public class TelephonyManager { * with the last 4 bytes being the status word. If the command fails, * returns an empty string. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String sendEnvelopeWithStatus(int subId, String content) { try { ITelephony telephony = getITelephony(); diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index 8e6c170f255b..a1d40e85fb10 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java index 93ccba1dd996..81a09c645070 100644 --- a/telephony/java/android/telephony/UiccAccessRule.java +++ b/telephony/java/android/telephony/UiccAccessRule.java @@ -15,6 +15,8 @@ */ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java index 414b9995fd58..121401277ce9 100644 --- a/telephony/java/android/telephony/VoLteServiceState.java +++ b/telephony/java/android/telephony/VoLteServiceState.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Bundle; diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index dbfb6a2a0f2e..fab1bf2215af 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -28,7 +28,7 @@ import android.provider.Telephony; import android.provider.Telephony.Carriers; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.NetworkType; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 372bdf1c0f81..bff12b624ae8 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -31,7 +31,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.telephony.AccessNetworkConstants; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 11dc78a611ff..d33d3f9a5eee 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -22,7 +22,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.LinkProperties; import android.os.RemoteException; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.data.DataService.DataServiceProvider; import java.lang.annotation.Retention; diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index e793979a61c9..8220b16500de 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -28,7 +28,7 @@ import android.os.Message; import android.os.RemoteException; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.Annotation.ApnType; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index 16662652847d..cd3fc953f9d2 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -25,7 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java index 8d2049b97138..abfee61930ed 100644 --- a/telephony/java/android/telephony/ims/ImsConferenceState.java +++ b/telephony/java/android/telephony/ims/ImsConferenceState.java @@ -24,7 +24,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telecom.Call; import android.telecom.Connection; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.Log; import java.util.HashMap; diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java index dcb9c9d5ec27..136a83e2eec9 100644 --- a/telephony/java/android/telephony/ims/ImsExternalCallState.java +++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java @@ -24,7 +24,7 @@ import android.annotation.TestApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index 4f0f089dfa79..c96271432ea2 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -35,6 +35,8 @@ import android.telephony.ims.feature.RcsFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; +import com.android.internal.telephony.IIntegerConsumer; + import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -158,9 +160,20 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Register registration callback: IImsRcsController is null"); + throw new ImsException("Cannot find remote IMS service", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + c.setExecutor(executor); - throw new UnsupportedOperationException("registerImsRegistrationCallback is not" - + "supported."); + try { + imsRcsController.registerImsRegistrationCallback(mSubId, c.getBinder()); + } catch (RemoteException | IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } } /**{@inheritDoc}*/ @@ -171,8 +184,18 @@ public class ImsRcsManager implements RegistrationManager { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } - throw new UnsupportedOperationException("unregisterImsRegistrationCallback is not" - + "supported."); + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Unregister registration callback: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.unregisterImsRegistrationCallback(mSubId, c.getBinder()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /**{@inheritDoc}*/ @@ -186,8 +209,23 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } - throw new UnsupportedOperationException("getRegistrationState is not" - + "supported."); + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Get registration state error: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.getImsRcsRegistrationState(mSubId, new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + executor.execute(() -> stateCallback.accept(result)); + } + }); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /**{@inheritDoc}*/ @@ -202,10 +240,25 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } - throw new UnsupportedOperationException("getRegistrationTransportType is not" - + "supported."); - } + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Get registration transport type error: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.getImsRcsRegistrationTransportType(mSubId, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + executor.execute(() -> transportTypeCallback.accept(result)); + } + }); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } /** * Registers an {@link AvailabilityCallback} with the system, which will provide RCS diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java index 6b728599c7d3..2d2e63812fad 100644 --- a/telephony/java/android/telephony/ims/ImsSsData.java +++ b/telephony/java/android/telephony/ims/ImsSsData.java @@ -22,7 +22,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl index e81bac0f6764..6f6aa44371fa 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl @@ -19,6 +19,9 @@ package android.telephony.ims.aidl; import android.net.Uri; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IRcsUceControllerCallback; +import android.telephony.ims.aidl.IImsRegistrationCallback; + +import com.android.internal.telephony.IIntegerConsumer; /** * Interface used to interact with the Telephony IMS. @@ -26,6 +29,13 @@ import android.telephony.ims.aidl.IRcsUceControllerCallback; * {@hide} */ interface IImsRcsController { + // IMS RCS registration commands + void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c); + void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c); + void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer); + void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer); + + // IMS RCS capability commands void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c); void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c); boolean isCapable(int subId, int capability, int radioTech); diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index cfc803ca3639..9116a3bf3bde 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -19,7 +19,7 @@ package com.android.ims; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ProvisioningManager; import android.telephony.ims.aidl.IImsConfig; diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java index 1d6ec2d82eb7..8e86ff788a08 100644 --- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java +++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseIntArray; import com.android.internal.telephony.cdma.sms.UserData; diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index e75c5933f1df..832502cae37d 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -20,7 +20,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.sysprop.TelephonyProperties; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.telephony.cdma.CdmaSmsCbProgramData; diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index b5af6467a0ad..cbf0f5c297e1 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -18,7 +18,7 @@ package com.android.internal.telephony.cdma.sms; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SmsCbCmasInfo; import android.telephony.cdma.CdmaSmsCbProgramData; import android.telephony.cdma.CdmaSmsCbProgramResults; diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 0681dc11066e..417aafd765ea 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -28,7 +28,7 @@ import static com.android.internal.telephony.SmsConstants.MessageClass; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.text.TextUtils; import com.android.internal.telephony.EncodeException; diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index eed9a86cf448..0dc740194034 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -21,7 +21,7 @@ import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; import android.graphics.Color; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.GsmAlphabet; diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index b4cafe41662e..656628eb39d5 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -896,39 +896,78 @@ public class PackageWatchdogTest { assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); } - /** Test that observers execute correctly for different failure reasons */ + /** Test that observers execute correctly for failures reasons that go through thresholding. */ @Test - public void testFailureReasons() { + public void testNonImmediateFailureReasons() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); - TestObserver observer4 = new TestObserver(OBSERVER_NAME_4); watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); - watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION); - watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, - VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); - raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B, - VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); - raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH); - raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_D, + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); assertThat(observer1.getLastFailureReason()).isEqualTo( - PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); - assertThat(observer2.getLastFailureReason()).isEqualTo( - PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); - assertThat(observer3.getLastFailureReason()).isEqualTo( PackageWatchdog.FAILURE_REASON_APP_CRASH); - assertThat(observer4.getLastFailureReason()).isEqualTo( + assertThat(observer2.getLastFailureReason()).isEqualTo( PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); } + /** Test that observers execute correctly for failures reasons that skip thresholding. */ + @Test + public void testImmediateFailures() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); + + assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B); + } + + /** + * Test that a persistent observer will mitigate failures if it wishes to observe a package. + */ + @Test + public void testPersistentObserverWatchesPackage() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1); + persistentObserver.setPersistent(true); + persistentObserver.setMayObservePackages(true); + + watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); + assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A); + } + + /** + * Test that a persistent observer will not mitigate failures if it does not wish to observe + * a given package. + */ + @Test + public void testPersistentObserverDoesNotWatchPackage() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1); + persistentObserver.setPersistent(true); + persistentObserver.setMayObservePackages(false); + + watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); + assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty(); + } + private void adoptShellPermissions(String... permissions) { InstrumentationRegistry .getInstrumentation() @@ -964,7 +1003,12 @@ public class PackageWatchdogTest { /** Trigger package failures above the threshold. */ private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog, List<VersionedPackage> packages, int failureReason) { - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { + long triggerFailureCount = watchdog.getTriggerFailureCount(); + if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK + || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { + triggerFailureCount = 1; + } + for (int i = 0; i < triggerFailureCount; i++) { watchdog.onPackageFailure(packages, failureReason); } mTestLooper.dispatchAll(); @@ -1000,6 +1044,8 @@ public class PackageWatchdogTest { private final String mName; private int mImpact; private int mLastFailureReason; + private boolean mIsPersistent = false; + private boolean mMayObservePackages = false; final List<String> mHealthCheckFailedPackages = new ArrayList<>(); final List<String> mMitigatedPackages = new ArrayList<>(); @@ -1028,9 +1074,25 @@ public class PackageWatchdogTest { return mName; } + public boolean isPersistent() { + return mIsPersistent; + } + + public boolean mayObservePackage(String packageName) { + return mMayObservePackages; + } + public int getLastFailureReason() { return mLastFailureReason; } + + public void setPersistent(boolean persistent) { + mIsPersistent = persistent; + } + + public void setMayObservePackages(boolean mayObservePackages) { + mMayObservePackages = mayObservePackages; + } } private static class TestController extends ExplicitHealthCheckController { diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp index f71be7b0b7d3..a6625ab9c17f 100644 --- a/tests/utils/testutils/Android.bp +++ b/tests/utils/testutils/Android.bp @@ -22,6 +22,7 @@ java_library { static_libs: [ "junit", "hamcrest-library", + "androidx.test.runner", ], libs: [ diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java index e2b517f875db..bce2ab5c5a7f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java +++ b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.accessibility; +package com.android.server.accessibility.test; import android.os.Handler; import android.os.Looper; @@ -31,7 +31,7 @@ import java.util.List; * at their target. */ public class MessageCapturingHandler extends Handler { - List<Pair<Message, Long>> timedMessages = new ArrayList<>(); + public List<Pair<Message, Long>> timedMessages = new ArrayList<>(); Handler.Callback mCallback; diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 65e9b7910e5e..945a23605b74 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -182,6 +182,12 @@ public final class SoftApConfiguration implements Parcelable { private final @SecurityType int mSecurityType; /** + * Delay in milliseconds before shutting down soft AP when + * there are no connected devices. + */ + private final int mShutdownTimeoutMillis; + + /** * Security types we support. */ /** @hide */ @@ -213,7 +219,7 @@ public final class SoftApConfiguration implements Parcelable { /** Private constructor for Builder and Parcelable implementation. */ private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid, @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel, - @SecurityType int securityType, int maxNumberOfClients) { + @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) { mSsid = ssid; mBssid = bssid; mPassphrase = passphrase; @@ -222,6 +228,7 @@ public final class SoftApConfiguration implements Parcelable { mChannel = channel; mSecurityType = securityType; mMaxNumberOfClients = maxNumberOfClients; + mShutdownTimeoutMillis = shutdownTimeoutMillis; } @Override @@ -240,13 +247,14 @@ public final class SoftApConfiguration implements Parcelable { && mBand == other.mBand && mChannel == other.mChannel && mSecurityType == other.mSecurityType - && mMaxNumberOfClients == other.mMaxNumberOfClients; + && mMaxNumberOfClients == other.mMaxNumberOfClients + && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis; } @Override public int hashCode() { return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid, - mBand, mChannel, mSecurityType, mMaxNumberOfClients); + mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis); } @Override @@ -261,6 +269,7 @@ public final class SoftApConfiguration implements Parcelable { sbuf.append(" \n Channel =").append(mChannel); sbuf.append(" \n SecurityType=").append(getSecurityType()); sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients); + sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis); return sbuf.toString(); } @@ -274,6 +283,7 @@ public final class SoftApConfiguration implements Parcelable { dest.writeInt(mChannel); dest.writeInt(mSecurityType); dest.writeInt(mMaxNumberOfClients); + dest.writeInt(mShutdownTimeoutMillis); } @Override @@ -289,7 +299,7 @@ public final class SoftApConfiguration implements Parcelable { in.readString(), in.readParcelable(MacAddress.class.getClassLoader()), in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(), - in.readInt()); + in.readInt(), in.readInt()); } @Override @@ -381,6 +391,15 @@ public final class SoftApConfiguration implements Parcelable { } /** + * Returns the shutdown timeout in milliseconds. + * The Soft AP will shutdown when there are no devices associated to it for + * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}. + */ + public int getShutdownTimeoutMillis() { + return mShutdownTimeoutMillis; + } + + /** * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a * Soft AP. * @@ -396,6 +415,7 @@ public final class SoftApConfiguration implements Parcelable { private int mChannel; private int mMaxNumberOfClients; private int mSecurityType; + private int mShutdownTimeoutMillis; /** * Constructs a Builder with default values (see {@link Builder}). @@ -409,6 +429,7 @@ public final class SoftApConfiguration implements Parcelable { mChannel = 0; mMaxNumberOfClients = 0; mSecurityType = SECURITY_TYPE_OPEN; + mShutdownTimeoutMillis = 0; } /** @@ -425,6 +446,7 @@ public final class SoftApConfiguration implements Parcelable { mChannel = other.mChannel; mMaxNumberOfClients = other.mMaxNumberOfClients; mSecurityType = other.mSecurityType; + mShutdownTimeoutMillis = other.mShutdownTimeoutMillis; } /** @@ -435,7 +457,8 @@ public final class SoftApConfiguration implements Parcelable { @NonNull public SoftApConfiguration build() { return new SoftApConfiguration(mSsid, mBssid, mPassphrase, - mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients); + mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients, + mShutdownTimeoutMillis); } /** @@ -643,5 +666,30 @@ public final class SoftApConfiguration implements Parcelable { mMaxNumberOfClients = maxNumberOfClients; return this; } + + /** + * Specifies the shutdown timeout in milliseconds. + * The Soft AP will shut down when there are no devices connected to it for + * the timeout duration. + * + * Specify a value of 0 to have the framework automatically use default timeout + * setting which defined in {@link R.integer.config_wifi_framework_soft_ap_timeout_delay} + * + * <p> + * <li>If not set, defaults to 0</li> + * <li>The shut down timout will apply when + * {@link Settings.Global.SOFT_AP_TIMEOUT_ENABLED} is true</li> + * + * @param timeoutMillis milliseconds of the timeout delay. + * @return Builder for chaining. + */ + @NonNull + public Builder setShutdownTimeoutMillis(int timeoutMillis) { + if (timeoutMillis < 0) { + throw new IllegalArgumentException("Invalid timeout value"); + } + mShutdownTimeoutMillis = timeoutMillis; + return this; + } } } diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java index acd334355806..eeea7e2a6cd8 100644 --- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -117,6 +117,7 @@ public class SoftApConfigurationTest { .setChannel(149, SoftApConfiguration.BAND_5GHZ) .setHiddenSsid(true) .setMaxNumberOfClients(10) + .setShutdownTimeoutMillis(500000) .build(); assertThat(original.getPassphrase()).isEqualTo("secretsecret"); assertThat(original.getSecurityType()).isEqualTo( @@ -125,6 +126,7 @@ public class SoftApConfigurationTest { assertThat(original.getChannel()).isEqualTo(149); assertThat(original.isHiddenSsid()).isEqualTo(true); assertThat(original.getMaxNumberOfClients()).isEqualTo(10); + assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000); SoftApConfiguration unparceled = parcelUnparcel(original); assertThat(unparceled).isNotSameAs(original); @@ -230,4 +232,10 @@ public class SoftApConfigurationTest { .build(); } + @Test(expected = IllegalArgumentException.class) + public void testInvalieShutdownTimeoutMillis() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setShutdownTimeoutMillis(-1) + .build(); + } } |