diff options
154 files changed, 4573 insertions, 2338 deletions
diff --git a/Android.bp b/Android.bp index 25e738ccb3cf..41d5f28a2680 100644 --- a/Android.bp +++ b/Android.bp @@ -158,9 +158,9 @@ java_defaults { "core/java/android/hardware/IConsumerIrService.aidl", "core/java/android/hardware/ISerialManager.aidl", "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl", - "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl", "core/java/android/hardware/biometrics/IBiometricService.aidl", "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl", + "core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl", "core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl", "core/java/android/hardware/display/IDisplayManager.aidl", "core/java/android/hardware/display/IDisplayManagerCallback.aidl", diff --git a/Android.mk b/Android.mk index 770ec20f151e..b7dda9a45ed8 100644 --- a/Android.mk +++ b/Android.mk @@ -73,55 +73,37 @@ $(OUT_DOCS)/offline-sdk-timestamp: $(OUT_DOCS)/offline-sdk-docs-docs.zip ( unzip -qo $< -d $(OUT_DOCS)/offline-sdk && touch -f $@ ) || exit 1 # ==== hiddenapi lists ======================================= -.KATI_RESTAT: \ - $(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) -$(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST): \ - .KATI_IMPLICIT_OUTPUTS := \ - $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) -$(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST): \ +.KATI_RESTAT: $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS) +$(INTERNAL_PLATFORM_HIDDENAPI_FLAGS): \ frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \ frameworks/base/config/hiddenapi-light-greylist.txt \ frameworks/base/config/hiddenapi-vendor-list.txt \ + frameworks/base/config/hiddenapi-greylist-max-o.txt \ frameworks/base/config/hiddenapi-max-sdk-p-blacklist.txt \ frameworks/base/config/hiddenapi-force-blacklist.txt \ $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \ $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \ $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \ - --input-public $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \ - --input-private $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \ - --input-whitelists $(PRIVATE_WHITELIST_INPUTS) \ - --input-greylists \ + --public $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \ + --private $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \ + --csv $(PRIVATE_FLAGS_INPUTS) \ + --greylist \ frameworks/base/config/hiddenapi-light-greylist.txt \ frameworks/base/config/hiddenapi-vendor-list.txt \ - frameworks/base/config/hiddenapi-max-sdk-p-blacklist.txt \ - <(comm -12 <(sort $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE)) \ - $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST)) \ - $(PRIVATE_GREYLIST_INPUTS) \ - --input-blacklists frameworks/base/config/hiddenapi-force-blacklist.txt \ - --output-whitelist $(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST).tmp \ - --output-light-greylist $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST).tmp \ - --output-dark-greylist $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST).tmp \ - --output-blacklist $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST).tmp - $(call commit-change-for-toc,$(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST)) - $(call commit-change-for-toc,$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)) - $(call commit-change-for-toc,$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)) - $(call commit-change-for-toc,$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)) + --greylist-ignore-conflicts $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \ + --greylist-max-o-ignore-conflicts \ + frameworks/base/config/hiddenapi-greylist-max-o.txt \ + --blacklist frameworks/base/config/hiddenapi-force-blacklist.txt \ + --output $@.tmp + $(call commit-change-for-toc,$@) $(INTERNAL_PLATFORM_HIDDENAPI_GREYLIST_METADATA): \ frameworks/base/tools/hiddenapi/merge_csv.py \ $(PRIVATE_METADATA_INPUTS) frameworks/base/tools/hiddenapi/merge_csv.py $(PRIVATE_METADATA_INPUTS) > $@ -$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST)) -$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)) -$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)) -$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)) +$(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_FLAGS)) $(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_GREYLIST_METADATA)) # Include subdirectory makefiles diff --git a/api/current.txt b/api/current.txt index 0dce8d490f7c..079539e3c5f4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -150,6 +150,7 @@ package android { field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final java.lang.String USE_BIOMETRIC = "android.permission.USE_BIOMETRIC"; field public static final deprecated java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; + field public static final java.lang.String USE_FULL_SCREEN_INTENT = "android.permission.USE_FULL_SCREEN_INTENT"; field public static final java.lang.String USE_SIP = "android.permission.USE_SIP"; field public static final java.lang.String VIBRATE = "android.permission.VIBRATE"; field public static final java.lang.String WAKE_LOCK = "android.permission.WAKE_LOCK"; @@ -11339,7 +11340,7 @@ package android.content.pm { method public abstract int checkSignatures(java.lang.String, java.lang.String); method public abstract int checkSignatures(int, int); method public abstract void clearInstantAppCookie(); - method public abstract void clearPackagePreferredActivities(java.lang.String); + method public abstract deprecated void clearPackagePreferredActivities(java.lang.String); method public abstract java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]); method public abstract void extendVerificationTimeout(int, int, long); method public abstract android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; @@ -11383,8 +11384,8 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String); - method public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int); + method public abstract deprecated int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String); + method public abstract deprecated java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int); method public abstract android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; @@ -29054,7 +29055,7 @@ package android.net.wifi { public class WifiManager { method public deprecated int addNetwork(android.net.wifi.WifiConfiguration); - method public boolean addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>); + method public int addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>); method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); method public static int calculateSignalLevel(int, int); method public deprecated void cancelWps(android.net.wifi.WifiManager.WpsCallback); @@ -29068,6 +29069,7 @@ package android.net.wifi { method public deprecated java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks(); method public android.net.wifi.WifiInfo getConnectionInfo(); method public android.net.DhcpInfo getDhcpInfo(); + method public int getMaxNumberOfNetworkSuggestionsPerApp(); method public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations(); method public java.util.List<android.net.wifi.ScanResult> getScanResults(); method public int getWifiState(); @@ -29086,7 +29088,7 @@ package android.net.wifi { method public deprecated boolean reassociate(); method public deprecated boolean reconnect(); method public deprecated boolean removeNetwork(int); - method public boolean removeNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>); + method public int removeNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>); method public void removePasspointConfiguration(java.lang.String); method public deprecated boolean saveConfiguration(); method public void setTdlsEnabled(java.net.InetAddress, boolean); @@ -29115,6 +29117,11 @@ package android.net.wifi { field public static final java.lang.String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE"; field public static final java.lang.String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED"; field public static final java.lang.String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS"; + field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 2; // 0x2 + field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 3; // 0x3 + field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL = 1; // 0x1 + field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 4; // 0x4 + field public static final int STATUS_NETWORK_SUGGESTIONS_SUCCESS = 0; // 0x0 field public static final deprecated java.lang.String SUPPLICANT_CONNECTION_CHANGE_ACTION = "android.net.wifi.supplicant.CONNECTION_CHANGE"; field public static final deprecated java.lang.String SUPPLICANT_STATE_CHANGED_ACTION = "android.net.wifi.supplicant.STATE_CHANGE"; field public static final int WIFI_MODE_FULL = 1; // 0x1 @@ -29156,6 +29163,9 @@ package android.net.wifi { method public void setReferenceCounted(boolean); } + public static abstract class WifiManager.NetworkSuggestionsStatusCode implements java.lang.annotation.Annotation { + } + public class WifiManager.WifiLock { method public void acquire(); method public boolean isHeld(); @@ -49070,10 +49080,12 @@ package android.view { method protected int getTopPaddingOffset(); method public android.view.TouchDelegate getTouchDelegate(); method public java.util.ArrayList<android.view.View> getTouchables(); + method public float getTransitionAlpha(); method public java.lang.String getTransitionName(); method public float getTranslationX(); method public float getTranslationY(); method public float getTranslationZ(); + method public long getUniqueDrawingId(); method public int getVerticalFadingEdgeLength(); method public int getVerticalScrollbarPosition(); method public int getVerticalScrollbarWidth(); @@ -49278,6 +49290,7 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); + method public void setAnimationMatrix(android.graphics.Matrix); method public void setAutofillHints(java.lang.String...); method public void setAutofillId(android.view.autofill.AutofillId); method public void setBackground(android.graphics.drawable.Drawable); @@ -49329,7 +49342,7 @@ package android.view { method public void setLayoutDirection(int); method public void setLayoutParams(android.view.ViewGroup.LayoutParams); method public final void setLeft(int); - method public void setLeftTopRightBottom(int, int, int, int); + method public final void setLeftTopRightBottom(int, int, int, int); method public void setLongClickable(boolean); method protected final void setMeasuredDimension(int, int); method public void setMinimumHeight(int); @@ -49396,6 +49409,7 @@ package android.view { method public void setTooltipText(java.lang.CharSequence); method public final void setTop(int); method public void setTouchDelegate(android.view.TouchDelegate); + method public void setTransitionAlpha(float); method public final void setTransitionName(java.lang.String); method public void setTranslationX(float); method public void setTranslationY(float); @@ -49869,6 +49883,7 @@ package android.view { method public deprecated boolean isAnimationCacheEnabled(); method protected boolean isChildrenDrawingOrderEnabled(); method protected deprecated boolean isChildrenDrawnWithCacheEnabled(); + method public boolean isLayoutSuppressed(); method public boolean isMotionEventSplittingEnabled(); method public boolean isTransitionGroup(); method public final void layout(int, int, int, int); @@ -49934,6 +49949,7 @@ package android.view { method public android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int); method public void startLayoutAnimation(); method public void startViewTransition(android.view.View); + method public void suppressLayout(boolean); method public void updateViewLayout(android.view.View, android.view.ViewGroup.LayoutParams); field protected static final int CLIP_TO_PADDING_MASK = 34; // 0x22 field public static final int FOCUS_AFTER_DESCENDANTS = 262144; // 0x40000 diff --git a/api/system-current.txt b/api/system-current.txt index 2a581a806a8a..62d446f7254e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -217,6 +217,10 @@ package android { field public static final java.lang.String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS"; } + public static final class Manifest.permission_group { + field public static final java.lang.String UNDEFINED = "android.permission-group.UNDEFINED"; + } + public static final class R.array { field public static final int config_keySystemUuidMapping = 17235973; // 0x1070005 } @@ -1240,7 +1244,7 @@ package android.content.pm { method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle); method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback); method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); - method public void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName); + method public deprecated void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName); method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public void sendDeviceCustomizationReadyBroadcast(); method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); @@ -3689,7 +3693,6 @@ package android.net.wifi { method public void disableEphemeralNetwork(java.lang.String); method public void forget(int, android.net.wifi.WifiManager.ActionListener); method public java.util.List<android.net.wifi.WifiConfiguration> getAllMatchingWifiConfigs(java.util.List<android.net.wifi.ScanResult>); - method public java.util.List<android.net.wifi.hotspot2.OsuProvider> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>); method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); method public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method public int getWifiApState(); @@ -3848,6 +3851,7 @@ package android.net.wifi { ctor public WifiScanner.ScanSettings(); field public int band; field public android.net.wifi.WifiScanner.ChannelSpec[] channels; + field public boolean ignoreLocationSettings; field public int maxPeriodInMs; field public int maxScansToCache; field public int numBssidsPerScan; @@ -4305,6 +4309,7 @@ package android.os { public class UserManager { method public void clearSeedAccountData(); + method public android.os.UserHandle getProfileParent(android.os.UserHandle); method public java.lang.String getSeedAccountName(); method public android.os.PersistableBundle getSeedAccountOptions(); method public java.lang.String getSeedAccountType(); @@ -4636,6 +4641,8 @@ package android.provider { field public static final java.lang.String HUSH_GESTURE_USED = "hush_gesture_used"; field public static final java.lang.String INSTANT_APPS_ENABLED = "instant_apps_enabled"; field public static final java.lang.String LAST_SETUP_SHOWN = "last_setup_shown"; + field public static final java.lang.String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis"; + field public static final java.lang.String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis"; field public static final java.lang.String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications"; field public static final java.lang.String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications"; field public static final java.lang.String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count"; @@ -5174,6 +5181,7 @@ package android.service.notification { package android.service.oemlock { public class OemLockManager { + method public java.lang.String getLockName(); method public boolean isOemUnlockAllowedByCarrier(); method public boolean isOemUnlockAllowedByUser(); method public void setOemUnlockAllowedByCarrier(boolean, byte[]); diff --git a/api/test-current.txt b/api/test-current.txt index b871c78571ad..46cbb52f6efa 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -943,6 +943,10 @@ package android.os.health { package android.os.storage { + public class StorageManager { + method public static boolean hasIsolatedStorage(); + } + public final class StorageVolume implements android.os.Parcelable { method public java.lang.String getPath(); } @@ -998,6 +1002,7 @@ package android.provider { } public static final class Settings.Secure extends android.provider.Settings.NameValueTable { + method public static void resetToDefaults(android.content.ContentResolver, java.lang.String); field public static final java.lang.String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled"; field public static final java.lang.String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service"; field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification"; @@ -1009,6 +1014,8 @@ package android.provider { field public static final java.lang.String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length"; field public static final java.lang.String DISABLED_PRINT_SERVICES = "disabled_print_services"; field public static final deprecated java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages"; + field public static final java.lang.String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis"; + field public static final java.lang.String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis"; field public static final java.lang.String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; field public static final java.lang.String USER_SETUP_COMPLETE = "user_setup_complete"; field public static final java.lang.String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index 1c3ebd877695..b23c1eda87b3 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -24,6 +24,7 @@ #include "incidentd_util.h" #include "section_list.h" +#include <android/os/IncidentReportArgs.h> #include <binder/IPCThreadState.h> #include <binder/IResultReceiver.h> #include <binder/IServiceManager.h> @@ -41,6 +42,15 @@ enum { WHAT_RUN_REPORT = 1, WHAT_SEND_BACKLOG_TO_DROPBOX = 2 }; #define DEFAULT_BYTES_SIZE_LIMIT (20 * 1024 * 1024) // 20MB #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day +// Skip logs (1100 - 1108) because they are already in the bug report +// Skip 1200, 1201, 1202, 3018 because they take too long +// TODO(120079956): Skip 3008, 3015 because of error +#define SKIPPED_SECTIONS { 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, /* Logs */ \ + 1200, 1201, 1202, /* Native, hal, java traces */ \ + 3008, /* "package --proto" */ \ + 3015, /* "activity --proto processes" */ \ + 3018 /* "meminfo -a --proto" */ } + namespace android { namespace os { namespace incidentd { @@ -391,6 +401,38 @@ status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<Str return NO_ERROR; } +status_t IncidentService::dump(int fd, const Vector<String16>& args) { + if (std::find(args.begin(), args.end(), String16("--proto")) == args.end()) { + ALOGD("Skip dumping incident. Only proto format is supported."); + dprintf(fd, "Incident dump only supports proto version.\n"); + return NO_ERROR; + } + + ALOGD("Dump incident proto"); + IncidentReportArgs incidentArgs; + incidentArgs.setDest(DEST_EXPLICIT); + int skipped[] = SKIPPED_SECTIONS; + for (const Section** section = SECTION_LIST; *section; section++) { + const int id = (*section)->id; + if (std::find(std::begin(skipped), std::end(skipped), id) == std::end(skipped)) { + incidentArgs.addSection(id); + } + } + + if (!checkIncidentPermissions(incidentArgs).isOk()) { + return PERMISSION_DENIED; + } + + int fd1 = dup(fd); + if (fd1 < 0) { + return -errno; + } + + mHandler->scheduleRunReport(new ReportRequest(incidentArgs, NULL, fd1)); + + return NO_ERROR; +} + } // namespace incidentd } // namespace os } // namespace android diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h index e176bfd95c5f..6252ad295224 100644 --- a/cmds/incidentd/src/IncidentService.h +++ b/cmds/incidentd/src/IncidentService.h @@ -112,6 +112,7 @@ public: virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override; virtual status_t command(FILE* in, FILE* out, FILE* err, Vector<String8>& args); + virtual status_t dump(int fd, const Vector<String16>& args); private: sp<ReportRequestQueue> mQueue; diff --git a/cmds/incidentd/src/main.cpp b/cmds/incidentd/src/main.cpp index 494882336611..098d74ecd786 100644 --- a/cmds/incidentd/src/main.cpp +++ b/cmds/incidentd/src/main.cpp @@ -45,7 +45,8 @@ int main(int /*argc*/, char** /*argv*/) { // Create the service sp<IncidentService> service = new IncidentService(looper); - if (defaultServiceManager()->addService(String16("incident"), service) != 0) { + if (defaultServiceManager()->addService(String16("incident"), service, false, + IServiceManager::DUMP_FLAG_PRIORITY_NORMAL | IServiceManager::DUMP_FLAG_PROTO) != 0) { ALOGE("Failed to add service"); return -1; } diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 661768914c69..3e5e82f7f4df 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -101,6 +101,7 @@ const int FIELD_ID_UID_MAP_DELETED_APPS = 4; const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = { {android::util::BINDER_CALLS, {6000, 10000}}, + {android::util::LOOPER_STATS, {1500, 2500}}, {android::util::CPU_TIME_PER_UID_FREQ, {6000, 10000}}, }; diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 25bd0330ad5c..e10f72913d22 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -2695,7 +2695,6 @@ Lcom/android/internal/telephony/dataconnection/DcTracker;->onRecordsLoadedOrSubI Lcom/android/internal/telephony/dataconnection/DcTracker;->onSetUserDataEnabled(Z)V Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Lcom/android/internal/telephony/dataconnection/ApnContext;)Z Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Ljava/lang/String;)Z -Lcom/android/internal/telephony/dataconnection/DcTracker;->registerForAllDataDisconnected(Landroid/os/Handler;ILjava/lang/Object;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->registerSettingsObserver()V Lcom/android/internal/telephony/dataconnection/DcTracker;->resetPollStats()V Lcom/android/internal/telephony/dataconnection/DcTracker;->restartDataStallAlarm()V @@ -2709,7 +2708,6 @@ Lcom/android/internal/telephony/dataconnection/DcTracker;->startNetStatPoll()V Lcom/android/internal/telephony/dataconnection/DcTracker;->stopDataStallAlarm()V Lcom/android/internal/telephony/dataconnection/DcTracker;->stopNetStatPoll()V Lcom/android/internal/telephony/dataconnection/DcTracker;->unregisterForAllDataDisconnected(Landroid/os/Handler;)V -Lcom/android/internal/telephony/dataconnection/DcTracker;->updateRecords()V Lcom/android/internal/telephony/DctConstants$Activity;->DATAIN:Lcom/android/internal/telephony/DctConstants$Activity; Lcom/android/internal/telephony/DctConstants$Activity;->DATAINANDOUT:Lcom/android/internal/telephony/DctConstants$Activity; Lcom/android/internal/telephony/DctConstants$Activity;->DATAOUT:Lcom/android/internal/telephony/DctConstants$Activity; @@ -3130,7 +3128,6 @@ Lcom/android/internal/telephony/ITelephony;->getCallState()I Lcom/android/internal/telephony/ITelephony;->getDataActivity()I Lcom/android/internal/telephony/ITelephony;->getDataState()I Lcom/android/internal/telephony/ITelephony;->getNetworkType()I -Lcom/android/internal/telephony/ITelephony;->getVoiceMessageCount()I Lcom/android/internal/telephony/ITelephony;->handlePinMmi(Ljava/lang/String;)Z Lcom/android/internal/telephony/ITelephony;->handlePinMmiForSubscriber(ILjava/lang/String;)Z Lcom/android/internal/telephony/ITelephony;->hasIccCard()Z @@ -3201,7 +3198,6 @@ Lcom/android/internal/telephony/Phone;->isVolteEnabled()Z Lcom/android/internal/telephony/Phone;->isWifiCallingEnabled()Z Lcom/android/internal/telephony/Phone;->mCi:Lcom/android/internal/telephony/CommandsInterface; Lcom/android/internal/telephony/Phone;->mContext:Landroid/content/Context; -Lcom/android/internal/telephony/Phone;->mDcTracker:Lcom/android/internal/telephony/dataconnection/DcTracker; Lcom/android/internal/telephony/Phone;->mIccRecords:Ljava/util/concurrent/atomic/AtomicReference; Lcom/android/internal/telephony/Phone;->mImsPhone:Lcom/android/internal/telephony/Phone; Lcom/android/internal/telephony/Phone;->mMmiRegistrants:Landroid/os/RegistrantList; @@ -3294,7 +3290,6 @@ Lcom/android/internal/telephony/ProxyController;->mOldRadioAccessFamily:[I Lcom/android/internal/telephony/ProxyController;->mRadioCapabilitySessionId:I Lcom/android/internal/telephony/ProxyController;->mSetRadioAccessFamilyStatus:[I Lcom/android/internal/telephony/ProxyController;->mUniqueIdGenerator:Ljava/util/concurrent/atomic/AtomicInteger; -Lcom/android/internal/telephony/ProxyController;->registerForAllDataDisconnected(ILandroid/os/Handler;ILjava/lang/Object;)V Lcom/android/internal/telephony/ProxyController;->sendRadioCapabilityRequest(IIIILjava/lang/String;II)V Lcom/android/internal/telephony/ProxyController;->sProxyController:Lcom/android/internal/telephony/ProxyController; Lcom/android/internal/telephony/RadioCapability;->getRadioAccessFamily()I @@ -3394,7 +3389,6 @@ Lcom/android/internal/telephony/ServiceStateTracker;->mVoiceRoamingOnRegistrants Lcom/android/internal/telephony/ServiceStateTracker;->notifyDataRegStateRilRadioTechnologyChanged()V Lcom/android/internal/telephony/ServiceStateTracker;->notifySignalStrength()Z Lcom/android/internal/telephony/ServiceStateTracker;->pollState()V -Lcom/android/internal/telephony/ServiceStateTracker;->powerOffRadioSafely(Lcom/android/internal/telephony/dataconnection/DcTracker;)V Lcom/android/internal/telephony/ServiceStateTracker;->reRegisterNetwork(Landroid/os/Message;)V Lcom/android/internal/telephony/ServiceStateTracker;->resetServiceStateInIwlanMode()V Lcom/android/internal/telephony/ServiceStateTracker;->setOperatorIdd(Ljava/lang/String;)V diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index c879db8967d3..446d98e97936 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -34,7 +34,9 @@ import android.view.InputDevice; import android.view.InputEvent; import android.view.MotionEvent; import android.view.Surface; +import android.view.SurfaceControl; import android.view.SurfaceHolder; +import android.view.SurfaceSession; import android.view.SurfaceView; import android.view.ViewGroup; import android.view.WindowManager; @@ -59,12 +61,16 @@ public class ActivityView extends ViewGroup { private VirtualDisplay mVirtualDisplay; private final SurfaceView mSurfaceView; - private Surface mSurface; + + /** + * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be + * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl. + */ + private SurfaceControl mRootSurfaceControl; private final SurfaceCallback mSurfaceCallback; private StateCallback mActivityViewCallback; - private IActivityManager mActivityManager; private IActivityTaskManager mActivityTaskManager; private IInputForwarder mInputForwarder; // Temp container to store view coordinates on screen. @@ -75,6 +81,9 @@ public class ActivityView extends ViewGroup { private final CloseGuard mGuard = CloseGuard.get(); private boolean mOpened; // Protected by mGuard. + private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); + private Surface mTmpSurface = new Surface(); + @UnsupportedAppUsage public ActivityView(Context context) { this(context, null /* attrs */); @@ -87,7 +96,6 @@ public class ActivityView extends ViewGroup { public ActivityView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mActivityManager = ActivityManager.getService(); mActivityTaskManager = ActivityTaskManager.getService(); mSurfaceView = new SurfaceView(context); mSurfaceCallback = new SurfaceCallback(); @@ -297,14 +305,19 @@ public class ActivityView extends ViewGroup { private class SurfaceCallback implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { - mSurface = mSurfaceView.getHolder().getSurface(); + mTmpSurface = new Surface(); if (mVirtualDisplay == null) { - initVirtualDisplay(); + initVirtualDisplay(new SurfaceSession(surfaceHolder.getSurface())); if (mVirtualDisplay != null && mActivityViewCallback != null) { mActivityViewCallback.onActivityViewReady(ActivityView.this); } } else { - mVirtualDisplay.setSurface(surfaceHolder.getSurface()); + // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by + // whether it has a surface. Setting a fake surface here so DisplayManager will + // consider this display on. + mVirtualDisplay.setSurface(mTmpSurface); + mTmpTransaction.reparent(mRootSurfaceControl, + mSurfaceView.getSurfaceControl().getHandle()).apply(); } updateLocation(); } @@ -319,8 +332,8 @@ public class ActivityView extends ViewGroup { @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - mSurface.release(); - mSurface = null; + mTmpSurface.release(); + mTmpSurface = null; if (mVirtualDisplay != null) { mVirtualDisplay.setSurface(null); } @@ -328,7 +341,7 @@ public class ActivityView extends ViewGroup { } } - private void initVirtualDisplay() { + private void initVirtualDisplay(SurfaceSession surfaceSession) { if (mVirtualDisplay != null) { throw new IllegalStateException("Trying to initialize for the second time."); } @@ -336,9 +349,13 @@ public class ActivityView extends ViewGroup { final int width = mSurfaceView.getWidth(); final int height = mSurfaceView.getHeight(); final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + + // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by + // whether it has a surface. Setting a fake surface here so DisplayManager will consider + // this display on. mVirtualDisplay = displayManager.createVirtualDisplay( DISPLAY_NAME + "@" + System.identityHashCode(this), - width, height, getBaseDisplayDensity(), mSurface, + width, height, getBaseDisplayDensity(), mTmpSurface, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); if (mVirtualDisplay == null) { @@ -348,11 +365,20 @@ public class ActivityView extends ViewGroup { final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + + mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession) + .setContainerLayer(true) + .setName(DISPLAY_NAME) + .build(); + try { + wm.reparentDisplayContent(displayId, mRootSurfaceControl.getHandle()); wm.dontOverrideDisplayInfo(displayId); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + + mTmpTransaction.show(mRootSurfaceControl).apply(); mInputForwarder = InputManager.getInstance().createInputForwarder(displayId); mTaskStackListener = new TaskStackListenerImpl(); try { @@ -392,9 +418,9 @@ public class ActivityView extends ViewGroup { displayReleased = false; } - if (mSurface != null) { - mSurface.release(); - mSurface = null; + if (mTmpSurface != null) { + mTmpSurface.release(); + mTmpSurface = null; } if (displayReleased && mActivityViewCallback != null) { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 16d35802a9f6..0281e6a0631e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3867,6 +3867,9 @@ public class Notification implements Parcelable * The system UI may choose to display a heads-up notification, instead of * launching this intent, while the user is using the device. * </p> + * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request + * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to + * use full screen intents.</p> * * @param intent The pending intent to launch. * @param highPriority Passing true will cause this notification to be sent @@ -4468,6 +4471,14 @@ public class Notification implements Parcelable } } + private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { + contentView.setDrawableTint( + R.id.alerted_icon, + false /* targetBackground */, + getNeutralColor(p), + PorterDuff.Mode.SRC_ATOP); + } + /** * @hide */ @@ -4870,6 +4881,7 @@ public class Notification implements Parcelable bindHeaderTextSecondary(contentView, p); bindHeaderChronometerAndTime(contentView, p); bindProfileBadge(contentView, p); + bindAlertedIcon(contentView, p); } bindActivePermissions(contentView, p); bindExpandButton(contentView, p); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 27471cac10e9..27ae0b0314d0 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1898,23 +1898,33 @@ public class WallpaperManager { * @hide */ public static ComponentName getDefaultWallpaperComponent(Context context) { + ComponentName cn = null; + String flat = SystemProperties.get(PROP_WALLPAPER_COMPONENT); if (!TextUtils.isEmpty(flat)) { - final ComponentName cn = ComponentName.unflattenFromString(flat); - if (cn != null) { - return cn; + cn = ComponentName.unflattenFromString(flat); + } + + if (cn == null) { + flat = context.getString(com.android.internal.R.string.default_wallpaper_component); + if (!TextUtils.isEmpty(flat)) { + cn = ComponentName.unflattenFromString(flat); } } - flat = context.getString(com.android.internal.R.string.default_wallpaper_component); - if (!TextUtils.isEmpty(flat)) { - final ComponentName cn = ComponentName.unflattenFromString(flat); - if (cn != null) { - return cn; + // Check if the package exists + if (cn != null) { + try { + final PackageManager packageManager = context.getPackageManager(); + packageManager.getPackageInfo(cn.getPackageName(), + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + } catch (PackageManager.NameNotFoundException e) { + cn = null; } } - return null; + return cn; } /** diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java index 237082e4cb66..2f0b44f76ffb 100644 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java +++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.os.Parcel; import android.os.Parcelable; +import android.util.EventLog; /** @@ -30,6 +31,8 @@ import android.os.Parcelable; */ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { + private static final int MAX_DESCRIPTOR_SIZE = 2048; + private final String mName; private final String mDescription; private final String mProvider; @@ -55,6 +58,12 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { mDescription = description; mProvider = provider; mSubclass = subclass; + + if (descriptors == null || descriptors.length > MAX_DESCRIPTOR_SIZE) { + EventLog.writeEvent(0x534e4554, "119819889", -1, ""); + throw new IllegalArgumentException("descriptors must be not null and shorter than " + + MAX_DESCRIPTOR_SIZE); + } mDescriptors = descriptors.clone(); } diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java index 11f8ab7551c2..e3672a7e064f 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -52,8 +52,7 @@ import java.util.List; @RequiresFeature(PackageManager.FEATURE_BLUETOOTH) public final class BluetoothManager { private static final String TAG = "BluetoothManager"; - private static final boolean DBG = true; - private static final boolean VDBG = true; + private static final boolean DBG = false; private final BluetoothAdapter mAdapter; diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 437039dcbccf..7d5202d0dbce 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -97,8 +97,7 @@ public abstract class ContentResolver { * * @hide */ - public static final boolean DEPRECATE_DATA_COLUMNS = SystemProperties - .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false); + public static final boolean DEPRECATE_DATA_COLUMNS = StorageManager.hasIsolatedStorage(); /** * Special filesystem path prefix which indicates that a path should be diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6421dc5a3f68..b7df2bf5a5f7 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5372,6 +5372,10 @@ public abstract class PackageManager { public abstract void removePackageFromPreferred(String packageName); /** + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superseded + * by (and conflicts with) the modern activity-based preferences. + * * Retrieve the list of all currently configured preferred packages. The * first package on the list is the most preferred, the last is the least * preferred. @@ -5380,6 +5384,7 @@ public abstract class PackageManager { * @return A List of PackageInfo objects, one for each preferred * application, in order of preference. */ + @Deprecated public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags); /** @@ -5406,11 +5411,16 @@ public abstract class PackageManager { ComponentName[] set, ComponentName activity); /** + * @deprecated This is a protected API that should not have been available + * to third party applications. It is the platform's responsibility for + * assigning preferred activities and this cannot be directly modified. + * * Same as {@link #addPreferredActivity(IntentFilter, int, ComponentName[], ComponentName)}, but with a specific userId to apply the preference to. * @hide */ + @Deprecated @UnsupportedAppUsage public void addPreferredActivityAsUser(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, @UserIdInt int userId) { @@ -5444,6 +5454,10 @@ public abstract class PackageManager { ComponentName[] set, ComponentName activity); /** + * @deprecated This is a protected API that should not have been available + * to third party applications. It is the platform's responsibility for + * assigning preferred activities and this cannot be directly modified. + * * Replaces an existing preferred activity mapping to the system, and if that were not present * adds a new preferred activity. This will be used to automatically select the given activity * component when {@link Context#startActivity(Intent) Context.startActivity()} finds multiple @@ -5459,6 +5473,7 @@ public abstract class PackageManager { * * @hide */ + @Deprecated @SystemApi public void replacePreferredActivity(@NonNull IntentFilter filter, int match, @NonNull List<ComponentName> set, @NonNull ComponentName activity) { @@ -5476,6 +5491,10 @@ public abstract class PackageManager { } /** + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superseded + * by (and conflicts with) the modern activity-based preferences. + * * Remove all preferred activity mappings, previously added with * {@link #addPreferredActivity}, from the * system whose activities are implemented in the given package name. @@ -5484,9 +5503,14 @@ public abstract class PackageManager { * @param packageName The name of the package whose preferred activity * mappings are to be removed. */ + @Deprecated public abstract void clearPackagePreferredActivities(String packageName); /** + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superseded + * by (and conflicts with) the modern activity-based preferences. + * * Retrieve all preferred activities, previously added with * {@link #addPreferredActivity}, that are * currently registered with the system. @@ -5503,6 +5527,7 @@ public abstract class PackageManager { * (the number of distinct IntentFilter records, not the number of unique * activity components) that were found. */ + @Deprecated public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters, @NonNull List<ComponentName> outActivities, String packageName); diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 6f49cc42f6f6..b49c4476e82d 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -28,6 +28,7 @@ import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManager.ResolveInfoFlags; import android.os.Bundle; import android.os.PersistableBundle; +import android.util.ArraySet; import android.util.SparseArray; import com.android.internal.util.function.TriFunction; @@ -37,6 +38,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.function.BiFunction; +import java.util.function.Consumer; /** * Package manager local system service interface. @@ -735,4 +737,22 @@ public abstract class PackageManagerInternal { /** Returns {@code true} if the given user requires extra badging for icons. */ public abstract boolean userNeedsBadging(int userId); + + /** + * Perform the given action for each package. + * Note that packages lock will be held while performin the actions. + * + * @param actionLocked action to be performed + */ + public abstract void forEachPackage(Consumer<PackageParser.Package> actionLocked); + + /** Returns the list of enabled components */ + public abstract ArraySet<String> getEnabledComponents(String packageName, int userId); + + /** Returns the list of disabled components */ + public abstract ArraySet<String> getDisabledComponents(String packageName, int userId); + + /** Returns whether the given package is enabled for the given user */ + public abstract @PackageManager.EnabledState int getApplicationEnabledState( + String packageName, int userId); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 49189e53f385..ac18dca74950 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2513,7 +2513,7 @@ public class PackageParser { // If the storage model feature flag is disabled, we need to fiddle // around with permission definitions to return us to pre-Q behavior. // STOPSHIP(b/112545973): remove once feature enabled by default - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { if ("android".equals(pkg.packageName)) { final ArraySet<String> newGroups = new ArraySet<>(); newGroups.add(android.Manifest.permission_group.MEDIA_AURAL); diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index 79e15a7a9a2d..0ec812fe0350 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -30,19 +30,29 @@ import java.util.concurrent.Executor; public interface BiometricAuthenticator { /** + * No biometric methods or nothing has been enrolled. + * Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist" + * modalities when calling authenticate(). * @hide */ - int TYPE_FINGERPRINT = 1; + int TYPE_NONE = 0; + /** + * Constant representing fingerprint. + * @hide + */ + int TYPE_FINGERPRINT = 1 << 0; /** + * Constant representing iris. * @hide */ - int TYPE_IRIS = 2; + int TYPE_IRIS = 1 << 1; /** + * Constant representing face. * @hide */ - int TYPE_FACE = 3; + int TYPE_FACE = 1 << 2; /** * Container for biometric data diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index bd149fd05f59..b238d778f55a 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -251,27 +251,11 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan private Executor mExecutor; private AuthenticationCallback mAuthenticationCallback; - IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() { - @Override - public void onDialogDismissed(int reason) { - // Check the reason and invoke OnClickListener(s) if necessary - if (reason == DISMISSED_REASON_POSITIVE) { - mPositiveButtonInfo.executor.execute(() -> { - mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); - }); - } else if (reason == DISMISSED_REASON_NEGATIVE) { - mNegativeButtonInfo.executor.execute(() -> { - mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); - }); - } - } - }; - - IBiometricServiceReceiver mBiometricServiceReceiver = + private final IBiometricServiceReceiver mBiometricServiceReceiver = new IBiometricServiceReceiver.Stub() { @Override - public void onAuthenticationSucceeded(long deviceId) throws RemoteException { + public void onAuthenticationSucceeded() throws RemoteException { mExecutor.execute(() -> { final AuthenticationResult result = new AuthenticationResult(mCryptoObject); mAuthenticationCallback.onAuthenticationSucceeded(result); @@ -279,26 +263,39 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } @Override - public void onAuthenticationFailed(long deviceId) throws RemoteException { + public void onAuthenticationFailed() throws RemoteException { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationFailed(); }); } @Override - public void onError(long deviceId, int error, String message) - throws RemoteException { + public void onError(int error, String message) throws RemoteException { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationError(error, message); }); } @Override - public void onAcquired(long deviceId, int acquireInfo, String message) { + public void onAcquired(int acquireInfo, String message) throws RemoteException { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message); }); } + + @Override + public void onDialogDismissed(int reason) throws RemoteException { + // Check the reason and invoke OnClickListener(s) if necessary + if (reason == DISMISSED_REASON_POSITIVE) { + mPositiveButtonInfo.executor.execute(() -> { + mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); + }); + } else if (reason == DISMISSED_REASON_NEGATIVE) { + mNegativeButtonInfo.executor.execute(() -> { + mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); + }); + } + } }; private BiometricPrompt(Context context, Bundle bundle, @@ -557,9 +554,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan mExecutor = executor; mAuthenticationCallback = callback; final long sessionId = crypto != null ? crypto.getOpId() : 0; - mService.authenticate(mToken, sessionId, userId, - mBiometricServiceReceiver, 0 /* flags */, mContext.getOpPackageName(), - mBundle, mDialogReceiver); + mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver, + mContext.getOpPackageName(), mBundle); } catch (RemoteException e) { Log.e(TAG, "Remote exception while authenticating", e); mExecutor.execute(() -> { diff --git a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl deleted file mode 100644 index 27d25b86b859..000000000000 --- a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.hardware.biometrics; - -/** - * Communication channel from the BiometricPrompt (SysUI) back to AuthenticationClient. - * @hide - */ -oneway interface IBiometricPromptReceiver { - void onDialogDismissed(int reason); -} diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index e17feff0160a..53a076135aaa 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -18,7 +18,6 @@ package android.hardware.biometrics; import android.os.Bundle; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; -import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricServiceReceiver; /** @@ -32,8 +31,7 @@ interface IBiometricService { // Requests authentication. The service choose the appropriate biometric to use, and show // the corresponding BiometricDialog. void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, int flags, String opPackageName, - in Bundle bundle, IBiometricPromptReceiver dialogReceiver); + IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); @@ -46,4 +44,8 @@ interface IBiometricService { // Explicitly set the active user. void setActiveUser(int userId); + + // Notify BiometricService when <Biometric>Service is ready to start the prepared client. + // Client lifecycle is still managed in <Biometric>Service. + void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId); } diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index a6e3696eeb10..22ef33e86e17 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -16,12 +16,18 @@ package android.hardware.biometrics; /** - * Communication channel from the BiometricService back to BiometricPrompt. + * Communication channel from BiometricService back to BiometricPrompt * @hide */ oneway interface IBiometricServiceReceiver { - void onAuthenticationSucceeded(long deviceId); - void onAuthenticationFailed(long deviceId); - void onError(long deviceId, int error, String message); - void onAcquired(long deviceId, int acquiredInfo, String message); + // Notify BiometricPrompt that authentication was successful + void onAuthenticationSucceeded(); + // Noties that authentication failed. + void onAuthenticationFailed(); + // Notify BiometricPrompt that an error has occurred. + void onError(int error, String message); + // Notifies that a biometric has been acquired. + void onAcquired(int acquiredInfo, String message); + // Notifies that the SystemUI dialog has been dismissed. + void onDialogDismissed(int reason); } diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl new file mode 100644 index 000000000000..180daaf97ada --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +/** + * Communication channel from + * 1) BiometricDialogImpl (SysUI) back to BiometricService + * 2) <Biometric>Service back to BiometricService + * Receives messages from the above and does some handling before forwarding to BiometricPrompt + * via IBiometricServiceReceiver. + * @hide + */ +oneway interface IBiometricServiceReceiverInternal { + // Notify BiometricService that authentication was successful. If user confirmation is required, + // the auth token must be submitted into KeyStore. + void onAuthenticationSucceeded(boolean requireConfirmation, in byte[] token); + // Notify BiometricService that an error has occurred. + void onAuthenticationFailed(int cookie, boolean requireConfirmation); + // Notify BiometricService than an error has occured. Forward to the correct receiver depending + // on the cookie. + void onError(int cookie, int error, String message); + // Notifies that a biometric has been acquired. + void onAcquired(int acquiredInfo, String message); + // Notifies that the SystemUI dialog has been dismissed. + void onDialogDismissed(int reason); + // Notifies that the user has pressed the "try again" button on SystemUI + void onTryAgainPressed(); +} diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 47df8e8f9729..a15dcec3b276 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -15,9 +15,7 @@ */ package android.hardware.face; -import android.os.Bundle; -import android.hardware.biometrics.IBiometricPromptReceiver; -import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.face.IFaceServiceReceiver; import android.hardware.face.Face; @@ -32,19 +30,24 @@ interface IFaceService { void authenticate(IBinder token, long sessionId, IFaceServiceReceiver receiver, int flags, String opPackageName); - // This method invokes the BiometricDialog. The arguments are almost the same as above, - // but should only be called from (BiometricPromptService). - void authenticateFromService(boolean requireConfirmation, IBinder token, long sessionId, - int userId, IBiometricServiceReceiver receiver, int flags, String opPackageName, - in Bundle bundle, IBiometricPromptReceiver dialogReceiver, - int callingUid, int callingPid, int callingUserId); + // This method prepares the service to start authenticating, but doesn't start authentication. + // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be + // called from BiometricService. The additional uid, pid, userId arguments should be determined + // by BiometricService. To start authentication after the clients are ready, use + // startPreparedClient(). + void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId, + int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, + int cookie, int callingUid, int callingPid, int callingUserId); + + // Starts authentication with the previously prepared client. + void startPreparedClient(int cookie); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); // Same as above, with extra arguments. void cancelAuthenticationFromService(IBinder token, String opPackageName, - int callingUid, int callingPid, int callingUserId); + int callingUid, int callingPid, int callingUserId, boolean fromClient); // Start face enrollment void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver, diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 2662a11c2dd4..dd6b29d87d67 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -15,9 +15,7 @@ */ package android.hardware.fingerprint; -import android.os.Bundle; -import android.hardware.biometrics.IBiometricPromptReceiver; -import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.fingerprint.IFingerprintClientActiveCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; @@ -35,22 +33,25 @@ interface IFingerprintService { void authenticate(IBinder token, long sessionId, int userId, IFingerprintServiceReceiver receiver, int flags, String opPackageName); - // This method invokes the BiometricDialog. The arguments are almost the same as above, except - // this is protected by the MANAGE_BIOMETRIC signature permission. This method should only be - // called from BiometricPromptService. The additional uid, pid, userId arguments should be - // determined by BiometricPromptService. - void authenticateFromService(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, int flags, String opPackageName, - in Bundle bundle, IBiometricPromptReceiver dialogReceiver, + // This method prepares the service to start authenticating, but doesn't start authentication. + // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be + // called from BiometricService. The additional uid, pid, userId arguments should be determined + // by BiometricService. To start authentication after the clients are ready, use + // startPreparedClient(). + void prepareForAuthentication(IBinder token, long sessionId, int userId, + IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId); + // Starts authentication with the previously prepared client. + void startPreparedClient(int cookie); + // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); // Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes // an additional uid, pid, userid. void cancelAuthenticationFromService(IBinder token, String opPackageName, - int callingUid, int callingPid, int callingUserId); + int callingUid, int callingPid, int callingUserId, boolean fromClient); // Start fingerprint enrollment void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 1468fe5bfca8..64314a7d8060 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -928,7 +928,9 @@ public class Binder implements IBinder { final long origWorkSource = ThreadLocalWorkSource.setUid(Binder.getCallingUid()); try { if (tracingEnabled) { - Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" + code); + final String transactionName = getTransactionName(code); + Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" + + (transactionName != null ? transactionName : code)); } res = onTransact(code, data, reply, flags); } catch (RemoteException|RuntimeException e) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 92b316914d82..abfcfaacde21 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2463,6 +2463,27 @@ public class UserManager { } /** + * Get the parent of a user profile. + * + * @param user the handle of the user profile + * + * @return the parent of the user or {@code null} if the user is not profile + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public @Nullable UserHandle getProfileParent(@NonNull UserHandle user) { + UserInfo info = getProfileParent(user.getIdentifier()); + + if (info == null) { + return null; + } + + return UserHandle.of(info.id); + } + + /** * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a * managed profile don't run, generate notifications, or consume data or battery. * <p> diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index b42f1c4df4e3..8e11d858128a 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -25,6 +25,7 @@ import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.annotation.WorkerThread; import android.app.Activity; @@ -1533,6 +1534,12 @@ public class StorageManager { return SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false); } + /** {@hide} */ + @TestApi + public static boolean hasIsolatedStorage() { + return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false); + } + /** * @deprecated disabled now that FUSE has been replaced by sdcardfs * @hide diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d7729037bfad..c297ef4f2a85 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5075,6 +5075,7 @@ public final class Settings { * @hide */ @SystemApi + @TestApi @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull ContentResolver resolver, @Nullable String tag) { @@ -8232,6 +8233,24 @@ public final class Settings { public static final String NOTIFICATION_NEW_INTERRUPTION_MODEL = "new_interruption_model"; /** + * How often to check for location access. + * @hide + */ + @SystemApi + @TestApi + public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = + "location_access_check_interval_millis"; + + /** + * Delay between granting location access and checking it. + * @hide + */ + @SystemApi + @TestApi + public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = + "location_access_check_delay_millis"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING new file mode 100644 index 000000000000..8e67ce7b2aa9 --- /dev/null +++ b/core/java/android/provider/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.provider.SettingsBackupTest" + } + ] + } + ] +} diff --git a/core/java/android/service/oemlock/IOemLockService.aidl b/core/java/android/service/oemlock/IOemLockService.aidl index d5e10d6629ca..99cffc5c5b8b 100644 --- a/core/java/android/service/oemlock/IOemLockService.aidl +++ b/core/java/android/service/oemlock/IOemLockService.aidl @@ -22,6 +22,8 @@ package android.service.oemlock; * @hide */ interface IOemLockService { + String getLockName(); + void setOemUnlockAllowedByCarrier(boolean allowed, in byte[] signature); boolean isOemUnlockAllowedByCarrier(); diff --git a/core/java/android/service/oemlock/OemLockManager.java b/core/java/android/service/oemlock/OemLockManager.java index f0d660354167..029d645dda19 100644 --- a/core/java/android/service/oemlock/OemLockManager.java +++ b/core/java/android/service/oemlock/OemLockManager.java @@ -44,6 +44,23 @@ public class OemLockManager { } /** + * Returns a vendor specific name for the OEM lock. + * + * This value is used to identify the security protocol used by locks. + * + * @return The name of the OEM lock or {@code null} if failed to get the name. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE) + @Nullable + public String getLockName() { + try { + return mService.getLockName(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets whether the carrier has allowed this device to be OEM unlocked. * * Depending on the implementation, the validity of the request might need to be proved. This diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index dace388b66f6..c4be0e504c5b 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -304,7 +304,7 @@ interface IWindowManager /** * Get the position of the nav bar */ - int getNavBarPosition(); + int getNavBarPosition(int displayId); /** * Lock the device immediately with the specified options (can be null). @@ -551,4 +551,16 @@ interface IWindowManager * @see KeyguardManager#isDeviceLocked() */ void setShouldShowIme(int displayId, boolean shouldShow); + + /** + * Reparent the top layers for a display to the requested surfaceControl. The display that + * is going to be re-parented (the displayId passed in) needs to have been created by the same + * process that is requesting the re-parent. This is to ensure clients can't just re-parent + * display content info to any SurfaceControl, as this would be a security issue. + * + * @param displayId The id of the display. + * @param surfaceControlHandle The SurfaceControl handle that the top level layers for the + * display should be re-parented to. + */ + void reparentDisplayContent(int displayId, in IBinder surfaceControlHandle); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 46f396a0b66b..ab010855b896 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -104,6 +104,8 @@ public class SurfaceControl implements Parcelable { int flags, int mask); private static native void nativeSetWindowCrop(long transactionObj, long nativeObject, int l, int t, int r, int b); + private static native void nativeSetCornerRadius(long transactionObj, long nativeObject, + float cornerRadius); private static native void nativeSetLayerStack(long transactionObj, long nativeObject, int layerStack); @@ -1006,6 +1008,18 @@ public class SurfaceControl implements Parcelable { } } + /** + * Sets the corner radius of a {@link SurfaceControl}. + * + * @param cornerRadius Corner radius in pixels. + */ + public void setCornerRadius(float cornerRadius) { + checkNotReleased(); + synchronized (SurfaceControl.class) { + sGlobalTransaction.setCornerRadius(this, cornerRadius); + } + } + public void setLayerStack(int layerStack) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -1529,6 +1543,20 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * Sets the corner radius of a {@link SurfaceControl}. + * @param sc SurfaceControl + * @param cornerRadius Corner radius in pixels. + * @return Itself. + */ + @UnsupportedAppUsage + public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) { + sc.checkNotReleased(); + nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius); + + return this; + } + @UnsupportedAppUsage public Transaction setLayerStack(SurfaceControl sc, int layerStack) { sc.checkNotReleased(); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 3c4ce8f7cfad..797d1c5f47aa 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1126,6 +1126,13 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb } }; + /** + * @hide + */ + public SurfaceControl getSurfaceControl() { + return mSurfaceControl; + } + class SurfaceControlWithBackground extends SurfaceControl { SurfaceControl mBackgroundControl; private boolean mOpaque = true; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2767505aece3..c5d03741dbba 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4159,7 +4159,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, float mAlpha = 1f; /** - * The opacity of the view as manipulated by the Fade transition. This is a hidden + * The opacity of the view as manipulated by the Fade transition. This is a * property only used by transitions, which is composited with the other alpha * values to calculate the final visual alpha value. */ @@ -15729,15 +15729,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * This property is hidden and intended only for use by the Fade transition, which - * animates it to produce a visual translucency that does not side-effect (or get - * affected by) the real alpha property. This value is composited with the other - * alpha value (and the AlphaAnimation value, when that is present) to produce - * a final visual translucency result, which is what is passed into the DisplayList. - * - * @hide + * This property is intended only for use by the Fade transition, which animates it + * to produce a visual translucency that does not side-effect (or get affected by) + * the real alpha property. This value is composited with the other alpha value + * (and the AlphaAnimation value, when that is present) to produce a final visual + * translucency result, which is what is passed into the DisplayList. */ - @UnsupportedAppUsage public void setTransitionAlpha(float alpha) { ensureTransformationInfo(); if (mTransformationInfo.mTransitionAlpha != alpha) { @@ -15760,16 +15757,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * This property is hidden and intended only for use by the Fade transition, which - * animates it to produce a visual translucency that does not side-effect (or get - * affected by) the real alpha property. This value is composited with the other - * alpha value (and the AlphaAnimation value, when that is present) to produce - * a final visual translucency result, which is what is passed into the DisplayList. - * - * @hide + * This property is intended only for use by the Fade transition, which animates + * it to produce a visual translucency that does not side-effect (or get affected + * by) the real alpha property. This value is composited with the other alpha + * value (and the AlphaAnimation value, when that is present) to produce a final + * visual translucency result, which is what is passed into the DisplayList. */ @ViewDebug.ExportedProperty(category = "drawing") - @UnsupportedAppUsage public float getTransitionAlpha() { return mTransformationInfo != null ? mTransformationInfo.mTransitionAlpha : 1; } @@ -16288,9 +16282,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - /** @hide */ - @UnsupportedAppUsage - public void setAnimationMatrix(Matrix matrix) { + /** + * Changes the transformation matrix on the view. This is used in animation frameworks, + * such as {@link android.transition.Transition}. When the animation finishes, the matrix + * should be cleared by calling this method with <code>null</code> as the matrix parameter. + * Application developers should use transformation methods like {@link #setRotation(float)}, + * {@link #setScaleX(float)}, {@link #setScaleX(float)}, {@link #setTranslationX(float)}} + * and {@link #setTranslationY(float)} (float)}} instead. + * + * @param matrix The matrix, null indicates that the matrix should be cleared. + */ + public void setAnimationMatrix(@Nullable Matrix matrix) { invalidateViewProperty(true, false); mRenderNode.setAnimationMatrix(matrix); invalidateViewProperty(false, true); @@ -21460,7 +21462,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * previous ones * {@hide} */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; @@ -21537,7 +21539,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #setLeft(int), #setRight(int), #setTop(int), #setBottom(int) */ - public void setLeftTopRightBottom(int left, int top, int right, int bottom) { + public final void setLeftTopRightBottom(int left, int top, int right, int bottom) { setFrame(left, top, right, bottom); } @@ -23545,6 +23547,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Get the identifier used for this view by the drawing system. + * + * @see RenderNode#getUniqueId() + * @return A long that uniquely identifies this view's drawing component + */ + public long getUniqueDrawingId() { + return mRenderNode.getUniqueId(); + } + + /** * Returns this view's tag. * * @return the Object stored in this view as a tag, or {@code null} if not diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 1e91aa87bfb7..741510e67130 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7042,10 +7042,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * suppression is disabled with a later call to suppressLayout(false). * When layout suppression is disabled, a requestLayout() call is sent * if layout() was attempted while layout was being suppressed. - * - * @hide */ - @UnsupportedAppUsage public void suppressLayout(boolean suppress) { mSuppressLayout = suppress; if (!suppress) { @@ -7061,8 +7058,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * suppressed, due to an earlier call to {@link #suppressLayout(boolean)}. * * @return true if layout calls are currently suppressed, false otherwise. - * - * @hide */ public boolean isLayoutSuppressed() { return mSuppressLayout; diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 260e93890e3e..16bafe2c4f11 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -44,6 +44,7 @@ public interface WindowManagerPolicyConstants { int PRESENCE_EXTERNAL = 1 << 1; // Navigation bar position values + int NAV_BAR_INVALID = -1; int NAV_BAR_LEFT = 1 << 0; int NAV_BAR_RIGHT = 1 << 1; int NAV_BAR_BOTTOM = 1 << 2; diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 9ccd3211768d..f78180276373 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Rect; +import android.os.Build; import android.os.Bundle; import android.os.Trace; import android.util.AttributeSet; @@ -107,7 +108,7 @@ public class GridView extends AbsListView { */ public static final int AUTO_FIT = -1; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521080) private int mNumColumns = AUTO_FIT; @UnsupportedAppUsage @@ -117,7 +118,7 @@ public class GridView extends AbsListView { @UnsupportedAppUsage private int mVerticalSpacing = 0; private int mStretchMode = STRETCH_COLUMN_WIDTH; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521079) private int mColumnWidth; @UnsupportedAppUsage private int mRequestedColumnWidth; diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index 34e8ed406200..875d7c9ee7a6 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -52,17 +52,23 @@ public class BinderCallsStats implements BinderInternal.Observer { public static final boolean ENABLED_DEFAULT = false; public static final boolean DETAILED_TRACKING_DEFAULT = true; public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100; + public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000; + + private static class OverflowBinder extends Binder {} private static final String TAG = "BinderCallsStats"; private static final int CALL_SESSIONS_POOL_SIZE = 100; private static final int MAX_EXCEPTION_COUNT_SIZE = 50; private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow"; + private static final Class<? extends Binder> OVERFLOW_BINDER = OverflowBinder.class; + private static final int OVERFLOW_TRANSACTION_CODE = -1; // Whether to collect all the data: cpu + exceptions + reply/request sizes. private boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT; // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out // of 100 requests. private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT; + private int mMaxBinderCallStatsCount = MAX_BINDER_CALL_STATS_COUNT_DEFAULT; @GuardedBy("mLock") private final SparseArray<UidEntry> mUidEntries = new SparseArray<>(); @GuardedBy("mLock") @@ -71,6 +77,7 @@ public class BinderCallsStats implements BinderInternal.Observer { private final Object mLock = new Object(); private final Random mRandom; private long mStartTime = System.currentTimeMillis(); + private long mCallStatsCount = 0; private CachedDeviceState.Readonly mDeviceState; @@ -158,7 +165,13 @@ public class BinderCallsStats implements BinderInternal.Observer { final CallStat callStat = uidEntry.getOrCreate( callingUid, s.binderClass, s.transactionCode, - mDeviceState.isScreenInteractive()); + mDeviceState.isScreenInteractive(), + mCallStatsCount >= mMaxBinderCallStatsCount); + final boolean isNewCallStat = callStat.callCount == 0; + if (isNewCallStat) { + mCallStatsCount++; + } + callStat.callCount++; callStat.recordedCallCount++; callStat.cpuTimeMicros += duration; @@ -444,6 +457,24 @@ public class BinderCallsStats implements BinderInternal.Observer { } } + /** + * Sets the maximum number of items to track. + */ + public void setMaxBinderCallStats(int maxKeys) { + if (maxKeys <= 0) { + Slog.w(TAG, "Ignored invalid max value (value must be positive): " + + maxKeys); + return; + } + + synchronized (mLock) { + if (maxKeys != mMaxBinderCallStatsCount) { + mMaxBinderCallStatsCount = maxKeys; + reset(); + } + } + } + public void setSamplingInterval(int samplingInterval) { if (samplingInterval <= 0) { Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): " @@ -461,6 +492,7 @@ public class BinderCallsStats implements BinderInternal.Observer { public void reset() { synchronized (mLock) { + mCallStatsCount = 0; mUidEntries.clear(); mExceptionCounts.clear(); mStartTime = System.currentTimeMillis(); @@ -595,10 +627,21 @@ public class BinderCallsStats implements BinderInternal.Observer { } CallStat getOrCreate(int callingUid, Class<? extends Binder> binderClass, - int transactionCode, boolean screenInteractive) { + int transactionCode, boolean screenInteractive, boolean maxCallStatsReached) { CallStat mapCallStat = get(callingUid, binderClass, transactionCode, screenInteractive); - // Only create CallStat if it's a new entry, otherwise update existing instance + // Only create CallStat if it's a new entry, otherwise update existing instance. if (mapCallStat == null) { + if (maxCallStatsReached) { + mapCallStat = get(callingUid, OVERFLOW_BINDER, OVERFLOW_TRANSACTION_CODE, + screenInteractive); + if (mapCallStat != null) { + return mapCallStat; + } + + binderClass = OVERFLOW_BINDER; + transactionCode = OVERFLOW_TRANSACTION_CODE; + } + mapCallStat = new CallStat(callingUid, binderClass, transactionCode, screenInteractive); CallStatKey key = new CallStatKey(); diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 031377ebe27b..488b9912ee49 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -41,6 +41,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Color; diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java new file mode 100644 index 000000000000..100c6ee6763b --- /dev/null +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.internal.policy; + +import com.android.internal.R; + +import android.content.res.Resources; + +/** + * Utility functions for screen decorations used by both window manager and System UI. + */ +public class ScreenDecorationsUtils { + + /** + * Corner radius that should be used on windows in order to cover the display. + * These values are expressed in pixels because they should not respect display or font + * scaling, this means that we don't have to reload them on config changes. + */ + public static float getWindowCornerRadius(Resources resources) { + // Radius that should be used in case top or bottom aren't defined. + float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius); + + float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top); + if (topRadius == 0) { + topRadius = defaultRadius; + } + float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom); + if (bottomRadius == 0) { + bottomRadius = defaultRadius; + } + + // Always use the smallest radius to make sure the rounded corners will + // completely cover the display. + return Math.min(topRadius, bottomRadius); + } +} diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 604537ffee03..600b1b324257 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -18,7 +18,7 @@ package com.android.internal.statusbar; import android.content.ComponentName; import android.graphics.Rect; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -141,7 +141,7 @@ oneway interface IStatusBar void showShutdownUi(boolean isReboot, String reason); // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type, + void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(); @@ -151,4 +151,6 @@ oneway interface IStatusBar void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); + // Used to request the "try again" button for authentications which requireConfirmation=true + void showBiometricTryAgain(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index b7ffb5768a31..bf82dc610ad4 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -21,7 +21,7 @@ import android.content.ComponentName; import android.graphics.Rect; import android.os.Bundle; import android.service.notification.StatusBarNotification; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; @@ -92,7 +92,7 @@ interface IStatusBarService void showPinningEscapeToast(); // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type, + void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(); @@ -102,4 +102,6 @@ interface IStatusBarService void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); + // Used to request the "try again" button for authentications which requireConfirmation=true + void showBiometricTryAgain(); } diff --git a/core/java/com/android/internal/util/BitUtils.java b/core/java/com/android/internal/util/BitUtils.java index 17d5a2e36974..61581458f98a 100644 --- a/core/java/com/android/internal/util/BitUtils.java +++ b/core/java/com/android/internal/util/BitUtils.java @@ -28,7 +28,7 @@ import java.util.function.IntFunction; /** * A utility class for handling unsigned integers and unsigned arithmetics, as well as syntactic - * sugar methods for ByteBuffer. Useful for networking and packet manipulations. + * sugar methods for {@link ByteBuffer}. Useful for networking and packet manipulations. * {@hide} */ public final class BitUtils { @@ -151,4 +151,11 @@ public final class BitUtils { TextUtils.wrap(builder, "[", "]"); return builder.toString(); } + + /** + * Converts long to byte array + */ + public static byte[] toBytes(long l) { + return ByteBuffer.allocate(8).putLong(l).array(); + } } diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 083c0c9736f9..151901be7b5b 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; @@ -190,6 +191,13 @@ public class CollectionUtils { } /** + * Returns the size of the given map, or 0 if null + */ + public static int size(@Nullable Map<?, ?> cur) { + return cur != null ? cur.size() : 0; + } + + /** * Returns whether the given collection {@link Collection#isEmpty is empty} or {@code null} */ public static boolean isEmpty(@Nullable Collection<?> cur) { diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 849585004bc3..b97a9fa8d1cc 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -934,7 +934,7 @@ public class SystemConfig { // If the storage model feature flag is disabled, we need to fiddle // around with permission definitions to return us to pre-Q behavior. // STOPSHIP(b/112545973): remove once feature enabled by default - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { if (newPermissions.contains(android.Manifest.permission.READ_MEDIA_AUDIO) || newPermissions.contains(android.Manifest.permission.READ_MEDIA_VIDEO) || newPermissions.contains(android.Manifest.permission.READ_MEDIA_IMAGES)) { diff --git a/core/jni/android_text_AndroidCharacter.cpp b/core/jni/android_text_AndroidCharacter.cpp index 8885aac49064..c6ea4e10f63e 100644 --- a/core/jni/android_text_AndroidCharacter.cpp +++ b/core/jni/android_text_AndroidCharacter.cpp @@ -25,9 +25,10 @@ #include "unicode/uchar.h" #define PROPERTY_UNDEFINED (-1) +#define JAVA_LANG_CHARACTER_MAX_DIRECTIONALITY 18 // ICU => JDK mapping -static int directionality_map[U_CHAR_DIRECTION_COUNT] = { +static int directionality_map[JAVA_LANG_CHARACTER_MAX_DIRECTIONALITY + 1] = { 0, // U_LEFT_TO_RIGHT (0) => DIRECTIONALITY_LEFT_TO_RIGHT (0) 1, // U_RIGHT_TO_LEFT (1) => DIRECTIONALITY_RIGHT_TO_LEFT (1) 3, // U_EUROPEAN_NUMBER (2) => DIRECTIONALITY_EUROPEAN_NUMBER (3) @@ -75,7 +76,8 @@ static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, int c = 0x00010000 + ((src[i] - 0xD800) << 10) + (src[i + 1] & 0x3FF); int dir = u_charDirection(c); - if (dir < 0 || dir >= U_CHAR_DIRECTION_COUNT) + if (dir < 0 || dir > JAVA_LANG_CHARACTER_MAX_DIRECTIONALITY + || u_charType(c) == U_UNASSIGNED) dir = PROPERTY_UNDEFINED; else dir = directionality_map[dir]; @@ -85,7 +87,8 @@ static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, } else { int c = src[i]; int dir = u_charDirection(c); - if (dir < 0 || dir >= U_CHAR_DIRECTION_COUNT) + if (dir < 0 || dir > JAVA_LANG_CHARACTER_MAX_DIRECTIONALITY + || u_charType(c) == U_UNASSIGNED) dest[i] = PROPERTY_UNDEFINED; else dest[i] = directionality_map[dir]; @@ -96,7 +99,7 @@ static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, static jint getEastAsianWidth(JNIEnv* env, jobject obj, jchar input) { int width = u_getIntPropertyValue(input, UCHAR_EAST_ASIAN_WIDTH); - if (width < 0 || width >= U_EA_COUNT) + if (width < 0 || width > u_getIntPropertyMaxValue(UCHAR_EAST_ASIAN_WIDTH)) width = PROPERTY_UNDEFINED; return width; @@ -121,6 +124,7 @@ static void getEastAsianWidths(JNIEnv* env, jobject obj, jcharArray srcArray, return; } + int maxWidth = u_getIntPropertyMaxValue(UCHAR_EAST_ASIAN_WIDTH); for (int i = 0; i < count; i++) { const int srci = start + i; if (src[srci] >= 0xD800 && src[srci] <= 0xDBFF && @@ -129,7 +133,7 @@ static void getEastAsianWidths(JNIEnv* env, jobject obj, jcharArray srcArray, int c = 0x00010000 + ((src[srci] - 0xD800) << 10) + (src[srci + 1] & 0x3FF); int width = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH); - if (width < 0 || width >= U_EA_COUNT) + if (width < 0 || width > maxWidth) width = PROPERTY_UNDEFINED; dest[i++] = width; @@ -137,7 +141,7 @@ static void getEastAsianWidths(JNIEnv* env, jobject obj, jcharArray srcArray, } else { int c = src[srci]; int width = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH); - if (width < 0 || width >= U_EA_COUNT) + if (width < 0 || width > maxWidth) width = PROPERTY_UNDEFINED; dest[i] = width; diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ec9c8606e8df..ea6e0178bd9c 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -377,6 +377,14 @@ static void nativeSetWindowCrop(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setCrop_legacy(ctrl, crop); } +static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jfloat cornerRadius) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); + transaction->setCornerRadius(ctrl, cornerRadius); +} + static void nativeSetLayerStack(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint layerStack) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -883,6 +891,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetFlags }, {"nativeSetWindowCrop", "(JJIIII)V", (void*)nativeSetWindowCrop }, + {"nativeSetCornerRadius", "(JJF)V", + (void*)nativeSetCornerRadius }, {"nativeSetLayerStack", "(JJI)V", (void*)nativeSetLayerStack }, {"nativeGetBuiltInDisplay", "(I)Landroid/os/IBinder;", diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 6e661e1ce5b2..b465fb4f7eba 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -234,6 +234,14 @@ message SecureSettingsProto { } optional Location location = 31; + message LocationAccessCheck { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + optional SettingProto interval_millis = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto delay_millis = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + optional LocationAccessCheck location_access_check = 73; + message LockScreen { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -515,5 +523,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 73; + // Next tag = 74; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7ca8f24bc8c7..b4d5f67e945e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -627,6 +627,13 @@ <!-- ====================================================================== --> <eat-comment /> + <!-- Grouping for platform runtime permissions is not accessible to apps + @hide + @SystemApi + --> + <permission-group android:name="android.permission-group.UNDEFINED" + android:priority="100" /> + <!-- ====================================================================== --> <!-- Permissions for accessing user's contacts including personal profile --> <!-- ====================================================================== --> @@ -645,6 +652,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_CONTACTS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readContacts" android:description="@string/permdesc_readContacts" android:protectionLevel="dangerous" @@ -654,6 +662,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.WRITE_CONTACTS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_writeContacts" android:description="@string/permdesc_writeContacts" android:protectionLevel="dangerous" /> @@ -675,6 +684,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_CALENDAR" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readCalendar" android:description="@string/permdesc_readCalendar" android:protectionLevel="dangerous" @@ -684,6 +694,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.WRITE_CALENDAR" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_writeCalendar" android:description="@string/permdesc_writeCalendar" android:protectionLevel="dangerous" /> @@ -705,6 +716,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.SEND_SMS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_sendSms" android:description="@string/permdesc_sendSms" android:permissionFlags="costsMoney" @@ -714,6 +726,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.RECEIVE_SMS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_receiveSms" android:description="@string/permdesc_receiveSms" android:protectionLevel="dangerous" @@ -723,6 +736,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_SMS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readSms" android:description="@string/permdesc_readSms" android:protectionLevel="dangerous" @@ -732,6 +746,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.RECEIVE_WAP_PUSH" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_receiveWapPush" android:description="@string/permdesc_receiveWapPush" android:protectionLevel="dangerous" @@ -741,6 +756,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.RECEIVE_MMS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_receiveMms" android:description="@string/permdesc_receiveMms" android:protectionLevel="dangerous" @@ -759,6 +775,7 @@ <p>Protection level: dangerous @hide Pending API council approval --> <permission android:name="android.permission.READ_CELL_BROADCASTS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readCellBroadcasts" android:description="@string/permdesc_readCellBroadcasts" android:protectionLevel="dangerous" @@ -801,6 +818,7 @@ @deprecated replaced by new strongly-typed permission groups in Q. --> <permission android:name="android.permission.READ_EXTERNAL_STORAGE" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_sdcardRead" android:description="@string/permdesc_sdcardRead" android:protectionLevel="dangerous" @@ -822,6 +840,7 @@ @deprecated replaced by new strongly-typed permission groups in Q. --> <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_sdcardWrite" android:description="@string/permdesc_sdcardWrite" android:protectionLevel="dangerous" @@ -838,6 +857,7 @@ <!-- Allows an application to read the user's shared audio collection. --> <permission android:name="android.permission.READ_MEDIA_AUDIO" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_audioRead" android:description="@string/permdesc_audioRead" android:protectionLevel="dangerous" @@ -854,6 +874,7 @@ <!-- Allows an application to read the user's shared images collection. --> <permission android:name="android.permission.READ_MEDIA_IMAGES" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_imagesRead" android:description="@string/permdesc_imagesRead" android:protectionLevel="dangerous" @@ -861,6 +882,7 @@ <!-- Allows an application to read the user's shared video collection. --> <permission android:name="android.permission.READ_MEDIA_VIDEO" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_videoRead" android:description="@string/permdesc_videoRead" android:protectionLevel="dangerous" @@ -869,6 +891,7 @@ <!-- Allows an application to access any geographic locations persisted in the user's shared collection. --> <permission android:name="android.permission.ACCESS_MEDIA_LOCATION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_mediaLocation" android:description="@string/permdesc_mediaLocation" android:protectionLevel="dangerous" @@ -900,6 +923,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACCESS_FINE_LOCATION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_accessFineLocation" android:description="@string/permdesc_accessFineLocation" android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION" @@ -911,6 +935,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACCESS_COARSE_LOCATION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_accessCoarseLocation" android:description="@string/permdesc_accessCoarseLocation" android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION" @@ -924,6 +949,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_accessBackgroundLocation" android:description="@string/permdesc_accessBackgroundLocation" android:protectionLevel="dangerous|instant" @@ -965,6 +991,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_CALL_LOG" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readCallLog" android:description="@string/permdesc_readCallLog" android:protectionLevel="dangerous" @@ -984,6 +1011,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.WRITE_CALL_LOG" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_writeCallLog" android:description="@string/permdesc_writeCallLog" android:protectionLevel="dangerous" /> @@ -994,6 +1022,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.PROCESS_OUTGOING_CALLS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_processOutgoingCalls" android:description="@string/permdesc_processOutgoingCalls" android:protectionLevel="dangerous" @@ -1026,6 +1055,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.READ_PHONE_STATE" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readPhoneState" android:description="@string/permdesc_readPhoneState" android:protectionLevel="dangerous" @@ -1035,6 +1065,7 @@ granted by {@link #READ_PHONE_STATE} but is exposed to instant applications. <p>Protection level: dangerous--> <permission android:name="android.permission.READ_PHONE_NUMBERS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_readPhoneNumbers" android:description="@string/permdesc_readPhoneNumbers" android:protectionLevel="dangerous|instant" @@ -1045,6 +1076,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.CALL_PHONE" + android:permissionGroup="android.permission-group.UNDEFINED" android:permissionFlags="costsMoney" android:label="@string/permlab_callPhone" android:description="@string/permdesc_callPhone" @@ -1054,6 +1086,7 @@ <p>Protection level: dangerous --> <permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_addVoicemail" android:description="@string/permdesc_addVoicemail" android:protectionLevel="dangerous" /> @@ -1062,6 +1095,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.USE_SIP" + android:permissionGroup="android.permission-group.UNDEFINED" android:description="@string/permdesc_use_sip" android:label="@string/permlab_use_sip" android:protectionLevel="dangerous"/> @@ -1070,6 +1104,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ANSWER_PHONE_CALLS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_answerPhoneCalls" android:description="@string/permdesc_answerPhoneCalls" android:protectionLevel="dangerous|runtime" /> @@ -1097,6 +1132,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACCEPT_HANDOVER" + android:permissionGroup="android.permission-group.UNDEFINED" android.label="@string/permlab_acceptHandover" android:description="@string/permdesc_acceptHandovers" android:protectionLevel="dangerous" /> @@ -1120,6 +1156,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.RECORD_AUDIO" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_recordAudio" android:description="@string/permdesc_recordAudio" android:protectionLevel="dangerous|instant" @@ -1142,6 +1179,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.ACTIVITY_RECOGNITION" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_activityRecognition" android:description="@string/permdesc_activityRecognition" android:protectionLevel="dangerous|instant" @@ -1191,6 +1229,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.CAMERA" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_camera" android:description="@string/permdesc_camera" android:protectionLevel="dangerous|instant" @@ -1215,6 +1254,7 @@ measure what is happening inside his/her body, such as heart rate. <p>Protection level: dangerous --> <permission android:name="android.permission.BODY_SENSORS" + android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_bodySensors" android:description="@string/permdesc_bodySensors" android:protectionLevel="dangerous" @@ -1702,6 +1742,7 @@ <p>Protection level: dangerous --> <permission android:name="android.permission.GET_ACCOUNTS" + android:permissionGroup="android.permission-group.UNDEFINED" android:protectionLevel="dangerous" android:description="@string/permdesc_getAccounts" android:label="@string/permlab_getAccounts" @@ -4229,6 +4270,12 @@ <permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS" android:protectionLevel="signature|appop" /> + <!-- Required for apps targeting {@link android.os.Build.VERSION_CODES#P} that want to use + {@link android.app.Notification.Builder#setFullScreenIntent notification full screen + intents}. --> + <permission android:name="android.permission.USE_FULL_SCREEN_INTENT" + android:protectionLevel="normal" /> + <!-- @SystemApi Allows requesting the framework broadcast the {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent. @hide --> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 433ae399c88c..4bf1ad6651ee 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -126,7 +126,6 @@ android:visibility="gone" android:contentDescription="@string/notification_alerted_content_description" android:src="@drawable/ic_notifications_alerted" - android:tint="@color/notification_secondary_text_color_light" /> <ImageView android:id="@+id/profile_badge" diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index f7b9961c39e8..05a156b69371 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -430,7 +430,7 @@ <dimen name="notification_badge_size">12dp</dimen> <!-- Size of the alerted icon for notifications --> - <dimen name="notification_alerted_size">18dp</dimen> + <dimen name="notification_alerted_size">12dp</dimen> <!-- Keyguard dimensions --> <!-- TEMP --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index bd6d97622a4d..a33f6b2fbf9c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1444,9 +1444,14 @@ <!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] --> <string name="biometric_dialog_default_title">Application <xliff:g id="app" example="Gmail">%s</xliff:g> wants to authenticate.</string> - <!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] --> <string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string> + <!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] --> + <string name="biometric_error_user_canceled">Authentication canceled</string> + <!-- Message shown by the biometric dialog when biometric is not recognized --> + <string name="biometric_not_recognized">Not recognized</string> + <!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] --> + <string name="biometric_error_canceled">Authentication canceled</string> <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized --> <string name="fingerprint_acquired_partial">Partial fingerprint detected. Please try again.</string> @@ -1462,8 +1467,6 @@ <string-array name="fingerprint_acquired_vendor"> </string-array> - <!-- Message shown by the biometric dialog when biometric is not recognized --> - <string name="biometric_not_recognized">Not recognized</string> <!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] --> <string name="fingerprint_authenticated">Fingerprint authenticated</string> <!-- Accessibility message announced when a face has been authenticated [CHAR LIMIT=NONE] --> @@ -1585,14 +1588,14 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_readSyncStats">Allows an app to read the sync stats for an account, including the history of sync events and how much data is synced. </string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> <string name="permlab_sdcardRead">read the contents of your shared storage</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> <string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] --> <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] --> <string name="permdesc_sdcardWrite">Allows the app to write the contents of your shared storage.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> @@ -2983,7 +2986,7 @@ <!-- Label for item in the text selection menu to translate selected text with a translation app. Should be a verb. [CHAR LIMIT=30] --> <string name="translate">Translate</string> - + <!-- Accessibility description for an item in the text selection menu to translate selected text with a translation app. [CHAR LIMIT=NONE] --> <string name="translate_desc">Translate selected text</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e251e27a5496..783f1f355e87 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2406,7 +2406,9 @@ <!-- Biometric messages --> <java-symbol type="string" name="biometric_dialog_default_title" /> <java-symbol type="string" name="biometric_error_hw_unavailable" /> + <java-symbol type="string" name="biometric_error_user_canceled" /> <java-symbol type="string" name="biometric_not_recognized" /> + <java-symbol type="string" name="biometric_error_canceled" /> <!-- Fingerprint messages --> <java-symbol type="string" name="fingerprint_error_unable_to_process" /> @@ -3499,4 +3501,8 @@ <java-symbol type="string" name="config_defaultAssistantComponentName" /> <java-symbol type="id" name="transition_overlay_view_tag" /> + + <java-symbol type="dimen" name="rounded_corner_radius" /> + <java-symbol type="dimen" name="rounded_corner_radius_top" /> + <java-symbol type="dimen" name="rounded_corner_radius_bottom" /> </resources> diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 305d2af2122d..a8f9e8a5891b 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -664,7 +664,9 @@ public class SettingsBackupTest { Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE, Settings.Secure.FLASHLIGHT_AVAILABLE, Settings.Secure.FLASHLIGHT_ENABLED, - Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED); + Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED, + Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, + Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS); @Test public void systemSettingsBackedUpOrBlacklisted() { diff --git a/core/tests/coretests/src/android/text/AndroidCharacterTest.java b/core/tests/coretests/src/android/text/AndroidCharacterTest.java new file mode 100644 index 000000000000..0c7e730e78e4 --- /dev/null +++ b/core/tests/coretests/src/android/text/AndroidCharacterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS,d + * WITHOUT WARRANTIES 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.text; + +import static org.junit.Assert.assertArrayEquals; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; + +import org.junit.Test; + +@Presubmit +@SmallTest +public class AndroidCharacterTest { + + @Test + public void testGetDirectionalities_nonSupplementaryCharacters() { + int size = Character.MAX_VALUE + 1 + - (Character.MAX_SURROGATE - Character.MIN_SURROGATE + 1); + char[] chars = new char[size]; + byte[] java_lang_results = new byte[size]; + int index = 0; + for (int cp = 0; cp <= Character.MAX_VALUE; cp++) { + if (cp < Character.MIN_SURROGATE || cp > Character.MAX_SURROGATE) { + chars[index] = (char) cp; + java_lang_results[index] = Character.getDirectionality(cp); + index++; + } + } + + byte[] android_text_results = new byte[size]; + AndroidCharacter.getDirectionalities(chars, android_text_results, index); + assertArrayEquals(java_lang_results, android_text_results); + } + + @Test + public void testGetDirectionalities_supplementaryCharacters() { + int maxNumberOfChars = Character.MAX_CODE_POINT - Character.MIN_SUPPLEMENTARY_CODE_POINT + + 1; + int size = maxNumberOfChars * 2; + char[] chars = new char[size]; + byte[] java_lang_results = new byte[size]; + int index = 0; + for (int cp = Character.MIN_SUPPLEMENTARY_CODE_POINT; cp <= Character.MAX_CODE_POINT; + cp++) { + chars[index] = Character.highSurrogate(cp); + chars[index + 1] = Character.lowSurrogate(cp); + java_lang_results[index] = java_lang_results[index + 1] = Character + .getDirectionality(cp); + index += 2; + } + + byte[] android_text_results = new byte[size]; + AndroidCharacter.getDirectionalities(chars, android_text_results, index); + assertArrayEquals(java_lang_results, android_text_results); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java index 30309cf64e96..8691e73f82fb 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -557,6 +557,62 @@ public class BinderCallsStatsTest { assertEquals(0, bcs.getExceptionCounts().size()); } + @Test + public void testOverflow_sameEntry() { + TestBinderCallsStats bcs = new TestBinderCallsStats(); + bcs.setDetailedTracking(true); + bcs.setSamplingInterval(1); + bcs.setMaxBinderCallStats(2); + + Binder binder = new Binder(); + CallSession callSession = bcs.callStarted(binder, 1); + bcs.time += 10; + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + callSession = bcs.callStarted(binder, 1); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + callSession = bcs.callStarted(binder, 1); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + BinderCallsStats.UidEntry uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID); + List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList()); + assertEquals(1, callStatsList.size()); + BinderCallsStats.CallStat callStats = callStatsList.get(0); + assertEquals(3, callStats.callCount); + } + + @Test + public void testOverflow_overflowEntry() { + TestBinderCallsStats bcs = new TestBinderCallsStats(); + bcs.setDetailedTracking(true); + bcs.setSamplingInterval(1); + bcs.setMaxBinderCallStats(1); + + Binder binder = new Binder(); + CallSession callSession = bcs.callStarted(binder, 1); + bcs.time += 10; + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + callSession = bcs.callStarted(binder, 2); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + + List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats(); + assertEquals(2, callStatsList.size()); + BinderCallsStats.ExportedCallStat callStats = callStatsList.get(0); + assertEquals(1, callStats.callCount); + assertEquals("1", callStats.methodName); + assertEquals("android.os.Binder", callStats.className); + assertEquals(CALLING_UID, callStats.callingUid); + + callStats = callStatsList.get(1); + assertEquals(1, callStats.callCount); + assertEquals("-1", callStats.methodName); + assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder", + callStats.className); + assertEquals(CALLING_UID, callStats.callingUid); + } + class TestBinderCallsStats extends BinderCallsStats { public int callingUid = CALLING_UID; public int workSourceUid = WORKSOURCE_UID; diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 3933f508fe84..dbe6e8f4eebb 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -163,9 +163,6 @@ public class Typeface { private int[] mSupportedAxes; private static final int[] EMPTY_AXES = {}; - // The underlying font families. - private final FontFamily[] mFamilies; - @UnsupportedAppUsage private static void setDefault(Typeface t) { sDefaultTypeface = t; @@ -732,21 +729,17 @@ public class Typeface { public Typeface build() { final int userFallbackSize = mFamilies.size(); final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName); - final FontFamily[] fullFamilies = new FontFamily[fallback.length + userFallbackSize]; final long[] ptrArray = new long[fallback.length + userFallbackSize]; for (int i = 0; i < userFallbackSize; ++i) { ptrArray[i] = mFamilies.get(i).getNativePtr(); - fullFamilies[i] = mFamilies.get(i); } for (int i = 0; i < fallback.length; ++i) { ptrArray[i + userFallbackSize] = fallback[i].getNativePtr(); - fullFamilies[i + userFallbackSize] = fallback[i]; } final int weight = mStyle == null ? 400 : mStyle.getWeight(); final int italic = (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1; - - return new Typeface(nativeCreateFromArray(ptrArray, weight, italic), fullFamilies); + return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); } } @@ -811,7 +804,7 @@ public class Typeface { } } - typeface = new Typeface(nativeCreateFromTypeface(ni, style), family.mFamilies); + typeface = new Typeface(nativeCreateFromTypeface(ni, style)); styles.put(style, typeface); } return typeface; @@ -879,8 +872,7 @@ public class Typeface { } typeface = new Typeface( - nativeCreateFromTypefaceWithExactStyle( - base.native_instance, weight, italic), base.mFamilies); + nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic)); innerCache.put(key, typeface); } return typeface; @@ -890,8 +882,7 @@ public class Typeface { public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family, @NonNull List<FontVariationAxis> axes) { final Typeface base = family == null ? Typeface.DEFAULT : family; - return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes), - base.mFamilies); + return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes)); } /** @@ -997,7 +988,7 @@ public class Typeface { ptrArray[i] = families[i].getNativePtr(); } return new Typeface(nativeCreateFromArray(ptrArray, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE), families); + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); } /** @@ -1045,19 +1036,6 @@ public class Typeface { } native_instance = ni; - mFamilies = new FontFamily[0]; - sRegistry.registerNativeAllocation(this, native_instance); - mStyle = nativeGetStyle(ni); - mWeight = nativeGetWeight(ni); - } - - private Typeface(long ni, @NonNull FontFamily[] families) { - if (ni == 0) { - throw new IllegalStateException("native typeface cannot be made"); - } - - native_instance = ni; - mFamilies = families; sRegistry.registerNativeAllocation(this, native_instance); mStyle = nativeGetStyle(ni); mWeight = nativeGetWeight(ni); @@ -1084,8 +1062,7 @@ public class Typeface { final Typeface base = systemFontMap.get(alias.getToName()); final int weight = alias.getWeight(); final Typeface newFace = weight == 400 ? base : - new Typeface(nativeCreateWeightAlias(base.native_instance, weight), - base.mFamilies); + new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); systemFontMap.put(alias.getName(), newFace); } } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 5f27faed08f3..4a5b61a2d6cd 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -179,7 +179,6 @@ cc_defaults { "renderthread/CanvasContext.cpp", "renderthread/DrawFrameTask.cpp", "renderthread/EglManager.cpp", - "renderthread/ReliableSurface.cpp", "renderthread/VulkanManager.cpp", "renderthread/RenderProxy.cpp", "renderthread/RenderTask.cpp", diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 07979a22c988..142bca95e598 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -155,7 +155,7 @@ void SkiaOpenGLPipeline::onStop() { } } -bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, +bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, ColorMode colorMode) { if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 479910697871..4ab3541d447b 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -42,7 +42,7 @@ public: bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, + bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior, renderthread::ColorMode colorMode) override; void onStop() override; bool isSurfaceReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index e50ad1cd8c44..a494e490aea1 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -115,7 +115,7 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} -bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, +bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, ColorMode colorMode) { if (mVkSurface) { mVkManager.destroySurface(mVkSurface); diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 02874c7d2c69..14c0d69dba33 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -38,7 +38,7 @@ public: bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, + bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior, renderthread::ColorMode colorMode) override; void onStop() override; bool isSurfaceReady() override; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 182233fb3715..f1a522ecd588 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -142,12 +142,7 @@ void CanvasContext::destroy() { void CanvasContext::setSurface(sp<Surface>&& surface) { ATRACE_CALL(); - if (surface) { - mNativeSurface = new ReliableSurface{std::move(surface)}; - mNativeSurface->setDequeueTimeout(500_ms); - } else { - mNativeSurface = nullptr; - } + mNativeSurface = std::move(surface); ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode); @@ -290,7 +285,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy info.damageAccumulator = &mDamageAccumulator; info.layerUpdateQueue = &mLayerUpdateQueue; - info.out.canDrawThisFrame = true; mAnimationContext->startFrame(info.mode); mRenderPipeline->onPrepareTree(); @@ -310,7 +304,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; - if (CC_UNLIKELY(!hasSurface())) { + if (CC_UNLIKELY(!mNativeSurface.get())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; @@ -329,6 +323,27 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy // the deadline for RT animations info.out.canDrawThisFrame = false; } + /* This logic exists to try and recover from a display latch miss, which essentially + * results in the bufferqueue being double-buffered instead of triple-buffered. + * SurfaceFlinger itself now tries to handle & recover from this situation, so this + * logic should no longer be necessary. As it's occasionally triggering when + * undesired disable it. + * TODO: Remove this entirely if the results are solid. + else if (vsyncDelta >= mRenderThread.timeLord().frameIntervalNanos() * 3 || + (latestVsync - mLastDropVsync) < 500_ms) { + // It's been several frame intervals, assume the buffer queue is fine + // or the last drop was too recent + info.out.canDrawThisFrame = true; + } else { + info.out.canDrawThisFrame = !isSwapChainStuffed(); + if (!info.out.canDrawThisFrame) { + // dropping frame + mLastDropVsync = mRenderThread.timeLord().latestVsync(); + } + } + */ + } else { + info.out.canDrawThisFrame = true; } // TODO: Do we need to abort out if the backdrop is added but not ready? Should that even @@ -339,19 +354,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy if (!info.out.canDrawThisFrame) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); - return; - } - - int err = mNativeSurface->reserveNext(); - if (err != OK) { - mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); - info.out.canDrawThisFrame = false; - ALOGW("reserveNext failed, error = %d", err); - if (err != TIMED_OUT) { - // A timed out surface can still recover, but assume others are permanently dead. - setSurface(nullptr); - } - return; } bool postedFrameCallback = false; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 9e7abf447cd6..70be4a6d7730 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -25,7 +25,6 @@ #include "IRenderPipeline.h" #include "LayerUpdateQueue.h" #include "RenderNode.h" -#include "ReliableSurface.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" #include "thread/Task.h" @@ -220,7 +219,7 @@ private: EGLint mLastFrameHeight = 0; RenderThread& mRenderThread; - sp<ReliableSurface> mNativeSurface; + sp<Surface> mNativeSurface; // stopped indicates the CanvasContext will reject actual redraw operations, // and defer repaint until it is un-stopped bool mStopped = false; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 8230dfd44f9a..65ced6ad9316 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -31,8 +31,6 @@ #include <string> #include <vector> -#include <system/window.h> -#include <gui/Surface.h> #define GLES_VERSION 2 @@ -108,7 +106,7 @@ void EglManager::initialize() { LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE, "Failed to initialize display %p! err=%s", mEglDisplay, eglErrorString()); - ALOGV("Initialized EGL, version %d.%d", (int)major, (int)minor); + ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor); initExtensions(); diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 42e17b273bee..4972554c65cc 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -28,10 +28,10 @@ class GrContext; -struct ANativeWindow; - namespace android { +class Surface; + namespace uirenderer { class DeferredLayerUpdater; @@ -67,7 +67,7 @@ public: virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; - virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0; + virtual bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; virtual bool isContextReady() = 0; diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp deleted file mode 100644 index 0ab4cd29f1cd..000000000000 --- a/libs/hwui/renderthread/ReliableSurface.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ReliableSurface.h" - -#include <private/android/AHardwareBufferHelpers.h> - -namespace android::uirenderer::renderthread { - -// TODO: Make surface less protected -// This exists because perform is a varargs, and ANativeWindow has no va_list perform. -// So wrapping/chaining that is hard. Telling the compiler to ignore protected is easy, so we do -// that instead -struct SurfaceExposer : Surface { - // Make warnings happy - SurfaceExposer() = delete; - - using Surface::setBufferCount; - using Surface::setSwapInterval; - using Surface::dequeueBuffer; - using Surface::queueBuffer; - using Surface::cancelBuffer; - using Surface::lockBuffer_DEPRECATED; - using Surface::perform; -}; - -#define callProtected(surface, func, ...) ((*surface).*&SurfaceExposer::func)(__VA_ARGS__) - -ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) { - LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr"); - - ANativeWindow::setSwapInterval = hook_setSwapInterval; - ANativeWindow::dequeueBuffer = hook_dequeueBuffer; - ANativeWindow::cancelBuffer = hook_cancelBuffer; - ANativeWindow::queueBuffer = hook_queueBuffer; - ANativeWindow::query = hook_query; - ANativeWindow::perform = hook_perform; - - ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED; - ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED; - ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED; - ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED; -} - -void ReliableSurface::perform(int operation, va_list args) { - std::lock_guard _lock{mMutex}; - - switch (operation) { - case NATIVE_WINDOW_SET_USAGE: - mUsage = va_arg(args, uint32_t); - break; - case NATIVE_WINDOW_SET_USAGE64: - mUsage = va_arg(args, uint64_t); - break; - case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: - /* width */ va_arg(args, uint32_t); - /* height */ va_arg(args, uint32_t); - mFormat = va_arg(args, PixelFormat); - break; - case NATIVE_WINDOW_SET_BUFFERS_FORMAT: - mFormat = va_arg(args, PixelFormat); - break; - } -} - -int ReliableSurface::reserveNext() { - { - std::lock_guard _lock{mMutex}; - if (mReservedBuffer) { - ALOGW("reserveNext called but there was already a buffer reserved?"); - return OK; - } - if (mInErrorState) { - return UNKNOWN_ERROR; - } - } - - int fenceFd = -1; - ANativeWindowBuffer* buffer = nullptr; - int result = callProtected(mSurface, dequeueBuffer, &buffer, &fenceFd); - - { - std::lock_guard _lock{mMutex}; - LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext"); - mReservedBuffer = buffer; - mReservedFenceFd.reset(fenceFd); - if (result != OK) { - ALOGW("reserveNext failed, error %d", result); - } - } - - return result; -} - -void ReliableSurface::clearReservedBuffer() { - std::lock_guard _lock{mMutex}; - if (mReservedBuffer) { - ALOGW("Reserved buffer %p was never used", mReservedBuffer); - } - mReservedBuffer = nullptr; - mReservedFenceFd.reset(); -} - -int ReliableSurface::cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) { - clearReservedBuffer(); - if (isFallbackBuffer(buffer)) { - if (fenceFd > 0) { - close(fenceFd); - } - return OK; - } - int result = callProtected(mSurface, cancelBuffer, buffer, fenceFd); - return result; -} - -int ReliableSurface::dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) { - { - std::lock_guard _lock{mMutex}; - if (mReservedBuffer) { - *buffer = mReservedBuffer; - *fenceFd = mReservedFenceFd.release(); - mReservedBuffer = nullptr; - return OK; - } - } - - int result = callProtected(mSurface, dequeueBuffer, buffer, fenceFd); - if (result != OK) { - ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result); - *buffer = acquireFallbackBuffer(); - *fenceFd = -1; - return *buffer ? OK : INVALID_OPERATION; - } - return OK; -} - -int ReliableSurface::queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) { - clearReservedBuffer(); - - if (isFallbackBuffer(buffer)) { - if (fenceFd > 0) { - close(fenceFd); - } - return OK; - } - - int result = callProtected(mSurface, queueBuffer, buffer, fenceFd); - return result; -} - -bool ReliableSurface::isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const { - if (!mScratchBuffer || !windowBuffer) { - return false; - } - ANativeWindowBuffer* scratchBuffer = - AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); - return windowBuffer == scratchBuffer; -} - -ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer() { - std::lock_guard _lock{mMutex}; - mInErrorState = true; - - if (mScratchBuffer) { - return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); - } - - AHardwareBuffer_Desc desc; - desc.usage = mUsage; - desc.format = mFormat; - desc.width = 1; - desc.height = 1; - desc.layers = 1; - desc.rfu0 = 0; - desc.rfu1 = 0; - AHardwareBuffer* newBuffer = nullptr; - int err = AHardwareBuffer_allocate(&desc, &newBuffer); - if (err) { - // Allocate failed, that sucks - ALOGW("Failed to allocate scratch buffer, error=%d", err); - return nullptr; - } - mScratchBuffer.reset(newBuffer); - return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer); -} - -Surface* ReliableSurface::getWrapped(const ANativeWindow* window) { - return getSelf(window)->mSurface.get(); -} - -int ReliableSurface::hook_setSwapInterval(ANativeWindow* window, int interval) { - return callProtected(getWrapped(window), setSwapInterval, interval); -} - -int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, - int* fenceFd) { - return getSelf(window)->dequeueBuffer(buffer, fenceFd); -} - -int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, - int fenceFd) { - return getSelf(window)->cancelBuffer(buffer, fenceFd); -} - -int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, - int fenceFd) { - return getSelf(window)->queueBuffer(buffer, fenceFd); -} - -int ReliableSurface::hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer** buffer) { - ANativeWindowBuffer* buf; - int fenceFd = -1; - int result = window->dequeueBuffer(window, &buf, &fenceFd); - if (result != OK) { - return result; - } - sp<Fence> fence(new Fence(fenceFd)); - int waitResult = fence->waitForever("dequeueBuffer_DEPRECATED"); - if (waitResult != OK) { - ALOGE("dequeueBuffer_DEPRECATED: Fence::wait returned an error: %d", waitResult); - window->cancelBuffer(window, buf, -1); - return waitResult; - } - *buffer = buf; - return result; -} - -int ReliableSurface::hook_cancelBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - return window->cancelBuffer(window, buffer, -1); -} - -int ReliableSurface::hook_lockBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - // This method is a no-op in Surface as well - return OK; -} - -int ReliableSurface::hook_queueBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - return window->queueBuffer(window, buffer, -1); -} - -int ReliableSurface::hook_query(const ANativeWindow* window, int what, int* value) { - return getWrapped(window)->query(what, value); -} - -int ReliableSurface::hook_perform(ANativeWindow* window, int operation, ...) { - va_list args; - va_start(args, operation); - int result = callProtected(getWrapped(window), perform, operation, args); - va_end(args); - - switch (operation) { - case NATIVE_WINDOW_SET_BUFFERS_FORMAT: - case NATIVE_WINDOW_SET_USAGE: - case NATIVE_WINDOW_SET_USAGE64: - va_start(args, operation); - getSelf(window)->perform(operation, args); - va_end(args); - break; - default: - break; - } - - return result; -} - -}; // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h deleted file mode 100644 index 9ae53a9798d3..000000000000 --- a/libs/hwui/renderthread/ReliableSurface.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <gui/Surface.h> -#include <utils/Macros.h> -#include <utils/StrongPointer.h> - -#include <memory> - -namespace android::uirenderer::renderthread { - -class ReliableSurface : public ANativeObjectBase<ANativeWindow, ReliableSurface, RefBase> { - PREVENT_COPY_AND_ASSIGN(ReliableSurface); - -public: - ReliableSurface(sp<Surface>&& surface); - - void setDequeueTimeout(nsecs_t timeout) { mSurface->setDequeueTimeout(timeout); } - - int reserveNext(); - - void allocateBuffers() { mSurface->allocateBuffers(); } - - int query(int what, int* value) const { return mSurface->query(what, value); } - - nsecs_t getLastDequeueStartTime() const { return mSurface->getLastDequeueStartTime(); } - - uint64_t getNextFrameNumber() const { return mSurface->getNextFrameNumber(); } - -private: - const sp<Surface> mSurface; - - mutable std::mutex mMutex; - - uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER; - PixelFormat mFormat = PIXEL_FORMAT_RGBA_8888; - std::unique_ptr<AHardwareBuffer, void (*)(AHardwareBuffer*)> mScratchBuffer{ - nullptr, AHardwareBuffer_release}; - bool mInErrorState = false; - ANativeWindowBuffer* mReservedBuffer = nullptr; - base::unique_fd mReservedFenceFd; - - bool isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const; - ANativeWindowBuffer* acquireFallbackBuffer(); - void clearReservedBuffer(); - - void perform(int operation, va_list args); - int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd); - int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd); - int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd); - - static Surface* getWrapped(const ANativeWindow*); - - // ANativeWindow hooks - static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); - static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, - int* fenceFd); - static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); - - static int hook_perform(ANativeWindow* window, int operation, ...); - static int hook_query(const ANativeWindow* window, int what, int* value); - static int hook_setSwapInterval(ANativeWindow* window, int interval); - - static int hook_cancelBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); - static int hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer** buffer); - static int hook_lockBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); - static int hook_queueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); -}; - -}; // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index b047f8de515d..111dd0f0b63e 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.media.MediaPlayer2.DrmInfo; import android.media.MediaPlayer2Proto.PlayerMessage; import android.media.MediaPlayer2Proto.Value; import android.net.Uri; @@ -72,7 +73,11 @@ import java.util.Map; import java.util.Queue; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -300,21 +305,7 @@ public class MediaPlayer2 implements AutoCloseable private volatile float mVolume = 1.0f; private VideoSize mVideoSize = new VideoSize(0, 0); - // TODO: create per-source drm fields in SourceInfo - // Modular DRM - private final Object mDrmLock = new Object(); - //--- guarded by |mDrmLock| start - private UUID mDrmUUID; - private DrmInfo mDrmInfo; - private MediaDrm mDrmObj; - private byte[] mDrmSessionId; - private boolean mDrmInfoResolved; - private boolean mActiveDrmScheme; - private boolean mDrmConfigAllowed; - private boolean mDrmProvisioningInProgress; - private boolean mPrepareDrmInProgress; - private ProvisioningThread mDrmProvisioningThread; - //--- guarded by |mDrmLock| end + private ExecutorService mDrmThreadPool = Executors.newCachedThreadPool(); // Creating a dummy audio track, used for keeping session id alive private final Object mSessionIdLock = new Object(); @@ -328,6 +319,7 @@ public class MediaPlayer2 implements AutoCloseable private final List<Task> mPendingTasks = new LinkedList<>(); @GuardedBy("mTaskLock") private Task mCurrentTask; + private final AtomicLong mTaskIdGenerator = new AtomicLong(0); @GuardedBy("mTaskLock") boolean mIsPreviousCommandSeekTo = false; @@ -413,15 +405,13 @@ public class MediaPlayer2 implements AutoCloseable mHandlerThread = null; } - setCurrentSourceInfo(null); - clearNextSourceInfos(); + clearSourceInfos(); // Modular DRM clean up mOnDrmConfigHelper = null; synchronized (mDrmEventCbLock) { mDrmEventCallbackRecords.clear(); } - resetDrmState(); native_release(); @@ -461,13 +451,8 @@ public class MediaPlayer2 implements AutoCloseable synchronized (mDrmEventCbLock) { mDrmEventCallbackRecords.clear(); } - setCurrentSourceInfo(null); - clearNextSourceInfos(); - synchronized (mTaskLock) { - mPendingTasks.clear(); - mIsPreviousCommandSeekTo = false; - } + clearSourceInfos(); stayAwake(false); native_reset(); @@ -481,7 +466,6 @@ public class MediaPlayer2 implements AutoCloseable mTaskHandler.removeCallbacksAndMessages(null); } - resetDrmState(); } private native void native_reset(); @@ -706,13 +690,14 @@ public class MediaPlayer2 implements AutoCloseable } synchronized (mSrcLock) { - setCurrentSourceInfo(new SourceInfo(dsd)); + setCurrentSourceInfo_l(new SourceInfo(dsd)); handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId); } } finally { dsd.close(); } } + }); } @@ -732,7 +717,7 @@ public class MediaPlayer2 implements AutoCloseable void process() { Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null"); synchronized (mSrcLock) { - clearNextSourceInfos(); + clearNextSourceInfos_l(); mNextSourceInfos.add(new SourceInfo(dsd)); } prepareNextDataSource(); @@ -758,7 +743,7 @@ public class MediaPlayer2 implements AutoCloseable } synchronized (mSrcLock) { - clearNextSourceInfos(); + clearNextSourceInfos_l(); for (DataSourceDesc dsd : dsds) { if (dsd != null) { mNextSourceInfos.add(new SourceInfo(dsd)); @@ -781,7 +766,9 @@ public class MediaPlayer2 implements AutoCloseable return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) { @Override void process() { - clearNextSourceInfos(); + synchronized (mSrcLock) { + clearNextSourceInfos_l(); + } } }); } @@ -1073,7 +1060,7 @@ public class MediaPlayer2 implements AutoCloseable SourceInfo nextSourceInfo = mNextSourceInfos.peek(); if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) { // Switch to next source only when it has been prepared. - setCurrentSourceInfo(mNextSourceInfos.poll()); + setCurrentSourceInfo_l(mNextSourceInfos.poll()); long srcId = mCurrentSourceInfo.mId; try { @@ -2173,7 +2160,7 @@ public class MediaPlayer2 implements AutoCloseable final int what = msg.arg1; final int extra = msg.arg2; - final SourceInfo sourceInfo = getSourceInfoById(srcId); + final SourceInfo sourceInfo = getSourceInfo(srcId); if (sourceInfo == null) { return; } @@ -2227,11 +2214,11 @@ public class MediaPlayer2 implements AutoCloseable Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL"); } else if (msg.obj instanceof byte[]) { // The PlayerMessage was parsed already in postEventFromNative - final DrmInfo drmInfo; - synchronized (mDrmLock) { - if (mDrmInfo != null) { - drmInfo = mDrmInfo.makeCopy(); + final DrmInfo drmInfo; + synchronized (sourceInfo) { + if (sourceInfo.mDrmInfo != null) { + drmInfo = sourceInfo.mDrmInfo.makeCopy(); } else { drmInfo = null; } @@ -2303,7 +2290,7 @@ public class MediaPlayer2 implements AutoCloseable } }); - SourceInfo src = getSourceInfoById(srcId); + SourceInfo src = getSourceInfo(srcId); if (src != null) { src.mBufferedPercentage.set(percent); } @@ -2504,6 +2491,7 @@ public class MediaPlayer2 implements AutoCloseable return; } + final SourceInfo sourceInfo = mp.getSourceInfo(srcId); switch (what) { case MEDIA_DRM_INFO: // We need to derive mDrmInfo before prepare() returns so processing it here @@ -2511,7 +2499,7 @@ public class MediaPlayer2 implements AutoCloseable // notification looper so its handleMessage might process the event after prepare() // has returned. Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO"); - if (obj != null) { + if (obj != null && sourceInfo != null) { PlayerMessage playerMsg; try { playerMsg = PlayerMessage.parseFrom(obj); @@ -2520,11 +2508,12 @@ public class MediaPlayer2 implements AutoCloseable break; } DrmInfo drmInfo = new DrmInfo(playerMsg); - synchronized (mp.mDrmLock) { - mp.mDrmInfo = drmInfo; + synchronized (sourceInfo) { + sourceInfo.mDrmInfo = drmInfo; } } else { - Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj); + Log.w(TAG, "MEDIA_DRM_INFO sourceInfo " + sourceInfo + + " msg.obj of unexpected type " + obj); } break; @@ -2533,8 +2522,10 @@ public class MediaPlayer2 implements AutoCloseable // mainly for prepare() use case. For prepare(), this still can run to a race // condition b/c MediaPlayerNative releases the prepare() lock before calling notify // so we also set mDrmInfoResolved in prepare(). - synchronized (mp.mDrmLock) { - mp.mDrmInfoResolved = true; + if (sourceInfo != null) { + synchronized (sourceInfo) { + sourceInfo.mDrmInfoResolved = true; + } } break; } @@ -3211,9 +3202,7 @@ public class MediaPlayer2 implements AutoCloseable */ // This is a synchronous call. public void setOnDrmConfigHelper(OnDrmConfigHelper listener) { - synchronized (mDrmLock) { - mOnDrmConfigHelper = listener; - } + mOnDrmConfigHelper = listener; } private OnDrmConfigHelper mOnDrmConfigHelper; @@ -3358,24 +3347,27 @@ public class MediaPlayer2 implements AutoCloseable * @throws IllegalStateException if called before being prepared */ public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) { - // TODO: this implementation only works when dsd is the only data source - DrmInfo drmInfo = null; - - // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet; - // regardless below returns drmInfo anyway instead of raising an exception - synchronized (mDrmLock) { - if (!mDrmInfoResolved && mDrmInfo == null) { - final String msg = "The Player has not been prepared yet"; - Log.v(TAG, msg); - throw new IllegalStateException(msg); - } + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + DrmInfo drmInfo = null; + + // there is not much point if the app calls getDrmInfo within an OnDrmInfoListener; + // regardless below returns drmInfo anyway instead of raising an exception + synchronized (sourceInfo) { + if (!sourceInfo.mDrmInfoResolved && sourceInfo.mDrmInfo == null) { + final String msg = "The Player has not been prepared yet"; + Log.v(TAG, msg); + throw new IllegalStateException(msg); + } - if (mDrmInfo != null) { - drmInfo = mDrmInfo.makeCopy(); - } - } // synchronized + if (sourceInfo.mDrmInfo != null) { + drmInfo = sourceInfo.mDrmInfo.makeCopy(); + } + } // synchronized - return drmInfo; + return drmInfo; + } + return null; } /** @@ -3411,15 +3403,28 @@ public class MediaPlayer2 implements AutoCloseable */ // This is an asynchronous call. public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) { - // TODO: this implementation only works when dsd is the only data source return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) { @Override void process() { - int status = PREPARE_DRM_STATUS_SUCCESS; + final SourceInfo sourceInfo = getSourceInfo(dsd); + int status = PREPARE_DRM_STATUS_PREPARATION_ERROR; boolean sendEvent = true; + if (sourceInfo == null) { + Log.e(TAG, "prepareDrm(): DataSource not found."); + } else if (sourceInfo.mDrmInfo == null) { + // only allowing if tied to a protected source; + // might relax for releasing offline keys + Log.e(TAG, "prepareDrm(): Wrong usage: The player must be prepared and " + + "DRM info be retrieved before this call."); + } else { + status = PREPARE_DRM_STATUS_SUCCESS; + } + try { - doPrepareDrm(dsd, uuid); + if (status == PREPARE_DRM_STATUS_SUCCESS) { + sourceInfo.mDrmHandle.prepare(uuid); + } } catch (ResourceBusyException e) { status = PREPARE_DRM_STATUS_RESOURCE_BUSY; } catch (UnsupportedSchemeException e) { @@ -3428,14 +3433,14 @@ public class MediaPlayer2 implements AutoCloseable Log.w(TAG, "prepareDrm: NotProvisionedException"); // handle provisioning internally; it'll reset mPrepareDrmInProgress - status = handleProvisioninig(dsd, uuid); + status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId); if (status == PREPARE_DRM_STATUS_SUCCESS) { // DrmEventCallback will be fired in provisioning sendEvent = false; } else { - synchronized (mDrmLock) { - cleanDrmObj(); + synchronized (sourceInfo.mDrmHandle) { + sourceInfo.mDrmHandle.cleanDrmObj(); } switch (status) { @@ -3478,95 +3483,6 @@ public class MediaPlayer2 implements AutoCloseable }); } - private void doPrepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) - throws UnsupportedSchemeException, ResourceBusyException, - NotProvisionedException { - Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper); - - synchronized (mDrmLock) { - // only allowing if tied to a protected source; might relax for releasing offline keys - if (mDrmInfo == null) { - final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " - + "DRM info be retrieved before this call."; - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } - - if (mActiveDrmScheme) { - final String msg = "prepareDrm(): Wrong usage: There is already " - + "an active DRM scheme with " + mDrmUUID; - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } - - if (mPrepareDrmInProgress) { - final String msg = "prepareDrm(): Wrong usage: There is already " - + "a pending prepareDrm call."; - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } - - if (mDrmProvisioningInProgress) { - final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress."; - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } - - // shouldn't need this; just for safeguard - cleanDrmObj(); - - mPrepareDrmInProgress = true; - - try { - // only creating the DRM object to allow pre-openSession configuration - prepareDrm_createDrmStep(uuid); - } catch (Exception e) { - Log.w(TAG, "prepareDrm(): Exception ", e); - mPrepareDrmInProgress = false; - throw e; - } - - mDrmConfigAllowed = true; - } // synchronized - - // call the callback outside the lock - if (mOnDrmConfigHelper != null) { - mOnDrmConfigHelper.onDrmConfig(this, dsd); - } - - synchronized (mDrmLock) { - mDrmConfigAllowed = false; - boolean earlyExit = false; - - try { - prepareDrm_openSessionStep(uuid); - - mDrmUUID = uuid; - mActiveDrmScheme = true; - mPrepareDrmInProgress = false; - } catch (IllegalStateException e) { - final String msg = "prepareDrm(): Wrong usage: The player must be " - + "in the prepared state to call prepareDrm()."; - Log.e(TAG, msg); - earlyExit = true; - mPrepareDrmInProgress = false; - throw new IllegalStateException(msg); - } catch (NotProvisionedException e) { - Log.w(TAG, "prepareDrm: NotProvisionedException", e); - throw e; - } catch (Exception e) { - Log.e(TAG, "prepareDrm: Exception " + e); - earlyExit = true; - mPrepareDrmInProgress = false; - throw e; - } finally { - if (earlyExit) { // clean up object if didn't succeed - cleanDrmObj(); - } - } // finally - } // synchronized - } - /** * Releases the DRM session for the given data source * <p> @@ -3581,35 +3497,10 @@ public class MediaPlayer2 implements AutoCloseable // This is a synchronous call. public void releaseDrm(@NonNull DataSourceDesc dsd) throws NoDrmSchemeException { - // TODO: this implementation only works when dsd is the only data source - synchronized (mDrmLock) { - Log.v(TAG, "releaseDrm:"); - - if (!mActiveDrmScheme) { - Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); - throw new NoDrmSchemeException( - "releaseDrm: No active DRM scheme to release."); - } - - try { - // we don't have the player's state in this layer. The below call raises - // exception if we're in a non-stopped/prepared state. - - // for cleaning native/mediaserver crypto object - native_releaseDrm(); - - // for cleaning client-side MediaDrm object; only called if above has succeeded - cleanDrmObj(); - - mActiveDrmScheme = false; - } catch (IllegalStateException e) { - Log.w(TAG, "releaseDrm: Exception ", e); - throw new IllegalStateException( - "releaseDrm: The player is not in a valid state."); - } catch (Exception e) { - Log.e(TAG, "releaseDrm: Exception ", e); - } - } // synchronized + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + sourceInfo.mDrmHandle.release(); + } } private native void native_releaseDrm(); @@ -3653,51 +3544,22 @@ public class MediaPlayer2 implements AutoCloseable * * @throws NoDrmSchemeException if there is no active DRM session */ - @NonNull public MediaDrm.KeyRequest getDrmKeyRequest( @NonNull DataSourceDesc dsd, @Nullable byte[] keySetId, @Nullable byte[] initData, @Nullable String mimeType, @MediaDrmKeyType int keyType, @Nullable Map<String, String> optionalParameters) throws NoDrmSchemeException { - // TODO: this implementation only works when dsd is the only data source - Log.v(TAG, "getDrmKeyRequest: " - + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType - + " keyType: " + keyType + " optionalParameters: " + optionalParameters); - - synchronized (mDrmLock) { - if (!mActiveDrmScheme) { - Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); - throw new NoDrmSchemeException( - "getDrmKeyRequest: Has to set a DRM scheme first."); - } - - try { - byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) - ? mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE - keySetId; // keySetId for KEY_TYPE_RELEASE - - HashMap<String, String> hmapOptionalParameters = - (optionalParameters != null) - ? new HashMap<String, String>(optionalParameters) : - null; - - MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType, - keyType, hmapOptionalParameters); - Log.v(TAG, "getDrmKeyRequest: --> request: " + request); - - return request; - - } catch (NotProvisionedException e) { - Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " - + "Unexpected. Shouldn't have reached here."); - throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error."); - } catch (Exception e) { - Log.w(TAG, "getDrmKeyRequest Exception " + e); - throw e; - } - - } // synchronized + Log.v(TAG, "getDrmKeyRequest: " + + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType + + " keyType: " + keyType + " optionalParameters: " + optionalParameters); + + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + return sourceInfo.mDrmHandle.getDrmKeyRequest( + keySetId, initData, mimeType, keyType, optionalParameters); + } + return null; } /** @@ -3727,40 +3589,13 @@ public class MediaPlayer2 implements AutoCloseable @NonNull DataSourceDesc dsd, @Nullable byte[] keySetId, @NonNull byte[] response) throws NoDrmSchemeException, DeniedByServerException { - // TODO: this implementation only works when dsd is the only data source Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response); - synchronized (mDrmLock) { - - if (!mActiveDrmScheme) { - Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); - throw new NoDrmSchemeException( - "getDrmKeyRequest: Has to set a DRM scheme first."); - } - - try { - byte[] scope = (keySetId == null) - ? mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE - keySetId; // keySetId for KEY_TYPE_RELEASE - - byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); - - Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response - + " --> " + keySetResult); - - - return keySetResult; - - } catch (NotProvisionedException e) { - Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " - + "Unexpected. Shouldn't have reached here."); - throw new IllegalStateException("provideDrmKeyResponse: " - + "Unexpected provisioning error."); - } catch (Exception e) { - Log.w(TAG, "provideDrmKeyResponse Exception " + e); - throw e; - } - } // synchronized + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + return sourceInfo.mDrmHandle.provideDrmKeyResponse(keySetId, response); + } + return null; } /** @@ -3779,23 +3614,12 @@ public class MediaPlayer2 implements AutoCloseable @NonNull DataSourceDesc dsd, @NonNull byte[] keySetId) throws NoDrmSchemeException { - // TODO: this implementation only works when dsd is the only data source Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId); - synchronized (mDrmLock) { - if (!mActiveDrmScheme) { - Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); - throw new NoDrmSchemeException( - "restoreDrmKeys: Has to set a DRM scheme first."); - } - - try { - mDrmObj.restoreKeys(mDrmSessionId, keySetId); - } catch (Exception e) { - Log.w(TAG, "restoreKeys Exception " + e); - throw e; - } - } // synchronized + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + sourceInfo.mDrmHandle.restoreDrmKeys(keySetId); + } } /** @@ -3812,34 +3636,17 @@ public class MediaPlayer2 implements AutoCloseable * * @throws NoDrmSchemeException if there is no active DRM session */ - @NonNull public String getDrmPropertyString( @NonNull DataSourceDesc dsd, @NonNull @MediaDrmStringProperty String propertyName) throws NoDrmSchemeException { - // TODO: this implementation only works when dsd is the only data source Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName); - String value; - synchronized (mDrmLock) { - - if (!mActiveDrmScheme && !mDrmConfigAllowed) { - Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); - throw new NoDrmSchemeException( - "getDrmPropertyString: Has to prepareDrm() first."); - } - - try { - value = mDrmObj.getPropertyString(propertyName); - } catch (Exception e) { - Log.w(TAG, "getDrmPropertyString Exception " + e); - throw e; - } - } // synchronized - - Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value); - - return value; + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + return sourceInfo.mDrmHandle.getDrmPropertyString(propertyName); + } + return null; } /** @@ -3863,21 +3670,10 @@ public class MediaPlayer2 implements AutoCloseable // TODO: this implementation only works when dsd is the only data source Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value); - synchronized (mDrmLock) { - - if (!mActiveDrmScheme && !mDrmConfigAllowed) { - Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); - throw new NoDrmSchemeException( - "setDrmPropertyString: Has to prepareDrm() first."); - } - - try { - mDrmObj.setPropertyString(propertyName, value); - } catch (Exception e) { - Log.w(TAG, "setDrmPropertyString Exception " + e); - throw e; - } - } // synchronized + final SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo != null) { + sourceInfo.mDrmHandle.setDrmPropertyString(propertyName, value); + } } /** @@ -4029,43 +3825,6 @@ public class MediaPlayer2 implements AutoCloseable private native void native_prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId); - // Modular DRM helpers - - private void prepareDrm_createDrmStep(@NonNull UUID uuid) - throws UnsupportedSchemeException { - Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); - - try { - mDrmObj = new MediaDrm(uuid); - Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); - } catch (Exception e) { // UnsupportedSchemeException - Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); - throw e; - } - } - - private void prepareDrm_openSessionStep(@NonNull UUID uuid) - throws NotProvisionedException, ResourceBusyException { - Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); - - // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do - // it anyway so it raises provisioning error if needed. We'd rather handle provisioning - // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse - try { - mDrmSessionId = mDrmObj.openSession(); - Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); - - // Sending it down to native/mediaserver to create the crypto object - // This call could simply fail due to bad player state, e.g., after play(). - native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); - Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded"); - - } catch (Exception e) { //ResourceBusyException, NotProvisionedException - Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); - throw e; - } - } - // Instantiated from the native side @SuppressWarnings("unused") private static class StreamEventCallback extends AudioTrack.StreamEventCallback { @@ -4097,227 +3856,28 @@ public class MediaPlayer2 implements AutoCloseable } } - private class ProvisioningThread extends Thread { - public static final int TIMEOUT_MS = 60000; - - private final DataSourceDesc mDSD; - private UUID mUuid; - private String mUrlStr; - private Object mDrmLock; - private MediaPlayer2 mMediaPlayer; - private int mStatus; - public int status() { - return mStatus; - } - - public ProvisioningThread(MediaDrm.ProvisionRequest request, - DataSourceDesc dsd, - UUID uuid, MediaPlayer2 mediaPlayer) { - // lock is held by the caller - mDSD = dsd; - mDrmLock = mediaPlayer.mDrmLock; - mMediaPlayer = mediaPlayer; - - mUrlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); - mUuid = uuid; - - mStatus = PREPARE_DRM_STATUS_PREPARATION_ERROR; - - Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + mUrlStr); - } - - public void run() { - - byte[] response = null; - boolean provisioningSucceeded = false; - try { - URL url = new URL(mUrlStr); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - try { - connection.setRequestMethod("POST"); - connection.setDoOutput(false); - connection.setDoInput(true); - connection.setConnectTimeout(TIMEOUT_MS); - connection.setReadTimeout(TIMEOUT_MS); - - connection.connect(); - response = readInputStreamFully(connection.getInputStream()); - - Log.v(TAG, "handleProvisioninig: Thread run: response " - + response.length + " " + response); - } catch (Exception e) { - mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; - Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url); - } finally { - connection.disconnect(); - } - } catch (Exception e) { - mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; - Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e); - } - - if (response != null) { - try { - mDrmObj.provideProvisionResponse(response); - Log.v(TAG, "handleProvisioninig: Thread run: " - + "provideProvisionResponse SUCCEEDED!"); - - provisioningSucceeded = true; - } catch (Exception e) { - mStatus = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; - Log.w(TAG, "handleProvisioninig: Thread run: " - + "provideProvisionResponse " + e); - } - } - - boolean succeeded = false; - - synchronized (mDrmLock) { - // continuing with prepareDrm - if (provisioningSucceeded) { - succeeded = mMediaPlayer.resumePrepareDrm(mUuid); - mStatus = (succeeded) - ? PREPARE_DRM_STATUS_SUCCESS : - PREPARE_DRM_STATUS_PREPARATION_ERROR; - } - mMediaPlayer.mDrmProvisioningInProgress = false; - mMediaPlayer.mPrepareDrmInProgress = false; - if (!succeeded) { - cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock - } - } // synchronized - - // calling the callback outside the lock - sendDrmEvent(new DrmEventNotifier() { - @Override - public void notify(DrmEventCallback callback) { - callback.onDrmPrepared( - mMediaPlayer, mDSD, mStatus); - } - }); - - synchronized (mTaskLock) { - if (mCurrentTask != null - && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM - && mCurrentTask.mNeedToWaitForEventToComplete) { - mCurrentTask = null; - processPendingTask_l(); - } - } - } - - /** - * Returns a byte[] containing the remainder of 'in', closing it when done. - */ - private byte[] readInputStreamFully(InputStream in) throws IOException { - try { - return readInputStreamFullyNoClose(in); - } finally { - in.close(); - } - } - - /** - * Returns a byte[] containing the remainder of 'in'. - */ - private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count; - while ((count = in.read(buffer)) != -1) { - bytes.write(buffer, 0, count); - } - return bytes.toByteArray(); - } - } // ProvisioningThread - - private int handleProvisioninig(DataSourceDesc dsd, UUID uuid) { - synchronized (mDrmLock) { - if (mDrmProvisioningInProgress) { - Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress"); - return PREPARE_DRM_STATUS_PREPARATION_ERROR; - } - - MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); - if (provReq == null) { - Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null."); - return PREPARE_DRM_STATUS_PREPARATION_ERROR; - } - - Log.v(TAG, "handleProvisioninig provReq " - + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); - - // networking in a background thread - mDrmProvisioningInProgress = true; - - mDrmProvisioningThread = new ProvisioningThread(provReq, dsd, uuid, this); - mDrmProvisioningThread.start(); - - return PREPARE_DRM_STATUS_SUCCESS; - } - } - - private boolean resumePrepareDrm(UUID uuid) { - Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); - - // mDrmLock is guaranteed to be held - boolean success = false; + /** + * Returns a byte[] containing the remainder of 'in', closing it when done. + */ + private static byte[] readInputStreamFully(InputStream in) throws IOException { try { - // resuming - prepareDrm_openSessionStep(uuid); - - mDrmUUID = uuid; - mActiveDrmScheme = true; - - success = true; - } catch (Exception e) { - Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed with " + e); - // mDrmObj clean up is done by the caller + return readInputStreamFullyNoClose(in); + } finally { + in.close(); } - - return success; - } - - private void resetDrmState() { - synchronized (mDrmLock) { - Log.v(TAG, "resetDrmState:" - + " mDrmInfo=" + mDrmInfo - + " mDrmProvisioningThread=" + mDrmProvisioningThread - + " mPrepareDrmInProgress=" + mPrepareDrmInProgress - + " mActiveDrmScheme=" + mActiveDrmScheme); - - mDrmInfoResolved = false; - mDrmInfo = null; - - if (mDrmProvisioningThread != null) { - // timeout; relying on HttpUrlConnection - try { - mDrmProvisioningThread.join(); - } catch (InterruptedException e) { - Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); - } - mDrmProvisioningThread = null; - } - - mPrepareDrmInProgress = false; - mActiveDrmScheme = false; - - cleanDrmObj(); - } // synchronized } - private void cleanDrmObj() { - // the caller holds mDrmLock - Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); - - if (mDrmSessionId != null) { - mDrmObj.closeSession(mDrmSessionId); - mDrmSessionId = null; - } - if (mDrmObj != null) { - mDrmObj.release(); - mDrmObj = null; + /** + * Returns a byte[] containing the remainder of 'in'. + */ + private static byte[] readInputStreamFullyNoClose(InputStream in) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count; + while ((count = in.read(buffer)) != -1) { + bytes.write(buffer, 0, count); } + return bytes.toByteArray(); } private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) { @@ -4333,8 +3893,6 @@ public class MediaPlayer2 implements AutoCloseable return uuidBytes; } - // Modular DRM end - private static class TimedTextUtil { // These keys must be in sync with the keys in TextDescription2.h private static final int KEY_START_TIME = 7; // int @@ -4410,6 +3968,7 @@ public class MediaPlayer2 implements AutoCloseable } private abstract class Task implements Runnable { + final long mTaskId = mTaskIdGenerator.getAndIncrement(); private final int mMediaCallType; private final boolean mNeedToWaitForEventToComplete; private DataSourceDesc mDSD; @@ -4501,7 +4060,503 @@ public class MediaPlayer2 implements AutoCloseable } }; - private final class SourceInfo { + // Modular DRM + final class DrmHandle { + + static final int PROVISION_TIMEOUT_MS = 60000; + + final DataSourceDesc mDSD; + + //--- guarded by |this| start + MediaDrm mDrmObj; + byte[] mDrmSessionId; + UUID mActiveDrmUUID; + boolean mDrmConfigAllowed; + boolean mDrmProvisioningInProgress; + boolean mPrepareDrmInProgress; + Future<?> mProvisionResult; + //--- guarded by |this| end + + DrmHandle(DataSourceDesc dsd) { + mDSD = dsd; + } + + void prepare(UUID uuid) throws UnsupportedSchemeException, + ResourceBusyException, NotProvisionedException { + final OnDrmConfigHelper onDrmConfigHelper = mOnDrmConfigHelper; + Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + onDrmConfigHelper); + + synchronized (this) { + if (mActiveDrmUUID != null) { + final String msg = "prepareDrm(): Wrong usage: There is already " + + "an active DRM scheme with " + uuid; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + if (mPrepareDrmInProgress) { + final String msg = "prepareDrm(): Wrong usage: There is already " + + "a pending prepareDrm call."; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + if (mDrmProvisioningInProgress) { + final String msg = "prepareDrm(): Unexpectd: Provisioning already in progress"; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + // shouldn't need this; just for safeguard + cleanDrmObj(); + + mPrepareDrmInProgress = true; + + try { + // only creating the DRM object to allow pre-openSession configuration + prepareDrm_createDrmStep(uuid); + } catch (Exception e) { + Log.w(TAG, "prepareDrm(): Exception ", e); + mPrepareDrmInProgress = false; + throw e; + } + + mDrmConfigAllowed = true; + } // synchronized + + // call the callback outside the lock + if (onDrmConfigHelper != null) { + onDrmConfigHelper.onDrmConfig(MediaPlayer2.this, mDSD); + } + + synchronized (this) { + mDrmConfigAllowed = false; + boolean earlyExit = false; + + try { + prepareDrm_openSessionStep(uuid); + + this.mActiveDrmUUID = uuid; + mPrepareDrmInProgress = false; + } catch (IllegalStateException e) { + final String msg = "prepareDrm(): Wrong usage: The player must be " + + "in the prepared state to call prepareDrm()."; + Log.e(TAG, msg); + earlyExit = true; + mPrepareDrmInProgress = false; + throw new IllegalStateException(msg); + } catch (NotProvisionedException e) { + Log.w(TAG, "prepareDrm: NotProvisionedException", e); + throw e; + } catch (Exception e) { + Log.e(TAG, "prepareDrm: Exception " + e); + earlyExit = true; + mPrepareDrmInProgress = false; + throw e; + } finally { + if (earlyExit) { // clean up object if didn't succeed + cleanDrmObj(); + } + } // finally + } // synchronized + } + + void prepareDrm_createDrmStep(UUID uuid) + throws UnsupportedSchemeException { + Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); + + try { + mDrmObj = new MediaDrm(uuid); + Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); + } catch (Exception e) { // UnsupportedSchemeException + Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); + throw e; + } + } + + void prepareDrm_openSessionStep(UUID uuid) + throws NotProvisionedException, ResourceBusyException { + Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); + + // TODO: + // don't need an open session for a future specialKeyReleaseDrm mode but we should do + // it anyway so it raises provisioning error if needed. We'd rather handle provisioning + // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse + try { + mDrmSessionId = mDrmObj.openSession(); + Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); + + // Sending it down to native/mediaserver to create the crypto object + // This call could simply fail due to bad player state, e.g., after play(). + MediaPlayer2.this.native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); + Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded"); + + } catch (Exception e) { //ResourceBusyException, NotProvisionedException + Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); + throw e; + } + + } + + int handleProvisioninig(UUID uuid, long taskId) { + synchronized (this) { + if (mDrmProvisioningInProgress) { + Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress"); + return PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + + MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); + if (provReq == null) { + Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null."); + return PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + + Log.v(TAG, "handleProvisioninig provReq " + + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); + + // networking in a background thread + mDrmProvisioningInProgress = true; + + mProvisionResult = mDrmThreadPool.submit(newProvisioningTask(uuid, taskId)); + + return PREPARE_DRM_STATUS_SUCCESS; + } + } + + void provision(UUID uuid, long taskId) { + + MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); + String urlStr = provReq.getDefaultUrl(); + urlStr += "&signedRequest=" + new String(provReq.getData()); + Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + urlStr); + + byte[] response = null; + boolean provisioningSucceeded = false; + int status = PREPARE_DRM_STATUS_PREPARATION_ERROR; + try { + URL url = new URL(urlStr); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + try { + connection.setRequestMethod("POST"); + connection.setDoOutput(false); + connection.setDoInput(true); + connection.setConnectTimeout(PROVISION_TIMEOUT_MS); + connection.setReadTimeout(PROVISION_TIMEOUT_MS); + + connection.connect(); + response = readInputStreamFully(connection.getInputStream()); + + Log.v(TAG, "handleProvisioninig: Thread run: response " + + response.length + " " + response); + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; + Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url); + } finally { + connection.disconnect(); + } + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; + Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e); + } + + if (response != null) { + try { + mDrmObj.provideProvisionResponse(response); + Log.v(TAG, "handleProvisioninig: Thread run: " + + "provideProvisionResponse SUCCEEDED!"); + + provisioningSucceeded = true; + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; + Log.w(TAG, "handleProvisioninig: Thread run: " + + "provideProvisionResponse " + e); + } + } + + boolean succeeded = false; + + synchronized (this) { + // continuing with prepareDrm + if (provisioningSucceeded) { + succeeded = resumePrepare(uuid); + status = (succeeded) ? + PREPARE_DRM_STATUS_SUCCESS : + PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + mDrmProvisioningInProgress = false; + mPrepareDrmInProgress = false; + if (!succeeded) { + cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock + } + } // synchronized + + // calling the callback outside the lock + final int finalStatus = status; + sendDrmEvent(new DrmEventNotifier() { + @Override + public void notify(DrmEventCallback callback) { + callback.onDrmPrepared( + MediaPlayer2.this, mDSD, finalStatus); + } + }); + + synchronized (mTaskLock) { + if (mCurrentTask != null + && mCurrentTask.mTaskId == taskId + && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM + && mCurrentTask.mNeedToWaitForEventToComplete) { + mCurrentTask = null; + processPendingTask_l(); + } + } + } + + Runnable newProvisioningTask(UUID uuid, long taskId) { + return new Runnable() { + @Override + public void run() { + provision(uuid, taskId); + } + }; + } + + boolean resumePrepare(UUID uuid) { + Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); + + // mDrmLock is guaranteed to be held + boolean success = false; + try { + // resuming + prepareDrm_openSessionStep(uuid); + + this.mActiveDrmUUID = uuid; + + success = true; + } catch (Exception e) { + Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed:" + e); + // mDrmObj clean up is done by the caller + } + + return success; + } + + void cleanDrmObj() { + // the caller holds mDrmLock + Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); + + if (mDrmSessionId != null) { + mDrmObj.closeSession(mDrmSessionId); + mDrmSessionId = null; + } + if (mDrmObj != null) { + mDrmObj.close(); + mDrmObj = null; + } + } + + void release() throws NoDrmSchemeException { + synchronized (this) { + Log.v(TAG, "releaseDrm:"); + + if (mActiveDrmUUID == null) { + Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); + throw new NoDrmSchemeException( + "releaseDrm: No active DRM scheme to release."); + } + + try { + // we don't have the player's state in this layer. The below call raises + // exception if we're in a non-stopped/prepared state. + + // for cleaning native/mediaserver crypto object + native_releaseDrm(); + + // for cleaning client-side MediaDrm object; only called if above has succeeded + cleanDrmObj(); + + this.mActiveDrmUUID = null; + } catch (IllegalStateException e) { + Log.w(TAG, "releaseDrm: Exception ", e); + throw new IllegalStateException( + "releaseDrm: The player is not in a valid state."); + } catch (Exception e) { + Log.e(TAG, "releaseDrm: Exception ", e); + } + } // synchronized + } + + void cleanup() { + synchronized (this) { + Log.v(TAG, "cleanupDrm: " + + " mProvisioningTask=" + mProvisionResult + + " mPrepareDrmInProgress=" + mPrepareDrmInProgress + + " mActiveDrmScheme=" + mActiveDrmUUID); + + if (mProvisionResult != null) { + // timeout; relying on HttpUrlConnection + try { + mProvisionResult.get(); + } + catch (InterruptedException | ExecutionException e) { + Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); + } + } + + // set to false to avoid duplicate release calls + this.mActiveDrmUUID = null; + + cleanDrmObj(); + } // synchronized + } + + Runnable newCleanupTask() { + return new Runnable() { + @Override + public void run() { + cleanup(); + } + }; + } + + MediaDrm.KeyRequest getDrmKeyRequest( + byte[] keySetId, byte[] initData, + String mimeType, int keyType, + Map<String, String> optionalParameters) + throws NoDrmSchemeException { + synchronized (this) { + if (mActiveDrmUUID == null) { + Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); + throw new NoDrmSchemeException( + "getDrmKeyRequest: Has to set a DRM scheme first."); + } + + try { + byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? + mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE + keySetId; // keySetId for KEY_TYPE_RELEASE + + HashMap<String, String> hmapOptionalParameters = + (optionalParameters != null) + ? new HashMap<String, String>(optionalParameters) + : null; + + MediaDrm.KeyRequest request = mDrmObj.getKeyRequest( + scope, initData, mimeType, keyType, hmapOptionalParameters); + Log.v(TAG, "getDrmKeyRequest: --> request: " + request); + + return request; + + } catch (NotProvisionedException e) { + Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " + + "Unexpected. Shouldn't have reached here."); + throw new IllegalStateException("getDrmKeyRequest: provisioning error."); + } catch (Exception e) { + Log.w(TAG, "getDrmKeyRequest Exception " + e); + throw e; + } + + } + } + + byte[] provideDrmKeyResponse(byte[] keySetId, byte[] response) + throws NoDrmSchemeException, DeniedByServerException { + synchronized (this) { + + if (mActiveDrmUUID == null) { + Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); + throw new NoDrmSchemeException( + "getDrmKeyRequest: Has to set a DRM scheme first."); + } + + try { + byte[] scope = (keySetId == null) ? + mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE + keySetId; // keySetId for KEY_TYPE_RELEASE + + byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); + + Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + + " response: " + response + " --> " + keySetResult); + + + return keySetResult; + + } catch (NotProvisionedException e) { + Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " + + "Unexpected. Shouldn't have reached here."); + throw new IllegalStateException("provideDrmKeyResponse: " + + "Unexpected provisioning error."); + } catch (Exception e) { + Log.w(TAG, "provideDrmKeyResponse Exception " + e); + throw e; + } + } + } + + void restoreDrmKeys(byte[] keySetId) + throws NoDrmSchemeException { + synchronized (this) { + if (mActiveDrmUUID == null) { + Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); + throw new NoDrmSchemeException( + "restoreDrmKeys: Has to set a DRM scheme first."); + } + + try { + mDrmObj.restoreKeys(mDrmSessionId, keySetId); + } catch (Exception e) { + Log.w(TAG, "restoreKeys Exception " + e); + throw e; + } + } + } + + String getDrmPropertyString(String propertyName) + throws NoDrmSchemeException { + String v; + synchronized (this) { + + if (mActiveDrmUUID == null && !mDrmConfigAllowed) { + Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); + throw new NoDrmSchemeException( + "getDrmPropertyString: Has to prepareDrm() first."); + } + + try { + v = mDrmObj.getPropertyString(propertyName); + } catch (Exception e) { + Log.w(TAG, "getDrmPropertyString Exception " + e); + throw e; + } + } // synchronized + + Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + v); + + return v; + } + + void setDrmPropertyString(String propertyName, String value) + throws NoDrmSchemeException { + synchronized (this) { + + if ( mActiveDrmUUID == null && !mDrmConfigAllowed ) { + Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); + throw new NoDrmSchemeException( + "setDrmPropertyString: Has to prepareDrm() first."); + } + + try { + mDrmObj.setPropertyString(propertyName, value); + } catch ( Exception e ) { + Log.w(TAG, "setDrmPropertyString Exception " + e); + throw e; + } + } + } + + } + + final class SourceInfo { final DataSourceDesc mDSD; final long mId = mSrcIdGenerator.getAndIncrement(); AtomicInteger mBufferedPercentage = new AtomicInteger(0); @@ -4513,8 +4568,14 @@ public class MediaPlayer2 implements AutoCloseable int mStateAsNextSource = NEXT_SOURCE_STATE_INIT; boolean mPlayPendingAsNextSource = false; + // Modular DRM + final DrmHandle mDrmHandle; + DrmInfo mDrmInfo; + boolean mDrmInfoResolved; + SourceInfo(DataSourceDesc dsd) { this.mDSD = dsd; + mDrmHandle = new DrmHandle(dsd); } void close() { @@ -4535,7 +4596,7 @@ public class MediaPlayer2 implements AutoCloseable } - private SourceInfo getSourceInfoById(long srcId) { + private SourceInfo getSourceInfo(long srcId) { synchronized (mSrcLock) { if (isCurrentSource(srcId)) { return mCurrentSourceInfo; @@ -4547,34 +4608,65 @@ public class MediaPlayer2 implements AutoCloseable return null; } + private SourceInfo getSourceInfo(DataSourceDesc dsd) { + synchronized (mSrcLock) { + if (isCurrentSource(dsd)) { + return mCurrentSourceInfo; + } + if (isNextSource(dsd)) { + return mNextSourceInfos.peek(); + } + } + return null; + } + private boolean isCurrentSource(long srcId) { synchronized (mSrcLock) { return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId; } } + private boolean isCurrentSource(DataSourceDesc dsd) { + synchronized (mSrcLock) { + return mCurrentSourceInfo != null && mCurrentSourceInfo.mDSD == dsd; + } + } + private boolean isNextSource(long srcId) { SourceInfo nextSourceInfo = mNextSourceInfos.peek(); return nextSourceInfo != null && nextSourceInfo.mId == srcId; } - private void setCurrentSourceInfo(SourceInfo newSourceInfo) { - synchronized (mSrcLock) { - if (mCurrentSourceInfo != null) { - mCurrentSourceInfo.close(); - } - mCurrentSourceInfo = newSourceInfo; + private boolean isNextSource(DataSourceDesc dsd) { + SourceInfo nextSourceInfo = mNextSourceInfos.peek(); + return nextSourceInfo != null && nextSourceInfo.mDSD == dsd; + } + + @GuardedBy("mSrcLock") + private void setCurrentSourceInfo_l(SourceInfo sourceInfo) { + cleanupSourceInfo(mCurrentSourceInfo); + mCurrentSourceInfo = sourceInfo; + } + + @GuardedBy("mSrcLock") + private void clearNextSourceInfos_l() { + while (!mNextSourceInfos.isEmpty()) { + cleanupSourceInfo(mNextSourceInfos.poll()); + } + } + + private void cleanupSourceInfo(SourceInfo sourceInfo) { + if (sourceInfo != null) { + sourceInfo.close(); + Runnable task = sourceInfo.mDrmHandle.newCleanupTask(); + mDrmThreadPool.submit(task); } } - private void clearNextSourceInfos() { + private void clearSourceInfos() { synchronized (mSrcLock) { - for (SourceInfo sourceInfo : mNextSourceInfos) { - if (sourceInfo != null) { - sourceInfo.close(); - } - } - mNextSourceInfos.clear(); + setCurrentSourceInfo_l(null); + clearNextSourceInfos_l(); } } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 436897fbf8b0..874f21ea8c28 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -18,6 +18,7 @@ package android.media; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -1029,7 +1030,7 @@ public class RingtoneManager { * @throws FileNotFoundException if the provided URI could not be opened. * @see #getDefaultUri */ - public static AssetFileDescriptor openDefaultRingtoneUri( + public static @Nullable AssetFileDescriptor openDefaultRingtoneUri( @NonNull Context context, @NonNull Uri uri) throws FileNotFoundException { // Try cached ringtone first since the actual provider may not be // encryption aware, or it may be stored on CE media storage diff --git a/native/webview/plat_support/draw_gl.h b/native/webview/plat_support/draw_gl.h index c8434b61eba5..de13ed0dec6f 100644 --- a/native/webview/plat_support/draw_gl.h +++ b/native/webview/plat_support/draw_gl.h @@ -43,9 +43,9 @@ struct AwDrawGLInfo { // Input: tells the draw function what action to perform. enum Mode { kModeDraw = 0, - kModeProcess, - kModeProcessNoContext, - kModeSync, + kModeProcess = 1, + kModeProcessNoContext = 2, + kModeSync = 3, } mode; // Input: current clip rect in surface coordinates. Reflects the current state @@ -93,9 +93,9 @@ typedef void (AwDrawGLFunction)(long view_context, AwDrawGLInfo* draw_info, void* spare); enum AwMapMode { - MAP_READ_ONLY, - MAP_WRITE_ONLY, - MAP_READ_WRITE, + MAP_READ_ONLY = 0, + MAP_WRITE_ONLY = 1, + MAP_READ_WRITE = 2, }; // Called to create a GraphicBuffer diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp deleted file mode 100644 index bc06cab7aef5..000000000000 --- a/packages/PackageInstaller/Android.bp +++ /dev/null @@ -1,14 +0,0 @@ -android_app { - name: "PackageInstaller", - - srcs: ["src/**/*.java"], - - static_libs: [ - "androidx.leanback_leanback", - "xz-java", - ], - - certificate: "platform", - privileged: true, - platform_apis: true, -}
\ No newline at end of file diff --git a/packages/PackageInstaller/Android.mk b/packages/PackageInstaller/Android.mk new file mode 100644 index 000000000000..ab5483c8afb8 --- /dev/null +++ b/packages/PackageInstaller/Android.mk @@ -0,0 +1,32 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := PackageInstaller + +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true +LOCAL_PRIVATE_PLATFORM_APIS := true + +LOCAL_STATIC_JAVA_LIBRARIES := xz-java +LOCAL_STATIC_ANDROID_LIBRARIES := androidx.leanback_leanback + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 4801f62bae67..eb9ec82c9519 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -2,6 +2,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.packageinstaller"> + <original-package android:name="com.android.packageinstaller" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.DELETE_PACKAGES" /> diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp new file mode 100644 index 000000000000..e518e0b97c4b --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp @@ -0,0 +1,13 @@ +android_library { + name: "ActionButtonsPreference", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.preference_preference", + ], + + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/ActionButtonsPreference/AndroidManifest.xml b/packages/SettingsLib/ActionButtonsPreference/AndroidManifest.xml new file mode 100644 index 000000000000..4b9f1ab8d6cc --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout/settings_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout/settings_action_buttons.xml new file mode 100644 index 000000000000..4f47113bbafa --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/res/layout/settings_action_buttons.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="8dp" + android:orientation="horizontal"> + + <Button + android:id="@+id/button1" + style="@style/SettingsActionButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <Button + android:id="@+id/button2" + style="@style/SettingsActionButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <Button + android:id="@+id/button3" + style="@style/SettingsActionButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <Button + android:id="@+id/button4" + style="@style/SettingsActionButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values/styles.xml b/packages/SettingsLib/ActionButtonsPreference/res/values/styles.xml new file mode 100644 index 000000000000..efa508dcde62 --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/res/values/styles.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <style name="SettingsActionButton" parent="android:Widget.DeviceDefault.Button.Borderless.Colored"> + <item name="android:drawablePadding">4dp</item> + <item name="android:drawableTint">@*android:color/btn_colored_borderless_text_material</item> + <item name="android:layout_marginEnd">8dp</item> + <item name="android:paddingTop">20dp</item> + <item name="android:paddingBottom">20dp</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java new file mode 100644 index 000000000000..8b46cc608fd1 --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Button; + +import androidx.annotation.DrawableRes; +import androidx.annotation.StringRes; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +/** + * This preference provides a four buttons layout with Settings style. + * It looks like below + * + * -------------------------------------------------- + * button1 | button2 | button3 | button4 | + * -------------------------------------------------- + * + * User can set title / icon / click listener for each button. + * + * By default, four buttons are visible. + * However, there are two cases which button should be invisible(View.GONE). + * + * 1. User sets invisible for button. ex: ActionButtonPreference.setButton1Visible(false) + * 2. User doesn't set any title or icon for button. + */ +public class ActionButtonsPreference extends Preference { + + private static final String TAG = "ActionButtonPreference"; + private final ButtonInfo mButton1Info = new ButtonInfo(); + private final ButtonInfo mButton2Info = new ButtonInfo(); + private final ButtonInfo mButton3Info = new ButtonInfo(); + private final ButtonInfo mButton4Info = new ButtonInfo(); + + public ActionButtonsPreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + public ActionButtonsPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public ActionButtonsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ActionButtonsPreference(Context context) { + super(context); + init(); + } + + private void init() { + setLayoutResource(R.layout.settings_action_buttons); + setSelectable(false); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + holder.setDividerAllowedAbove(true); + holder.setDividerAllowedBelow(true); + + mButton1Info.mButton = (Button) holder.findViewById(R.id.button1); + mButton2Info.mButton = (Button) holder.findViewById(R.id.button2); + mButton3Info.mButton = (Button) holder.findViewById(R.id.button3); + mButton4Info.mButton = (Button) holder.findViewById(R.id.button4); + + mButton1Info.setUpButton(); + mButton2Info.setUpButton(); + mButton3Info.setUpButton(); + mButton4Info.setUpButton(); + } + + /** + * Set the visibility state of button1. + */ + public ActionButtonsPreference setButton1Visible(boolean isVisible) { + if (isVisible != mButton1Info.mIsVisible) { + mButton1Info.mIsVisible = isVisible; + notifyChanged(); + } + return this; + } + + /** + * Sets the text to be displayed in button1. + */ + public ActionButtonsPreference setButton1Text(@StringRes int textResId) { + final String newText = getContext().getString(textResId); + if (!TextUtils.equals(newText, mButton1Info.mText)) { + mButton1Info.mText = newText; + notifyChanged(); + } + return this; + } + + /** + * Sets the drawable to be displayed above of text in button1. + */ + public ActionButtonsPreference setButton1Icon(@DrawableRes int iconResId) { + if (iconResId == 0) { + return this; + } + + final Drawable icon; + try { + icon = getContext().getDrawable(iconResId); + mButton1Info.mIcon = icon; + notifyChanged(); + } catch (Resources.NotFoundException exception) { + Log.e(TAG, "Resource does not exist: " + iconResId); + } + return this; + } + + /** + * Set the enabled state of button1. + */ + public ActionButtonsPreference setButton1Enabled(boolean isEnabled) { + if (isEnabled != mButton1Info.mIsEnabled) { + mButton1Info.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Register a callback to be invoked when button1 is clicked. + */ + public ActionButtonsPreference setButton1OnClickListener( + View.OnClickListener listener) { + if (listener != mButton1Info.mListener) { + mButton1Info.mListener = listener; + notifyChanged(); + } + return this; + } + + /** + * Set the visibility state of button2. + */ + public ActionButtonsPreference setButton2Visible(boolean isVisible) { + if (isVisible != mButton2Info.mIsVisible) { + mButton2Info.mIsVisible = isVisible; + notifyChanged(); + } + return this; + } + + /** + * Sets the text to be displayed in button2. + */ + public ActionButtonsPreference setButton2Text(@StringRes int textResId) { + final String newText = getContext().getString(textResId); + if (!TextUtils.equals(newText, mButton2Info.mText)) { + mButton2Info.mText = newText; + notifyChanged(); + } + return this; + } + + /** + * Sets the drawable to be displayed above of text in button2. + */ + public ActionButtonsPreference setButton2Icon(@DrawableRes int iconResId) { + if (iconResId == 0) { + return this; + } + + final Drawable icon; + try { + icon = getContext().getDrawable(iconResId); + mButton2Info.mIcon = icon; + notifyChanged(); + } catch (Resources.NotFoundException exception) { + Log.e(TAG, "Resource does not exist: " + iconResId); + } + return this; + } + + /** + * Set the enabled state of button2. + */ + public ActionButtonsPreference setButton2Enabled(boolean isEnabled) { + if (isEnabled != mButton2Info.mIsEnabled) { + mButton2Info.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Register a callback to be invoked when button2 is clicked. + */ + public ActionButtonsPreference setButton2OnClickListener( + View.OnClickListener listener) { + if (listener != mButton2Info.mListener) { + mButton2Info.mListener = listener; + notifyChanged(); + } + return this; + } + + /** + * Set the visibility state of button3. + */ + public ActionButtonsPreference setButton3Visible(boolean isVisible) { + if (isVisible != mButton3Info.mIsVisible) { + mButton3Info.mIsVisible = isVisible; + notifyChanged(); + } + return this; + } + + /** + * Sets the text to be displayed in button3. + */ + public ActionButtonsPreference setButton3Text(@StringRes int textResId) { + final String newText = getContext().getString(textResId); + if (!TextUtils.equals(newText, mButton3Info.mText)) { + mButton3Info.mText = newText; + notifyChanged(); + } + return this; + } + + /** + * Sets the drawable to be displayed above of text in button3. + */ + public ActionButtonsPreference setButton3Icon(@DrawableRes int iconResId) { + if (iconResId == 0) { + return this; + } + + final Drawable icon; + try { + icon = getContext().getDrawable(iconResId); + mButton3Info.mIcon = icon; + notifyChanged(); + } catch (Resources.NotFoundException exception) { + Log.e(TAG, "Resource does not exist: " + iconResId); + } + return this; + } + + /** + * Set the enabled state of button3. + */ + public ActionButtonsPreference setButton3Enabled(boolean isEnabled) { + if (isEnabled != mButton3Info.mIsEnabled) { + mButton3Info.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Register a callback to be invoked when button3 is clicked. + */ + public ActionButtonsPreference setButton3OnClickListener( + View.OnClickListener listener) { + if (listener != mButton3Info.mListener) { + mButton3Info.mListener = listener; + notifyChanged(); + } + return this; + } + + /** + * Set the visibility state of button4. + */ + public ActionButtonsPreference setButton4Visible(boolean isVisible) { + if (isVisible != mButton4Info.mIsVisible) { + mButton4Info.mIsVisible = isVisible; + notifyChanged(); + } + return this; + } + + /** + * Sets the text to be displayed in button4. + */ + public ActionButtonsPreference setButton4Text(@StringRes int textResId) { + final String newText = getContext().getString(textResId); + if (!TextUtils.equals(newText, mButton4Info.mText)) { + mButton4Info.mText = newText; + notifyChanged(); + } + return this; + } + + /** + * Sets the drawable to be displayed above of text in button4. + */ + public ActionButtonsPreference setButton4Icon(@DrawableRes int iconResId) { + if (iconResId == 0) { + return this; + } + + final Drawable icon; + try { + icon = getContext().getDrawable(iconResId); + mButton4Info.mIcon = icon; + notifyChanged(); + } catch (Resources.NotFoundException exception) { + Log.e(TAG, "Resource does not exist: " + iconResId); + } + return this; + } + + /** + * Set the enabled state of button4. + */ + public ActionButtonsPreference setButton4Enabled(boolean isEnabled) { + if (isEnabled != mButton4Info.mIsEnabled) { + mButton4Info.mIsEnabled = isEnabled; + notifyChanged(); + } + return this; + } + + /** + * Register a callback to be invoked when button4 is clicked. + */ + public ActionButtonsPreference setButton4OnClickListener( + View.OnClickListener listener) { + if (listener != mButton4Info.mListener) { + mButton4Info.mListener = listener; + notifyChanged(); + } + return this; + } + + static class ButtonInfo { + private Button mButton; + private CharSequence mText; + private Drawable mIcon; + private View.OnClickListener mListener; + private boolean mIsEnabled = true; + private boolean mIsVisible = true; + + void setUpButton() { + mButton.setText(mText); + mButton.setOnClickListener(mListener); + mButton.setEnabled(mIsEnabled); + mButton.setCompoundDrawablesWithIntrinsicBounds( + null /* left */, mIcon /* top */, null /* right */, null /* bottom */); + + if (shouldBeVisible()) { + mButton.setVisibility(View.VISIBLE); + } else { + mButton.setVisibility(View.GONE); + } + } + + /** + * By default, four buttons are visible. + * However, there are two cases which button should be invisible. + * + * 1. User set invisible for this button. ex: mIsVisible = false. + * 2. User didn't set any title or icon. + */ + private boolean shouldBeVisible() { + return mIsVisible && (!TextUtils.isEmpty(mText) || mIcon != null); + } + } +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 444e72459510..cc17b25d9a40 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -17,6 +17,7 @@ android_library { "SettingsLibSearchWidget", "SettingsLibSettingsSpinner", "SettingsLayoutPreference", + "ActionButtonsPreference", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index a97e05442b6d..9270d13002d7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -208,6 +208,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mHiSyncId = id; } + public boolean isHearingAidDevice() { + return mHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID; + } + void onBondingDockConnect() { // Attempt to connect if UUIDs are available. Otherwise, // we will connect when the ACTION_UUID intent arrives. diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java new file mode 100644 index 000000000000..88fef08bfcb7 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.Button; + +import androidx.preference.PreferenceViewHolder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class ActionButtonsPreferenceTest { + + private Context mContext; + private View mRootView; + private ActionButtonsPreference mPref; + private PreferenceViewHolder mHolder; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mRootView = View.inflate(mContext, R.layout.settings_action_buttons, null /* parent */); + mHolder = PreferenceViewHolder.createInstanceForTests(mRootView); + mPref = new ActionButtonsPreference(mContext); + } + + @Test + public void onBindViewHolder_setTitle_shouldShowButtonByDefault() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton2Text(R.string.install_other_apps); + mPref.setButton3Text(R.string.install_other_apps); + mPref.setButton4Text(R.string.install_other_apps); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @Test + public void onBindViewHolder_setIcon_shouldShowButtonByDefault() { + mPref.setButton1Icon(R.drawable.ic_plus); + mPref.setButton2Icon(R.drawable.ic_plus); + mPref.setButton3Icon(R.drawable.ic_plus); + mPref.setButton4Icon(R.drawable.ic_plus); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @Test + public void onBindViewHolder_notSetTitleOrIcon_shouldNotShowButtonByDefault() { + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_setVisibleIsGoneAndSetTitle_shouldNotShowButton() { + mPref.setButton1Text(R.string.install_other_apps).setButton1Visible(false); + mPref.setButton2Text(R.string.install_other_apps).setButton2Visible(false); + mPref.setButton3Text(R.string.install_other_apps).setButton3Visible(false); + mPref.setButton4Text(R.string.install_other_apps).setButton4Visible(false); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_setVisibleIsGoneAndSetIcon_shouldNotShowButton() { + mPref.setButton1Icon(R.drawable.ic_plus).setButton1Visible(false); + mPref.setButton2Icon(R.drawable.ic_plus).setButton2Visible(false); + mPref.setButton3Icon(R.drawable.ic_plus).setButton3Visible(false); + mPref.setButton4Icon(R.drawable.ic_plus).setButton4Visible(false); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_setVisibility_shouldUpdateButtonVisibility() { + mPref.setButton1Text(R.string.install_other_apps).setButton1Visible(false); + mPref.setButton2Text(R.string.install_other_apps).setButton2Visible(false); + mPref.setButton3Text(R.string.install_other_apps).setButton3Visible(false); + mPref.setButton4Text(R.string.install_other_apps).setButton4Visible(false); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.GONE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.GONE); + + mPref.setButton1Visible(true); + mPref.setButton2Visible(true); + mPref.setButton3Visible(true); + mPref.setButton4Visible(true); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button2).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button3).getVisibility()) + .isEqualTo(View.VISIBLE); + assertThat(mRootView.findViewById(R.id.button4).getVisibility()) + .isEqualTo(View.VISIBLE); + } + + @Test + public void onBindViewHolder_setEnabled_shouldEnableButton() { + mPref.setButton1Enabled(true); + mPref.setButton2Enabled(false); + mPref.setButton3Enabled(true); + mPref.setButton4Enabled(false); + + mPref.onBindViewHolder(mHolder); + + assertThat(mRootView.findViewById(R.id.button1).isEnabled()).isTrue(); + assertThat(mRootView.findViewById(R.id.button2).isEnabled()).isFalse(); + assertThat(mRootView.findViewById(R.id.button3).isEnabled()).isTrue(); + assertThat(mRootView.findViewById(R.id.button4).isEnabled()).isFalse(); + } + + @Test + public void onBindViewHolder_setText_shouldShowSameText() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton2Text(R.string.install_other_apps); + mPref.setButton3Text(R.string.install_other_apps); + mPref.setButton4Text(R.string.install_other_apps); + + mPref.onBindViewHolder(mHolder); + + assertThat(((Button) mRootView.findViewById(R.id.button1)).getText()) + .isEqualTo(mContext.getText(R.string.install_other_apps)); + assertThat(((Button) mRootView.findViewById(R.id.button2)).getText()) + .isEqualTo(mContext.getText(R.string.install_other_apps)); + assertThat(((Button) mRootView.findViewById(R.id.button3)).getText()) + .isEqualTo(mContext.getText(R.string.install_other_apps)); + assertThat(((Button) mRootView.findViewById(R.id.button4)).getText()) + .isEqualTo(mContext.getText(R.string.install_other_apps)); + } + + @Test + public void onBindViewHolder_setButtonIcon_iconMustDisplayAboveText() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton1Icon(R.drawable.ic_plus); + + mPref.onBindViewHolder(mHolder); + final Drawable[] drawablesAroundText = + ((Button) mRootView.findViewById(R.id.button1)) + .getCompoundDrawables(); + + assertThat(drawablesAroundText[1 /* top */]).isNotNull(); + } + + @Test + public void setButtonIcon_iconResourceIdIsZero_shouldNotDisplayIcon() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton1Icon(0); + + mPref.onBindViewHolder(mHolder); + final Drawable[] drawablesAroundText = + ((Button) mRootView.findViewById(R.id.button1)) + .getCompoundDrawables(); + + assertThat(drawablesAroundText[1 /* top */]).isNull(); + } + + @Test + public void setButtonIcon_iconResourceIdNotExisting_shouldNotDisplayIconAndCrash() { + mPref.setButton1Text(R.string.install_other_apps); + mPref.setButton1Icon(999999999 /* not existing id */); + // Should not crash here + mPref.onBindViewHolder(mHolder); + final Drawable[] drawablesAroundText = + ((Button) mRootView.findViewById(R.id.button1)) + .getCompoundDrawables(); + + assertThat(drawablesAroundText[1 /* top */]).isNull(); + } + + public static ActionButtonsPreference createMock() { + final ActionButtonsPreference pref = mock(ActionButtonsPreference.class); + when(pref.setButton1Text(anyInt())).thenReturn(pref); + when(pref.setButton1Icon(anyInt())).thenReturn(pref); + when(pref.setButton1Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton1Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton1OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton2Text(anyInt())).thenReturn(pref); + when(pref.setButton2Icon(anyInt())).thenReturn(pref); + when(pref.setButton2Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton2Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton2OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton3Text(anyInt())).thenReturn(pref); + when(pref.setButton3Icon(anyInt())).thenReturn(pref); + when(pref.setButton3Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton3Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton3OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton4Text(anyInt())).thenReturn(pref); + when(pref.setButton4Icon(anyInt())).thenReturn(pref); + when(pref.setButton4Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton4Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton4OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + return pref; + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index b2c12b277dc0..56b768feee23 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1914,6 +1914,15 @@ class SettingsProtoDumpUtil { SecureSettingsProto.Location.CHANGER); p.end(locationToken); + final long locationAccessCheckToken = p.start(SecureSettingsProto.LOCATION_ACCESS_CHECK); + dumpSetting(s, p, + Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, + SecureSettingsProto.LocationAccessCheck.INTERVAL_MILLIS); + dumpSetting(s, p, + Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS, + SecureSettingsProto.LocationAccessCheck.DELAY_MILLIS); + p.end(locationAccessCheckToken); + final long lockScreenToken = p.start(SecureSettingsProto.LOCK_SCREEN); // Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS intentionally excluded since it's deprecated. // Settings.Secure.LOCK_PATTERN_ENABLED intentionally excluded since it's deprecated. diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml index 5ca34b033f0d..1e8cd5a7e13b 100644 --- a/packages/SystemUI/res/layout/biometric_dialog.xml +++ b/packages/SystemUI/res/layout/biometric_dialog.xml @@ -160,6 +160,15 @@ android:maxLines="2" android:text="@string/biometric_dialog_confirm" android:visibility="gone"/> + <!-- Try Again Button --> + <Button android:id="@+id/button_try_again" + android:layout_width="wrap_content" + android:layout_height="match_parent" + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:gravity="center" + android:maxLines="2" + android:text="@string/biometric_dialog_try_again" + android:visibility="gone"/> <Space android:id="@+id/rightSpacer" android:layout_width="12dip" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2148e274d506..9917257bba8a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -272,6 +272,8 @@ <string name="accessibility_biometric_dialog_help_area">Help message area</string> <!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] --> <string name="biometric_dialog_confirm">Confirm</string> + <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR_LIMIT=30] --> + <string name="biometric_dialog_try_again">Try again</string> <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication --> <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string> @@ -700,6 +702,8 @@ <string name="quick_settings_bluetooth_secondary_label_headset">Headset</string> <!-- QuickSettings: Bluetooth secondary label for an input/IO device being connected [CHAR LIMIT=20]--> <string name="quick_settings_bluetooth_secondary_label_input">Input</string> + <!-- QuickSettings: Bluetooth secondary label for a Hearing Aids device being connected [CHAR LIMIT=20]--> + <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing Aids</string> <!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] --> <string name="quick_settings_bluetooth_secondary_label_transient">Turning on…</string> <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index ece2bb9a9507..f3bdbae97e42 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -71,4 +71,9 @@ interface ISystemUiProxy { */ void onStatusBarMotionEvent(in MotionEvent event) = 9; + /** + * Get the corner radius of windows in pixels. + */ + float getWindowCornerRadius() = 10; + } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java index 65c52200a7d8..a9cf857c3e50 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java @@ -83,6 +83,7 @@ public class SyncRtSurfaceTransactionApplier { t.setWindowCrop(params.surface, params.windowCrop); t.setAlpha(params.surface, params.alpha); t.setLayer(params.surface, params.layer); + t.setCornerRadius(params.surface, params.cornerRadius); t.show(params.surface); } @@ -98,12 +99,13 @@ public class SyncRtSurfaceTransactionApplier { * @param windowCrop Crop to apply. */ public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix, - Rect windowCrop, int layer) { + Rect windowCrop, int layer, float cornerRadius) { this.surface = surface.mSurfaceControl; this.alpha = alpha; this.matrix = new Matrix(matrix); this.windowCrop = new Rect(windowCrop); this.layer = layer; + this.cornerRadius = cornerRadius; } final SurfaceControl surface; @@ -111,5 +113,6 @@ public class SyncRtSurfaceTransactionApplier { final Matrix matrix; final Rect windowCrop; final int layer; + final float cornerRadius; } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java index ff9e84ca29c1..8a251ae51f38 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java @@ -18,6 +18,7 @@ package com.android.systemui.shared.system; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; @@ -61,7 +62,7 @@ public class WindowManagerWrapper { public static final int TRANSIT_KEYGUARD_OCCLUDE = WindowManager.TRANSIT_KEYGUARD_OCCLUDE; public static final int TRANSIT_KEYGUARD_UNOCCLUDE = WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; - public static final int NAV_BAR_POS_INVALID = -1; + public static final int NAV_BAR_POS_INVALID = NAV_BAR_INVALID; public static final int NAV_BAR_POS_LEFT = NAV_BAR_LEFT; public static final int NAV_BAR_POS_RIGHT = NAV_BAR_RIGHT; public static final int NAV_BAR_POS_BOTTOM = NAV_BAR_BOTTOM; @@ -178,10 +179,9 @@ public class WindowManagerWrapper { * @see #NAV_BAR_POS_BOTTOM * @see #NAV_BAR_POS_INVALID */ - public int getNavBarPosition() { + public int getNavBarPosition(int displayId) { try { - // TODO: Use WindowManagerService.getNavBarPosition(int displayId) - return WindowManagerGlobal.getWindowManagerService().getNavBarPosition(); + return WindowManagerGlobal.getWindowManagerService().getNavBarPosition(displayId); } catch (RemoteException e) { Log.w(TAG, "Failed to get nav bar position"); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index c0047c015813..a90a7d231dc1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -21,7 +21,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -52,15 +52,20 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private static final int MSG_BUTTON_NEGATIVE = 6; private static final int MSG_USER_CANCELED = 7; private static final int MSG_BUTTON_POSITIVE = 8; + private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9; + private static final int MSG_TRY_AGAIN_PRESSED = 10; private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view private SomeArgs mCurrentDialogArgs; private BiometricDialogView mCurrentDialog; private WindowManager mWindowManager; - private IBiometricPromptReceiver mReceiver; + private IBiometricServiceReceiverInternal mReceiver; private boolean mDialogShowing; private Callback mCallback = new Callback(); + private boolean mTryAgainShowing; // No good place to save state before config change :/ + private boolean mConfirmShowing; // No good place to save state before config change :/ + private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -89,6 +94,15 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba case MSG_BUTTON_POSITIVE: handleButtonPositive(); break; + case MSG_BIOMETRIC_SHOW_TRY_AGAIN: + handleShowTryAgain(); + break; + case MSG_TRY_AGAIN_PRESSED: + handleTryAgainPressed(); + break; + default: + Log.w(TAG, "Unknown message: " + msg.what); + break; } } }; @@ -96,7 +110,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private class Callback implements DialogViewCallback { @Override public void onUserCanceled() { - mHandler.obtainMessage(BiometricDialogImpl.MSG_USER_CANCELED).sendToTarget(); + mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget(); } @Override @@ -107,12 +121,17 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba @Override public void onNegativePressed() { - mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget(); + mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget(); } @Override public void onPositivePressed() { - mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget(); + mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget(); + } + + @Override + public void onTryAgainPressed() { + mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget(); } } @@ -139,13 +158,14 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, - boolean requireConfirmation, int userId) { + public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int type, boolean requireConfirmation, int userId) { if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type); // Remove these messages as they are part of the previous client mHandler.removeMessages(MSG_BIOMETRIC_ERROR); mHandler.removeMessages(MSG_BIOMETRIC_HELP); mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED); + mHandler.removeMessages(MSG_HIDE_DIALOG); SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; args.arg2 = receiver; @@ -179,6 +199,12 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget(); } + @Override + public void showBiometricTryAgain() { + if (DEBUG) Log.d(TAG, "showBiometricTryAgain"); + mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget(); + } + private void handleShowDialog(SomeArgs args, boolean skipAnimation) { mCurrentDialogArgs = args; final int type = args.argi1; @@ -193,11 +219,13 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba Log.w(TAG, "Dialog already showing"); return; } - mReceiver = (IBiometricPromptReceiver) args.arg2; + mReceiver = (IBiometricServiceReceiverInternal) args.arg2; mCurrentDialog.setBundle((Bundle)args.arg1); mCurrentDialog.setRequireConfirmation((boolean) args.arg3); mCurrentDialog.setUserId(args.argi2); mCurrentDialog.setSkipIntro(skipAnimation); + mCurrentDialog.setPendingTryAgain(mTryAgainShowing); + mCurrentDialog.setPendingConfirm(mConfirmShowing); mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); mDialogShowing = true; } @@ -209,7 +237,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mContext.getResources() .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); if (mCurrentDialog.requiresConfirmation()) { - mCurrentDialog.showConfirmationButton(); + mConfirmShowing = true; + mCurrentDialog.showConfirmationButton(true /* show */); } else { handleHideDialog(false /* userCanceled */); } @@ -226,6 +255,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba if (DEBUG) Log.d(TAG, "Dialog already dismissed"); return; } + mTryAgainShowing = false; mCurrentDialog.showErrorMessage(error); } @@ -246,6 +276,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } mReceiver = null; mDialogShowing = false; + mConfirmShowing = false; + mTryAgainShowing = false; mCurrentDialog.startDismiss(); } @@ -259,6 +291,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling negative button", e); } + mTryAgainShowing = false; handleHideDialog(false /* userCanceled */); } @@ -272,13 +305,31 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling positive button", e); } + mConfirmShowing = false; handleHideDialog(false /* userCanceled */); } private void handleUserCanceled() { + mTryAgainShowing = false; + mConfirmShowing = false; handleHideDialog(true /* userCanceled */); } + private void handleShowTryAgain() { + mCurrentDialog.showTryAgainButton(true /* show */); + mTryAgainShowing = true; + } + + private void handleTryAgainPressed() { + try { + mCurrentDialog.clearTemporaryMessage(); + mTryAgainShowing = false; + mReceiver.onTryAgainPressed(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException when handling try again", e); + } + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 38427adfd42a..e085f2368214 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -87,6 +87,9 @@ public abstract class BiometricDialogView extends LinearLayout { protected boolean mRequireConfirmation; private int mUserId; // used to determine if we should show work background + private boolean mPendingShowTryAgain; + private boolean mPendingShowConfirm; + protected abstract void updateIcon(int lastState, int newState); protected abstract int getHintStringResourceId(); protected abstract int getAuthenticatedAccessibilityResourceId(); @@ -178,6 +181,7 @@ public abstract class BiometricDialogView extends LinearLayout { final Button negative = mLayout.findViewById(R.id.button2); final Button positive = mLayout.findViewById(R.id.button1); final ImageView icon = mLayout.findViewById(R.id.biometric_icon); + final Button tryAgain = mLayout.findViewById(R.id.button_try_again); icon.setContentDescription(getResources().getString(getIconDescriptionResourceId())); @@ -193,6 +197,11 @@ public abstract class BiometricDialogView extends LinearLayout { mCallback.onPositivePressed(); }); + tryAgain.setOnClickListener((View v) -> { + showTryAgainButton(false /* show */); + mCallback.onTryAgainPressed(); + }); + mLayout.setFocusableInTouchMode(true); mLayout.requestFocus(); } @@ -207,7 +216,6 @@ public abstract class BiometricDialogView extends LinearLayout { final TextView subtitle = mLayout.findViewById(R.id.subtitle); final TextView description = mLayout.findViewById(R.id.description); final Button negative = mLayout.findViewById(R.id.button2); - final Button positive = mLayout.findViewById(R.id.button1); final ImageView backgroundView = mLayout.findViewById(R.id.background); if (mUserManager.isManagedProfile(mUserId)) { @@ -233,8 +241,6 @@ public abstract class BiometricDialogView extends LinearLayout { title.setText(titleText); title.setSelected(true); - positive.setVisibility(View.INVISIBLE); - final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); if (TextUtils.isEmpty(subtitleText)) { subtitle.setVisibility(View.GONE); @@ -243,7 +249,8 @@ public abstract class BiometricDialogView extends LinearLayout { subtitle.setText(subtitleText); } - final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); + final CharSequence descriptionText = + mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); if (TextUtils.isEmpty(descriptionText)) { description.setVisibility(View.GONE); } else { @@ -253,6 +260,9 @@ public abstract class BiometricDialogView extends LinearLayout { negative.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); + showTryAgainButton(mPendingShowTryAgain); + showConfirmationButton(mPendingShowConfirm); + if (mWasForceRemoved || mSkipIntro) { // Show the dialog immediately mLayout.animate().cancel(); @@ -281,11 +291,17 @@ public abstract class BiometricDialogView extends LinearLayout { public void startDismiss() { mAnimatingAway = true; + // This is where final cleanup should occur. final Runnable endActionRunnable = new Runnable() { @Override public void run() { mWindowManager.removeView(BiometricDialogView.this); mAnimatingAway = false; + // Set the icons / text back to normal state + handleClearMessage(); + showTryAgainButton(false /* show */); + mPendingShowTryAgain = false; + mPendingShowConfirm = false; } }; @@ -345,9 +361,13 @@ public abstract class BiometricDialogView extends LinearLayout { return mRequireConfirmation; } - public void showConfirmationButton() { + public void showConfirmationButton(boolean show) { final Button positive = mLayout.findViewById(R.id.button1); - positive.setVisibility(View.VISIBLE); + if (show) { + positive.setVisibility(View.VISIBLE); + } else { + positive.setVisibility(View.GONE); + } } public void setUserId(int userId) { @@ -376,12 +396,18 @@ public abstract class BiometricDialogView extends LinearLayout { BiometricPrompt.HIDE_DIALOG_DELAY); } + public void clearTemporaryMessage() { + mHandler.removeMessages(MSG_CLEAR_MESSAGE); + mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget(); + } + public void showHelpMessage(String message) { showTemporaryMessage(message); } public void showErrorMessage(String error) { showTemporaryMessage(error); + showTryAgainButton(false /* show */); mCallback.onErrorShown(); } @@ -390,6 +416,25 @@ public abstract class BiometricDialogView extends LinearLayout { mLastState = newState; } + public void showTryAgainButton(boolean show) { + final Button tryAgain = mLayout.findViewById(R.id.button_try_again); + if (show) { + tryAgain.setVisibility(View.VISIBLE); + } else { + tryAgain.setVisibility(View.GONE); + } + } + + // Set the state before the window is attached, so we know if the dialog should be started + // with or without the button. This is because there's no good onPause signal + public void setPendingTryAgain(boolean show) { + mPendingShowTryAgain = show; + } + + public void setPendingConfirm(boolean show) { + mPendingShowConfirm = show; + } + public WindowManager.LayoutParams getLayoutParams() { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java index f388d9c67718..24fd22e2ee80 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java @@ -43,4 +43,9 @@ public interface DialogViewCallback { * should be dismissed. */ void onPositivePressed(); + + /** + * Invoked when the "try again" button is pressed. + */ + void onTryAgainPressed(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 591e9e015897..9d2be39b28fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -26,6 +26,7 @@ import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; +import android.provider.Settings; import android.service.quicksettings.TileService; import android.text.TextUtils; import android.util.ArraySet; @@ -69,7 +70,8 @@ public class TileQueryHelper { mSpecs.clear(); mFinished = false; // Enqueue jobs to fetch every system tile and then ever package tile. - addStockTiles(host); + addCurrentAndStockTiles(host); + addPackageTiles(host); } @@ -77,16 +79,28 @@ public class TileQueryHelper { return mFinished; } - private void addStockTiles(QSTileHost host) { - String possible = mContext.getString(R.string.quick_settings_tiles_stock); + private void addCurrentAndStockTiles(QSTileHost host) { + String stock = mContext.getString(R.string.quick_settings_tiles_stock); + String current = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.QS_TILES); final ArrayList<String> possibleTiles = new ArrayList<>(); - possibleTiles.addAll(Arrays.asList(possible.split(","))); - if (Build.IS_DEBUGGABLE) { + if (current != null) { + // The setting QS_TILES is not populated immediately upon Factory Reset + possibleTiles.addAll(Arrays.asList(current.split(","))); + } + String[] stockSplit = stock.split(","); + for (String spec : stockSplit) { + if (!current.contains(spec)) { + possibleTiles.add(spec); + } + } + if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) { possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC); } final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); for (String spec : possibleTiles) { + // Only add current and stock tiles that can be created from QSFactoryImpl final QSTile tile = host.createTile(spec); if (tile == null) { continue; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index b2f60436fecd..7d52f0b2763d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -114,7 +114,7 @@ public class QSFactoryImpl implements QSFactory { } // Broken tiles. - Log.w(TAG, "Bad tile spec: " + tileSpec); + Log.w(TAG, "No stock tile spec: " + tileSpec); return null; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index c62a592be8e2..3ab1c21b5066 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -205,7 +205,10 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } else { final BluetoothClass bluetoothClass = lastDevice.getBtClass(); if (bluetoothClass != null) { - if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { + if (lastDevice.isHearingAidDevice()) { + return mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_hearing_aids); + } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { return mContext.getString( R.string.quick_settings_bluetooth_secondary_label_audio); } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 1b89324209de..12b6f673de1c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -43,6 +43,7 @@ import android.provider.Settings; import android.util.Log; import android.view.MotionEvent; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.Prefs; @@ -97,6 +98,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private int mCurrentBoundedUserId = -1; private float mBackButtonAlpha; private MotionEvent mStatusBarGestureDownEvent; + private float mWindowCornerRadius; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -228,6 +230,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public float getWindowCornerRadius() { + if (!verifyCaller("getWindowCornerRadius")) { + return 0; + } + long token = Binder.clearCallingIdentity(); + try { + return mWindowCornerRadius; + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -334,6 +348,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis .setPackage(mRecentsComponentName.getPackageName()); mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS, getDefaultInteractionFlags()); + mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()); // Listen for the package update changes. if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 0c8f48752455..8b9399536969 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -21,7 +21,7 @@ import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; import android.app.StatusBarManager; import android.content.ComponentName; import android.graphics.Rect; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -96,6 +96,7 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT; + private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -163,12 +164,13 @@ public class CommandQueue extends IStatusBar.Stub { default void onRotationProposal(int rotation, boolean isValid) { } - default void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, + default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { } default void onBiometricAuthenticated() { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } default void hideBiometricDialog() { } + default void showBiometricTryAgain() { } } @VisibleForTesting @@ -523,8 +525,8 @@ public class CommandQueue extends IStatusBar.Stub { } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, - boolean requireConfirmation, int userId) { + public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int type, boolean requireConfirmation, int userId) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; @@ -565,6 +567,13 @@ public class CommandQueue extends IStatusBar.Stub { } } + @Override + public void showBiometricTryAgain() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -774,7 +783,7 @@ public class CommandQueue extends IStatusBar.Stub { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).showBiometricDialog( (Bundle) someArgs.arg1, - (IBiometricPromptReceiver) someArgs.arg2, + (IBiometricServiceReceiverInternal) someArgs.arg2, someArgs.argi1 /* type */, (boolean) someArgs.arg3 /* requireConfirmation */, someArgs.argi2 /* userId */); @@ -816,6 +825,11 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.get(i).showPinningEscapeToast(); } break; + case MSG_BIOMETRIC_TRY_AGAIN: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showBiometricTryAgain(); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index f0e5462bc167..f506753379a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -29,6 +29,7 @@ import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Interpolators; import com.android.systemui.shared.system.SurfaceControlCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; @@ -55,6 +56,7 @@ public class ActivityLaunchAnimator { private final NotificationPanelView mNotificationPanel; private final NotificationListContainer mNotificationContainer; private final StatusBarWindowView mStatusBarWindow; + private final float mWindowCornerRadius; private Callback mCallback; private final Runnable mTimeoutRunnable = () -> { setAnimationPending(false); @@ -72,6 +74,8 @@ public class ActivityLaunchAnimator { mNotificationContainer = container; mStatusBarWindow = statusBarWindow; mCallback = callback; + mWindowCornerRadius = ScreenDecorationsUtils + .getWindowCornerRadius(statusBarWindow.getResources()); } public RemoteAnimationAdapter getLaunchAnimation( @@ -124,6 +128,8 @@ public class ActivityLaunchAnimator { private final ExpandableNotificationRow mSourceNotification; private final ExpandAnimationParameters mParams; private final Rect mWindowCrop = new Rect(); + private final float mNotificationCornerRadius; + private float mCornerRadius; private boolean mIsFullScreenLaunch = true; private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier; @@ -131,6 +137,8 @@ public class ActivityLaunchAnimator { mSourceNotification = sourceNofitication; mParams = new ExpandAnimationParameters(); mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification); + mNotificationCornerRadius = Math.max(mSourceNotification.getCurrentTopRoundness(), + mSourceNotification.getCurrentBottomRoundness()); } @Override @@ -181,8 +189,7 @@ public class ActivityLaunchAnimator { @Override public void onAnimationUpdate(ValueAnimator animation) { mParams.linearProgress = animation.getAnimatedFraction(); - float progress - = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + float progress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( mParams.linearProgress); int newWidth = (int) MathUtils.lerp(notificationWidth, targetWidth, progress); @@ -194,6 +201,8 @@ public class ActivityLaunchAnimator { + notificationHeight, primary.position.y + primary.sourceContainerBounds.bottom, progress); + mCornerRadius = MathUtils.lerp(mNotificationCornerRadius, + mWindowCornerRadius, progress); applyParamsToWindow(primary); applyParamsToNotification(mParams); applyParamsToNotificationList(mParams); @@ -259,7 +268,7 @@ public class ActivityLaunchAnimator { m.postTranslate(0, (float) (mParams.top - app.position.y)); mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight()); SurfaceParams params = new SurfaceParams(new SurfaceControlCompat(app.leash), - 1f /* alpha */, m, mWindowCrop, app.prefixOrderIndex); + 1f /* alpha */, m, mWindowCrop, app.prefixOrderIndex, mCornerRadius); mSyncRtTransactionApplier.scheduleApply(params); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 713bd90b30c3..fef28cf1393e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -375,12 +375,9 @@ public class StackStateAnimator { // Find the amount to translate up. This is needed in order to understand the // direction of the remove animation (either downwards or upwards) - ExpandableViewState viewState = - ((ExpandableView) event.viewAfterChangingView).getViewState(); - int actualHeight = changingView.getActualHeight(); // upwards by default float translationDirection = -1.0f; - if (viewState != null) { + if (event.viewAfterChangingView != null) { float ownPosition = changingView.getTranslationY(); if (changingView instanceof ExpandableNotificationRow && event.viewAfterChangingView instanceof ExpandableNotificationRow) { @@ -396,8 +393,11 @@ public class StackStateAnimator { ownPosition = changingRow.getTranslationWhenRemoved(); } } + int actualHeight = changingView.getActualHeight(); // there was a view after this one, Approximate the distance the next child // travelled + ExpandableViewState viewState = + ((ExpandableView) event.viewAfterChangingView).getViewState(); translationDirection = ((viewState.yTranslation - (ownPosition + actualHeight / 2.0f)) * 2 / actualHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 33d022c83c16..cd6e1d794428 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON; @@ -950,10 +951,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private void updateTaskSwitchHelper() { if (mGestureHelper == null) return; boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); - int navBarPos = 0; + int navBarPos = NAV_BAR_INVALID; try { - // TODO: Use WindowManagerService.getNavBarPosition(int displayId) - navBarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition(); + navBarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition( + mDisplay.getDisplayId()); } catch (RemoteException e) { Slog.e(TAG, "Failed to get nav bar position.", e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index 497fdfb2deb1..43e86d66b8be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; @@ -421,6 +422,9 @@ public class QuickStepController implements GestureHelper { mDragHPositive = !isRTL; mDragVPositive = true; break; + case NAV_BAR_INVALID: + Log.e(TAG, "Invalid nav bar position"); + break; } for (NavigationGestureAction action: mGestureActions) { 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 408ab42a6f06..cfd53be8979b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1211,7 +1211,8 @@ public class StatusBar extends SystemUI implements DemoMode, } int dockSide = WindowManagerProxy.getInstance().getDockSide(); if (dockSide == WindowManager.DOCKED_INVALID) { - final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(); + final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition( + mDisplay.getDisplayId()); if (navbarPos == NAV_BAR_POS_INVALID) { return false; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index f63d2360d976..26fa20de4e86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -14,41 +14,76 @@ package com.android.systemui.qs.customize; -import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.content.pm.PackageManager; +import android.provider.Settings; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import android.text.TextUtils; +import android.util.ArraySet; import com.android.systemui.Dependency; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSTileHost; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper public class TileQueryHelperTest extends SysuiTestCase { - @Mock private TileQueryHelper.TileStateListener mListener; - @Mock private QSTileHost mQSTileHost; + private static final String CURRENT_TILES = "wifi,dnd,nfc"; + private static final String ONLY_STOCK_TILES = "wifi,dnd"; + private static final String WITH_OTHER_TILES = "wifi,dnd,other"; + // Note no nfc in stock tiles + private static final String STOCK_TILES = "wifi,dnd,cell,battery"; + private static final String ALL_TILES = "wifi,dnd,nfc,cell,battery"; + private static final Set<String> FACTORY_TILES = new ArraySet<>(); + + static { + FACTORY_TILES.addAll(Arrays.asList( + new String[]{"wifi", "bt", "cell", "dnd", "inversion", "airplane", "work", + "rotation", "flashlight", "location", "cast", "hotspot", "user", "battery", + "saver", "night", "nfc"})); + } - private TestableLooper mBGLooper; + @Mock + private TileQueryHelper.TileStateListener mListener; + @Mock + private QSTileHost mQSTileHost; + @Mock + private PackageManager mPackageManager; + private QSTile.State mState; + private TestableLooper mBGLooper; private TileQueryHelper mTileQueryHelper; @Before @@ -56,6 +91,23 @@ public class TileQueryHelperTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mBGLooper = TestableLooper.get(this); mDependency.injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper()); + mContext.setMockPackageManager(mPackageManager); + + mState = new QSTile.State(); + doAnswer(invocation -> { + String spec = (String) invocation.getArguments()[0]; + if (FACTORY_TILES.contains(spec)) { + QSTile m = mock(QSTile.class); + when(m.isAvailable()).thenReturn(true); + when(m.getTileSpec()).thenReturn(spec); + when(m.getState()).thenReturn(mState); + return m; + } else { + return null; + } + } + ).when(mQSTileHost).createTile(anyString()); + mTileQueryHelper = new TileQueryHelper(mContext, mListener); } @@ -98,4 +150,72 @@ public class TileQueryHelperTest extends SysuiTestCase { assertTrue(mTileQueryHelper.isFinished()); } + + @Test + public void testQueryTiles_correctTilesAndOrderOnlyStockTiles() { + ArgumentCaptor<List<TileQueryHelper.TileInfo>> captor = ArgumentCaptor.forClass(List.class); + + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, + ONLY_STOCK_TILES); + mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, + STOCK_TILES); + + mTileQueryHelper.queryTiles(mQSTileHost); + + mBGLooper.processAllMessages(); + waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER)); + + verify(mListener, atLeastOnce()).onTilesChanged(captor.capture()); + List<String> specs = new ArrayList<>(); + for (TileQueryHelper.TileInfo t : captor.getValue()) { + specs.add(t.spec); + } + String tiles = TextUtils.join(",", specs); + assertThat(tiles, is(equalTo(STOCK_TILES))); + } + + @Test + public void testQueryTiles_correctTilesAndOrderOtherFactoryTiles() { + ArgumentCaptor<List<TileQueryHelper.TileInfo>> captor = ArgumentCaptor.forClass(List.class); + + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, + CURRENT_TILES); + mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, + STOCK_TILES); + + mTileQueryHelper.queryTiles(mQSTileHost); + + mBGLooper.processAllMessages(); + waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER)); + + verify(mListener, atLeastOnce()).onTilesChanged(captor.capture()); + List<String> specs = new ArrayList<>(); + for (TileQueryHelper.TileInfo t : captor.getValue()) { + specs.add(t.spec); + } + String tiles = TextUtils.join(",", specs); + assertThat(tiles, is(equalTo(ALL_TILES))); + } + + @Test + public void testQueryTiles_otherTileNotIncluded() { + ArgumentCaptor<List<TileQueryHelper.TileInfo>> captor = ArgumentCaptor.forClass(List.class); + + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, + WITH_OTHER_TILES); + mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, + STOCK_TILES); + + mTileQueryHelper.queryTiles(mQSTileHost); + + mBGLooper.processAllMessages(); + waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER)); + + verify(mListener, atLeastOnce()).onTilesChanged(captor.capture()); + List<String> specs = new ArrayList<>(); + for (TileQueryHelper.TileInfo t : captor.getValue()) { + specs.add(t.spec); + } + assertFalse(specs.contains("other")); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java index 435ede4d8ff0..55583931af43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java @@ -62,6 +62,7 @@ public class ActivityLaunchAnimatorTest extends SysuiTestCase { @Before public void setUp() throws Exception { + when(mStatusBarWindowView.getResources()).thenReturn(mContext.getResources()); when(mCallback.areLaunchAnimationsEnabled()).thenReturn(true); mLaunchAnimator = new ActivityLaunchAnimator( mStatusBarWindowView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 21047217e942..59a49378e9e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.app.Notification; import android.app.StatusBarManager; @@ -63,9 +64,11 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mContext.putComponent(CommandQueue.class, mCommandQueue); mDependency.injectTestDependency(ShadeController.class, mShadeController); + StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class); + when(statusBarWindowView.getResources()).thenReturn(mContext.getResources()); mStatusBar = new StatusBarNotificationPresenter(mContext, mock(NotificationPanelView.class), mock(HeadsUpManagerPhone.class), - mock(StatusBarWindowView.class), mock(NotificationListContainerViewGroup.class), + statusBarWindowView, mock(NotificationListContainerViewGroup.class), mock(DozeScrimController.class), mock(ScrimController.class), mock(ActivityLaunchAnimator.Callback.class)); } diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 356a4daba66e..8d912fadf6d1 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -692,7 +692,7 @@ public class AppOpsService extends IAppOpsService.Stub { } }); - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { StorageManagerInternal storageManagerInternal = LocalServices.getService( StorageManagerInternal.class); storageManagerInternal.addExternalStoragePolicy( diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java index dd960751ab21..98203213e996 100644 --- a/services/core/java/com/android/server/BinderCallsStatsService.java +++ b/services/core/java/com/android/server/BinderCallsStatsService.java @@ -56,6 +56,7 @@ public class BinderCallsStatsService extends Binder { private static final String SETTINGS_DETAILED_TRACKING_KEY = "detailed_tracking"; private static final String SETTINGS_UPLOAD_DATA_KEY = "upload_data"; private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; + private static final String SETTINGS_MAX_CALL_STATS_KEY = "max_call_stats_count"; private boolean mEnabled; private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS); @@ -97,6 +98,9 @@ public class BinderCallsStatsService extends Binder { mBinderCallsStats.setSamplingInterval(mParser.getInt( SETTINGS_SAMPLING_INTERVAL_KEY, BinderCallsStats.PERIODIC_SAMPLING_INTERVAL_DEFAULT)); + mBinderCallsStats.setSamplingInterval(mParser.getInt( + SETTINGS_MAX_CALL_STATS_KEY, + BinderCallsStats.MAX_BINDER_CALL_STATS_COUNT_DEFAULT)); final boolean enabled = diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 0e6f8dda44f6..e933bd0bc7ff 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -183,8 +183,7 @@ class StorageManagerService extends IStorageManager.Stub private static final String ZRAM_ENABLED_PROPERTY = "persist.sys.zram_enabled"; - private static final boolean ENABLE_ISOLATED_STORAGE = SystemProperties - .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false); + private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage(); public static class Lifecycle extends SystemService { private StorageManagerService mStorageManagerService; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 80f47d5a000b..0e354d515eef 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -328,6 +328,7 @@ import com.android.server.AlarmManagerInternal; import com.android.server.AppOpsService; import com.android.server.AttributeCache; import com.android.server.DeviceIdleController; +import com.android.server.DisplayThread; import com.android.server.IntentResolver; import com.android.server.IoThread; import com.android.server.LocalServices; @@ -2262,7 +2263,8 @@ public class ActivityManagerService extends IActivityManager.Stub mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mActivityTaskManager = atm; - mActivityTaskManager.setActivityManagerService(mIntentFirewall, mPendingIntentController); + mActivityTaskManager.initialize(mIntentFirewall, mPendingIntentController, + DisplayThread.get().getLooper()); mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mProcessCpuThread = new Thread("CpuTracker") { @@ -19346,7 +19348,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public boolean isAppStorageSandboxed(int pid, int uid) { - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { return false; } if (uid == SHELL_UID || uid == ROOT_UID) { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index a0977be0e25d..8c39d75ea6a4 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -58,8 +58,9 @@ import java.util.Set; /** * BROADCASTS * - * We keep two broadcast queues and associated bookkeeping, one for those at - * foreground priority, and one for normal (background-priority) broadcasts. + * We keep three broadcast queues and associated bookkeeping, one for those at + * foreground priority, and one for normal (background-priority) broadcasts, and one to + * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED. */ public final class BroadcastQueue { private static final String TAG = "BroadcastQueue"; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4b19398cc73d..7991783e08c8 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -27,7 +27,6 @@ import static android.os.Process.getFreeMemory; import static android.os.Process.getTotalMemory; import static android.os.Process.killProcessQuiet; import static android.os.Process.startWebView; -import static android.os.storage.StorageManager.PROP_ISOLATED_STORAGE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; @@ -73,6 +72,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.text.TextUtils; import android.util.EventLog; @@ -1281,8 +1281,7 @@ public final class ProcessList { final IPackageManager pm = AppGlobals.getPackageManager(); permGids = pm.getPackageGids(app.info.packageName, MATCH_DIRECT_BOOT_AUTO, app.userId); - if (SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false) - && mountExtStorageFull) { + if (StorageManager.hasIsolatedStorage() && mountExtStorageFull) { mountExternal = Zygote.MOUNT_EXTERNAL_FULL; } else { StorageManagerInternal storageManagerInternal = LocalServices.getService( diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 2c2d4045b205..eaa7a83fab0d 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -19,19 +19,11 @@ package com.android.server.biometrics; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.BiometricPrompt; -import android.hardware.biometrics.IBiometricPromptReceiver; -import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.security.KeyStore; -import android.text.TextUtils; import android.util.Slog; -import com.android.internal.statusbar.IStatusBarService; - import java.util.ArrayList; /** @@ -39,88 +31,15 @@ import java.util.ArrayList; */ public abstract class AuthenticationClient extends ClientMonitor { private long mOpId; - private Handler mHandler; public abstract int handleFailedAttempt(); public abstract void resetFailedAttempts(); - public abstract String getErrorString(int error, int vendorCode); - public abstract String getAcquiredString(int acquireInfo, int vendorCode); - /** - * @return one of {@link #TYPE_FINGERPRINT} {@link #TYPE_IRIS} or {@link #TYPE_FACE} - */ - public abstract int getBiometricType(); public static final int LOCKOUT_NONE = 0; public static final int LOCKOUT_TIMED = 1; public static final int LOCKOUT_PERMANENT = 2; private final boolean mRequireConfirmation; - // Callback mechanism received from the client - // (BiometricPrompt -> BiometricPromptService -> <Biometric>Service -> AuthenticationClient) - private IBiometricPromptReceiver mDialogReceiverFromClient; - private Bundle mBundle; - private IStatusBarService mStatusBarService; - private boolean mInLockout; - private TokenEscrow mEscrow; - protected boolean mDialogDismissed; - - /** - * Container that holds the identifier and authToken. For biometrics that require user - * confirmation, these should not be sent to their final destinations until the user confirms. - */ - class TokenEscrow { - final BiometricAuthenticator.Identifier mIdentifier; - final ArrayList<Byte> mToken; - - TokenEscrow(BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) { - mIdentifier = identifier; - mToken = token; - } - - BiometricAuthenticator.Identifier getIdentifier() { - return mIdentifier; - } - - ArrayList<Byte> getToken() { - return mToken; - } - } - - // Receives events from SystemUI and handles them before forwarding them to BiometricDialog - protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() { - @Override // binder call - public void onDialogDismissed(int reason) { - if (mBundle != null && mDialogReceiverFromClient != null) { - try { - if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Positive button is used by passive modalities as a "confirm" button, - // do not send to client - mDialogReceiverFromClient.onDialogDismissed(reason); - } - if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) { - onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, - 0 /* vendorCode */); - } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Have the service send the token to KeyStore, and send onAuthenticated - // to the application. - if (mEscrow != null) { - if (DEBUG) Slog.d(getLogTag(), "Confirmed"); - addTokenToKeyStore(mEscrow.getToken()); - notifyClientAuthenticationSucceeded(mEscrow.getIdentifier()); - mEscrow = null; - onAuthenticationConfirmed(); - } else { - Slog.e(getLogTag(), "Escrow is null!!!"); - } - } - mDialogDismissed = true; - } catch (RemoteException e) { - Slog.e(getLogTag(), "Remote exception", e); - } - stop(true /* initiatedByClient */); - } - } - }; /** * This method is called when authentication starts. @@ -133,25 +52,13 @@ public abstract class AuthenticationClient extends ClientMonitor { */ public abstract void onStop(); - /** - * This method is called when biometric authentication was confirmed by the user. The client - * should be removed. - */ - public abstract void onAuthenticationConfirmed(); - public AuthenticationClient(Context context, Metrics metrics, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, - boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation) { super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId, - restricted, owner); + restricted, owner, cookie); mOpId = opId; - mBundle = bundle; - mDialogReceiverFromClient = dialogReceiver; - mStatusBarService = statusBarService; - mHandler = new Handler(Looper.getMainLooper()); mRequireConfirmation = requireConfirmation; } @@ -164,175 +71,99 @@ public abstract class AuthenticationClient extends ClientMonitor { stop(false /* initiatedByClient */); } - @Override - public boolean onAcquired(int acquiredInfo, int vendorCode) { - // If the dialog is showing, the client doesn't need to receive onAcquired messages. - if (mBundle != null) { - try { - if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { - mStatusBarService.onBiometricHelp(getAcquiredString(acquiredInfo, vendorCode)); - } - return false; // acquisition continues - } catch (RemoteException e) { - Slog.e(getLogTag(), "Remote exception when sending acquired message", e); - return true; // client failed - } finally { - // Good scans will keep the device awake - if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { - notifyUserActivity(); - } - } - } else { - return super.onAcquired(acquiredInfo, vendorCode); - } - } - - @Override - public boolean onError(long deviceId, int error, int vendorCode) { - if (mDialogDismissed) { - // If user cancels authentication, the application has already received the - // ERROR_USER_CANCELED message from onDialogDismissed() - // and stopped the biometric hardware, so there is no need to send a - // ERROR_CANCELED message. - return true; - } - if (mBundle != null && error != BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED) { - try { - mStatusBarService.onBiometricError(getErrorString(error, vendorCode)); - } catch (RemoteException e) { - Slog.e(getLogTag(), "Remote exception when sending error", e); - } - } - return super.onError(deviceId, error, vendorCode); - } - - public void setTitleIfEmpty(CharSequence title) { - if (TextUtils.isEmpty(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE))) { - mBundle.putCharSequence(BiometricPrompt.KEY_TITLE, title); - } - } - public boolean isBiometricPrompt() { - return mBundle != null; - } - - private void notifyClientAuthenticationSucceeded(BiometricAuthenticator.Identifier identifier) - throws RemoteException { - final BiometricServiceBase.ServiceListener listener = getListener(); - // Explicitly have if/else here to make it super obvious in case the code is - // touched in the future. - if (!getIsRestricted()) { - listener.onAuthenticationSucceeded( - getHalDeviceId(), identifier, getTargetUserId()); - } else { - listener.onAuthenticationSucceeded( - getHalDeviceId(), null, getTargetUserId()); - } + return getCookie() != 0; } - private void addTokenToKeyStore(ArrayList<Byte> token) { - // Send the token to KeyStore - final byte[] byteToken = new byte[token.size()]; - for (int i = 0; i < token.size(); i++) { - byteToken[i] = token.get(i); - } - KeyStore.getInstance().addAuthToken(byteToken); + public boolean getRequireConfirmation() { + return mRequireConfirmation; } @Override public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> token) { - if (authenticated) { - mAlreadyDone = true; - if (mRequireConfirmation) { - // Store the token so it can be sent to keystore after the user presses confirm - mEscrow = new TokenEscrow(identifier, token); - } else { - addTokenToKeyStore(token); - } - } + final BiometricServiceBase.ServiceListener listener = getListener(); + mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated); boolean result = false; - // If the biometric dialog is showing, notify authentication succeeded - if (mBundle != null) { - try { - if (authenticated) { - mStatusBarService.onBiometricAuthenticated(); - } else { - mStatusBarService.onBiometricHelp(getContext().getResources().getString( - com.android.internal.R.string.biometric_not_recognized)); + try { + if (authenticated) { + mAlreadyDone = true; + if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + getOwnerString() + + ", ID:" + identifier.getBiometricId() + + ", isBP: " + isBiometricPrompt() + + ", listener: " + listener + + ", requireConfirmation: " + mRequireConfirmation); + if (listener != null) { + vibrateSuccess(); } - } catch (RemoteException e) { - Slog.e(getLogTag(), "Failed to notify Authenticated:", e); - } - } + result = true; + resetFailedAttempts(); + onStop(); - final BiometricServiceBase.ServiceListener listener = getListener(); - if (listener != null) { - try { - mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated); - if (!authenticated) { - listener.onAuthenticationFailed(getHalDeviceId()); - } else { - if (DEBUG) { - Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString() - + ", id=" + identifier.getBiometricId()); - } - if (!mRequireConfirmation) { - notifyClientAuthenticationSucceeded(identifier); + final byte[] byteToken = new byte[token.size()]; + for (int i = 0; i < token.size(); i++) { + byteToken[i] = token.get(i); + } + if (isBiometricPrompt() && listener != null) { + // BiometricService will add the token to keystore + listener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken); + } else if (!isBiometricPrompt() && listener != null) { + KeyStore.getInstance().addAuthToken(byteToken); + try { + // Explicitly have if/else here to make it super obvious in case the code is + // touched in the future. + if (!getIsRestricted()) { + listener.onAuthenticationSucceeded( + getHalDeviceId(), identifier, getTargetUserId()); + } else { + listener.onAuthenticationSucceeded( + getHalDeviceId(), null, getTargetUserId()); + } + } catch (RemoteException e) { + Slog.e(getLogTag(), "Remote exception", e); } + } else { + // Client not listening + Slog.w(getLogTag(), "Client not listening"); + result = true; } - } catch (RemoteException e) { - Slog.w(getLogTag(), "Failed to notify Authenticated:", e); - result = true; // client failed - } - } else { - result = true; // client not listening - } - if (!authenticated) { - if (listener != null) { - vibrateError(); - } - // allow system-defined limit of number of attempts before giving up - int lockoutMode = handleFailedAttempt(); - if (lockoutMode != LOCKOUT_NONE) { - try { - mInLockout = true; - Slog.w(getLogTag(), "Forcing lockout (fp driver code should do this!), mode(" + - lockoutMode + ")"); + } else { + if (listener != null) { + vibrateError(); + } + // Allow system-defined limit of number of attempts before giving up + final int lockoutMode = handleFailedAttempt(); + if (lockoutMode != LOCKOUT_NONE) { + Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode(" + + lockoutMode + ")"); stop(false); - int errorCode = lockoutMode == LOCKOUT_TIMED ? - BiometricConstants.BIOMETRIC_ERROR_LOCKOUT : - BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; - - // Send the lockout message to the system dialog - if (mBundle != null) { - mStatusBarService.onBiometricError( - getErrorString(errorCode, 0 /* vendorCode */)); - mHandler.postDelayed(() -> { - try { - listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */); - } catch (RemoteException e) { - Slog.w(getLogTag(), "RemoteException while sending error"); - } - }, BiometricPrompt.HIDE_DIALOG_DELAY); - } else { - listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */); + final int errorCode = lockoutMode == LOCKOUT_TIMED + ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT + : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; + if (listener != null) { + listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */, + getCookie()); + } + } else { + // Don't send onAuthenticationFailed if we're in lockout, it causes a + // janky UI on Keyguard/BiometricPrompt since "authentication failed" + // will show briefly and be replaced by "device locked out" message. + if (listener != null) { + if (isBiometricPrompt()) { + listener.onAuthenticationFailedInternal(getCookie(), + getRequireConfirmation()); + } else { + listener.onAuthenticationFailed(getHalDeviceId()); + } } - } catch (RemoteException e) { - Slog.w(getLogTag(), "Failed to notify lockout:", e); } + result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode } - result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode - } else { - if (listener != null) { - vibrateSuccess(); - } - // we have a valid biometric that doesn't require confirmation, done - result |= !mRequireConfirmation; - resetFailedAttempts(); - onStop(); + } catch (RemoteException e) { + Slog.e(getLogTag(), "Remote exception", e); + result = true; } return result; } @@ -353,16 +184,6 @@ public abstract class AuthenticationClient extends ClientMonitor { return result; } if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating..."); - - // If authenticating with system dialog, show the dialog - if (mBundle != null) { - try { - mStatusBarService.showBiometricDialog(mBundle, mDialogReceiver, - getBiometricType(), mRequireConfirmation, getTargetUserId()); - } catch (RemoteException e) { - Slog.e(getLogTag(), "Unable to show biometric dialog", e); - } - } } catch (RemoteException e) { Slog.e(getLogTag(), "startAuthentication failed", e); return ERROR_ESRCH; @@ -390,18 +211,6 @@ public abstract class AuthenticationClient extends ClientMonitor { } catch (RemoteException e) { Slog.e(getLogTag(), "stopAuthentication failed", e); return ERROR_ESRCH; - } finally { - // If the user already cancelled authentication (via some interaction with the - // dialog, we do not need to hide it since it's already hidden. - // If the device is in lockout, don't hide the dialog - it will automatically hide - // after BiometricPrompt.HIDE_DIALOG_DELAY - if (mBundle != null && !mDialogDismissed && !mInLockout) { - try { - mStatusBarService.hideBiometricDialog(); - } catch (RemoteException e) { - Slog.e(getLogTag(), "Unable to hide biometric dialog", e); - } - } } mAlreadyCancelled = true; diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 5f09189bd84a..add55eaad166 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -19,9 +19,17 @@ package com.android.server.biometrics; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.app.AppOpsManager; +import android.app.IActivityTaskManager; +import android.app.TaskStackListener; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.Context; @@ -32,9 +40,9 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; -import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; import android.hardware.fingerprint.FingerprintManager; @@ -50,14 +58,21 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; +import android.security.KeyStore; +import android.text.TextUtils; import android.util.Pair; import android.util.Slog; import com.android.internal.R; +import com.android.internal.statusbar.IStatusBarService; import com.android.server.SystemService; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Random; /** * System service that arbitrates the modality for BiometricPrompt to use. @@ -66,32 +81,10 @@ public class BiometricService extends SystemService { private static final String TAG = "BiometricService"; - /** - * No biometric methods or nothing has been enrolled. - * Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist" - * modalities when calling authenticate(). - */ - private static final int BIOMETRIC_NONE = 0; - - /** - * Constant representing fingerprint. - */ - private static final int BIOMETRIC_FINGERPRINT = 1 << 0; - - /** - * Constant representing iris. - */ - private static final int BIOMETRIC_IRIS = 1 << 1; - - /** - * Constant representing face. - */ - private static final int BIOMETRIC_FACE = 1 << 2; - private static final int[] FEATURE_ID = { - BIOMETRIC_FINGERPRINT, - BIOMETRIC_IRIS, - BIOMETRIC_FACE + TYPE_FINGERPRINT, + TYPE_IRIS, + TYPE_FACE }; private final AppOpsManager mAppOps; @@ -242,10 +235,367 @@ public class BiometricService extends SystemService { */ private final class BiometricServiceWrapper extends IBiometricService.Stub { + /** + * Authentication either just called and we have not transitioned to the CALLED state, or + * authentication terminated (success or error). + */ + private static final int STATE_AUTH_IDLE = 0; + /** + * Authentication was called and we are waiting for the <Biometric>Services to return their + * cookies before starting the hardware and showing the BiometricPrompt. + */ + private static final int STATE_AUTH_CALLED = 1; + /** + * Authentication started, BiometricPrompt is showing and the hardware is authenticating. + */ + private static final int STATE_AUTH_STARTED = 2; + /** + * Authentication is paused, waiting for the user to press "try again" button. Since the + * try again button requires us to cancel authentication, this represents the state where + * ERROR_CANCELED is not received yet. + */ + private static final int STATE_AUTH_PAUSED = 3; + /** + * Same as above, except the ERROR_CANCELED has been received. + */ + private static final int STATE_AUTH_PAUSED_CANCELED = 4; + /** + * Authentication is successful, but we're waiting for the user to press "confirm" button. + */ + private static final int STATE_AUTH_PENDING_CONFIRM = 5; + + final class AuthSession { + // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from + // <Biometric>Services before we can start authenticating. Pairs that have been returned + // are moved to mModalitiesMatched. + final HashMap<Integer, Integer> mModalitiesWaiting; + // Pairs that have been matched. + final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>(); + + // The following variables are passed to authenticateInternal, which initiates the + // appropriate <Biometric>Services. + final IBinder mToken; + final long mSessionId; + final int mUserId; + // Original receiver from BiometricPrompt. + final IBiometricServiceReceiver mClientReceiver; + final String mOpPackageName; + // Info to be shown on BiometricDialog when all cookies are returned. + final Bundle mBundle; + final int mCallingUid; + final int mCallingPid; + final int mCallingUserId; + // Continue authentication with the same modality/modalities after "try again" is + // pressed + final int mModality; + + // The current state, which can be either idle, called, or started + private int mState = STATE_AUTH_IDLE; + // For explicit confirmation, do not send to keystore until the user has confirmed + // the authentication. + byte[] mTokenEscrow; + + AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId, + int userId, IBiometricServiceReceiver receiver, String opPackageName, + Bundle bundle, int callingUid, int callingPid, int callingUserId, + int modality) { + mModalitiesWaiting = modalities; + mToken = token; + mSessionId = sessionId; + mUserId = userId; + mClientReceiver = receiver; + mOpPackageName = opPackageName; + mBundle = bundle; + mCallingUid = callingUid; + mCallingPid = callingPid; + mCallingUserId = callingUserId; + mModality = modality; + } + + boolean containsCookie(int cookie) { + if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) { + return true; + } + if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) { + return true; + } + return false; + } + } + + final class BiometricTaskStackListener extends TaskStackListener { + @Override + public void onTaskStackChanged() { + try { + final List<ActivityManager.RunningTaskInfo> runningTasks = + mActivityTaskManager.getTasks(1); + if (!runningTasks.isEmpty()) { + final String topPackage = runningTasks.get(0).topActivity.getPackageName(); + if (mCurrentAuthSession != null + && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName) + && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + // We only care about this state, since <Biometric>Service will + // cancel any client that's still in STATE_AUTH_STARTED + mStatusBarService.hideBiometricDialog(); + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_canceled) + ); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to get running tasks", e); + } + } + } + + private final IActivityTaskManager mActivityTaskManager = getContext().getSystemService( + ActivityTaskManager.class).getService(); + private final IStatusBarService mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + private final BiometricTaskStackListener mTaskStackListener = + new BiometricTaskStackListener(); + private final Random mRandom = new Random(); + + // The current authentication session, null if idle/done. We need to track both the current + // and pending sessions since errors may be sent to either. + private AuthSession mCurrentAuthSession; + private AuthSession mPendingAuthSession; + + // Wrap the client's receiver so we can do things with the BiometricDialog first + private final IBiometricServiceReceiverInternal mInternalReceiver = + new IBiometricServiceReceiverInternal.Stub() { + @Override + public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token) + throws RemoteException { + try { + if (!requireConfirmation) { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + KeyStore.getInstance().addAuthToken(token); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } else { + // Store the auth token and submit it to keystore after the confirmation + // button has been pressed. + mCurrentAuthSession.mTokenEscrow = token; + mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM; + } + + // Notify SysUI that the biometric has been authenticated. SysUI already knows + // the implicit/explicit state and will react accordingly. + mStatusBarService.onBiometricAuthenticated(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onAuthenticationFailed(int cookie, boolean requireConfirmation) + throws RemoteException { + try { + mStatusBarService.onBiometricHelp(getContext().getResources().getString( + com.android.internal.R.string.biometric_not_recognized)); + if (requireConfirmation) { + mCurrentAuthSession.mState = STATE_AUTH_PAUSED; + mStatusBarService.showBiometricTryAgain(); + // Cancel authentication. Skip the token/package check since we are + // cancelling from system server. The interface is permission protected so + // this is fine. + cancelInternal(null /* token */, null /* package */, + false /* fromClient */); + } + mCurrentAuthSession.mClientReceiver.onAuthenticationFailed(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onError(int cookie, int error, String message) throws RemoteException { + Slog.d(TAG, "Error: " + error + " cookie: " + cookie); + // Errors can either be from the current auth session or the pending auth session. + // The pending auth session may receive errors such as ERROR_LOCKOUT before + // it becomes the current auth session. Similarly, the current auth session may + // receive errors such as ERROR_CANCELED while the pending auth session is preparing + // to be started. Thus we must match error messages with their cookies to be sure + // of their intended receivers. + try { + if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { + if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { + mStatusBarService.onBiometricError(message); + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { + mCurrentAuthSession.mClientReceiver.onError(error, message); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + mStatusBarService.hideBiometricDialog(); + } else { + // Send errors after the dialog is dismissed. + mHandler.postDelayed(() -> { + try { + mCurrentAuthSession.mClientReceiver.onError(error, message); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + }, BiometricPrompt.HIDE_DIALOG_DELAY); + } + } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED + || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED) { + if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED + && error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { + // Skip the first ERROR_CANCELED message when this happens, since + // "try again" requires us to cancel authentication but keep + // the prompt showing. + mCurrentAuthSession.mState = STATE_AUTH_PAUSED_CANCELED; + } else { + // In the "try again" state, we should forward canceled errors to + // the client and and clean up. + mCurrentAuthSession.mClientReceiver.onError(error, message); + mStatusBarService.onBiometricError(message); + mActivityTaskManager.unregisterTaskStackListener( + mTaskStackListener); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + } else { + Slog.e(TAG, "Impossible session error state: " + + mCurrentAuthSession.mState); + } + } else if (mPendingAuthSession != null + && mPendingAuthSession.containsCookie(cookie)) { + if (mPendingAuthSession.mState == STATE_AUTH_CALLED) { + mPendingAuthSession.mClientReceiver.onError(error, message); + mPendingAuthSession.mState = STATE_AUTH_IDLE; + mPendingAuthSession = null; + } else { + Slog.e(TAG, "Impossible pending session error state: " + + mPendingAuthSession.mState); + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onAcquired(int acquiredInfo, String message) throws RemoteException { + if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { + try { + mStatusBarService.onBiometricHelp(message); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + } + + @Override + public void onDialogDismissed(int reason) throws RemoteException { + if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Positive button is used by passive modalities as a "confirm" button, + // do not send to client + mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason); + // Cancel authentication. Skip the token/package check since we are cancelling + // from system server. The interface is permission protected so this is fine. + cancelInternal(null /* token */, null /* package */, false /* fromClient */); + } + if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) { + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_user_canceled)); + } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Have the service send the token to KeyStore, and send onAuthenticated + // to the application + KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + } + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + + @Override + public void onTryAgainPressed() { + Slog.d(TAG, "onTryAgainPressed"); + // No need to check permission, since it can only be invoked by SystemUI + // (or system server itself). + mHandler.post(() -> { + authenticateInternal(mCurrentAuthSession.mToken, + mCurrentAuthSession.mSessionId, + mCurrentAuthSession.mUserId, + mCurrentAuthSession.mClientReceiver, + mCurrentAuthSession.mOpPackageName, + mCurrentAuthSession.mBundle, + mCurrentAuthSession.mCallingUid, + mCurrentAuthSession.mCallingPid, + mCurrentAuthSession.mCallingUserId, + mCurrentAuthSession.mModality); + }); + } + }; + + @Override // Binder call + public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) { + checkInternalPermission(); + + Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); + if (pair.getValue() == cookie) { + mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue()); + mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey()); + Slog.d(TAG, "Matched cookie: " + cookie + ", " + + mPendingAuthSession.mModalitiesWaiting.size() + " remaining"); + break; + } + } + + if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) { + final boolean mContinuing = mCurrentAuthSession != null + && mCurrentAuthSession.mState == STATE_AUTH_PAUSED; + mCurrentAuthSession = mPendingAuthSession; + mPendingAuthSession = null; + + mCurrentAuthSession.mState = STATE_AUTH_STARTED; + try { + int modality = TYPE_NONE; + it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); + if (pair.getKey() == TYPE_FINGERPRINT) { + mFingerprintService.startPreparedClient(pair.getValue()); + } else if (pair.getKey() == TYPE_IRIS) { + Slog.e(TAG, "Iris unsupported"); + } else if (pair.getKey() == TYPE_FACE) { + mFaceService.startPreparedClient(pair.getValue()); + } else { + Slog.e(TAG, "Unknown modality: " + pair.getKey()); + } + modality |= pair.getKey(); + } + + if (!mContinuing) { + mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle, + mInternalReceiver, modality, requireConfirmation, userId); + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + } + @Override // Binder call public void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, int flags, String opPackageName, - Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException { + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle) + throws RemoteException { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); @@ -261,16 +611,45 @@ public class BiometricService extends SystemService { checkInternalPermission(); } - if (token == null || receiver == null || opPackageName == null || bundle == null - || dialogReceiver == null) { + if (token == null || receiver == null || opPackageName == null || bundle == null) { Slog.e(TAG, "Unable to authenticate, one or more null arguments"); return; } // Check the usage of this in system server. Need to remove this check if it becomes // a public API. - if (bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) { + final boolean useDefaultTitle = + bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false); + if (useDefaultTitle) { checkInternalPermission(); + // Set the default title if necessary + try { + if (useDefaultTitle) { + final List<ActivityManager.RunningAppProcessInfo> procs = + ActivityManager.getService().getRunningAppProcesses(); + for (int i = 0; i < procs.size(); i++) { + final ActivityManager.RunningAppProcessInfo info = procs.get(i); + if (info.uid == callingUid + && info.importance == IMPORTANCE_FOREGROUND) { + PackageManager pm = getContext().getPackageManager(); + final CharSequence label = pm.getApplicationLabel( + pm.getApplicationInfo(info.processName, + PackageManager.GET_META_DATA)); + final String title = getContext() + .getString(R.string.biometric_dialog_default_title, label); + if (TextUtils.isEmpty( + bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) { + bundle.putCharSequence(BiometricPrompt.KEY_TITLE, title); + } + break; + } + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Name not found", e); + } } mHandler.post(() -> { @@ -285,13 +664,13 @@ public class BiometricService extends SystemService { getContext().getString(R.string.biometric_error_hw_unavailable); switch (error) { case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: - receiver.onError(0 /* deviceId */, error, hardwareUnavailable); + receiver.onError(error, hardwareUnavailable); break; case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE: - receiver.onError(0 /* deviceId */, error, hardwareUnavailable); + receiver.onError(error, hardwareUnavailable); break; case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS: - receiver.onError(0 /* deviceId */, error, + receiver.onError(error, getErrorString(modality, error, 0 /* vendorCode */)); break; default: @@ -304,60 +683,91 @@ public class BiometricService extends SystemService { return; } - // Actually start authentication mCurrentModality = modality; - try { - // No polymorphism :( - if (mCurrentModality == BIOMETRIC_FINGERPRINT) { - mFingerprintService.authenticateFromService(token, sessionId, userId, - receiver, flags, opPackageName, bundle, dialogReceiver, - callingUid, callingPid, callingUserId); - } else if (mCurrentModality == BIOMETRIC_IRIS) { - Slog.w(TAG, "Unsupported modality"); - } else if (mCurrentModality == BIOMETRIC_FACE) { - mFaceService.authenticateFromService(true /* requireConfirmation */, - token, sessionId, userId, receiver, flags, opPackageName, - bundle, dialogReceiver, callingUid, callingPid, callingUserId); - } else { - Slog.w(TAG, "Unsupported modality"); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to start authentication", e); - } + + // Actually start authentication + authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, + callingUid, callingPid, callingUserId, modality); }); } + /** + * authenticate() (above) which is called from BiometricPrompt determines which + * modality/modalities to start authenticating with. authenticateInternal() should only be + * used for: + * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is, + * invoked, shortly after which BiometricPrompt is shown and authentication starts + * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown + * and the user has pressed "try again" + */ + private void authenticateInternal(IBinder token, long sessionId, int userId, + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, + int callingUid, int callingPid, int callingUserId, int modality) { + try { + // Generate random cookies to pass to the services that should prepare to start + // authenticating. Store the cookie here and wait for all services to "ack" + // with the cookie. Once all cookies are received, we can show the prompt + // and let the services start authenticating. The cookie should be non-zero. + final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; + Slog.d(TAG, "Creating auth session. Modality: " + modality + + ", cookie: " + cookie); + final HashMap<Integer, Integer> authenticators = new HashMap<>(); + authenticators.put(modality, cookie); + mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, + receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, + modality); + mPendingAuthSession.mState = STATE_AUTH_CALLED; + // No polymorphism :( + if ((modality & TYPE_FINGERPRINT) != 0) { + mFingerprintService.prepareForAuthentication(token, sessionId, userId, + mInternalReceiver, opPackageName, cookie, + callingUid, callingPid, callingUserId); + } + if ((modality & TYPE_IRIS) != 0) { + Slog.w(TAG, "Iris unsupported"); + } + if ((modality & TYPE_FACE) != 0) { + mFaceService.prepareForAuthentication(true /* requireConfirmation */, + token, sessionId, userId, mInternalReceiver, opPackageName, + cookie, callingUid, callingPid, callingUserId); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to start authentication", e); + } + } + @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName) throws RemoteException { checkPermission(); - if (token == null || opPackageName == null) { Slog.e(TAG, "Unable to cancel, one or more null arguments"); return; } - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final int callingUserId = UserHandle.getCallingUserId(); - - mHandler.post(() -> { - try { - if (mCurrentModality == BIOMETRIC_FINGERPRINT) { - mFingerprintService.cancelAuthenticationFromService(token, opPackageName, - callingUid, callingPid, callingUserId); - } else if (mCurrentModality == BIOMETRIC_IRIS) { - Slog.w(TAG, "Unsupported modality"); - } else if (mCurrentModality == BIOMETRIC_FACE) { - mFaceService.cancelAuthenticationFromService(token, opPackageName, - callingUid, callingPid, callingUserId); - } else { - Slog.w(TAG, "Unsupported modality"); + // We need to check the current authenticators state. If we're pending confirm + // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client, + // since we won't be getting an onError from the driver. + if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + mHandler.post(() -> { + try { + // Send error to client + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_user_canceled) + ); + + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + mStatusBarService.hideBiometricDialog(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to cancel authentication"); - } - }); + }); + } else { + cancelInternal(token, opPackageName, true /* fromClient */); + } } @Override // Binder call @@ -402,6 +812,31 @@ public class BiometricService extends SystemService { Binder.restoreCallingIdentity(ident); } } + + void cancelInternal(IBinder token, String opPackageName, boolean fromClient) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int callingUserId = UserHandle.getCallingUserId(); + mHandler.post(() -> { + try { + // TODO: For multiple modalities, send a single ERROR_CANCELED only when all + // drivers have canceled authentication. + if ((mCurrentModality & TYPE_FINGERPRINT) != 0) { + mFingerprintService.cancelAuthenticationFromService(token, opPackageName, + callingUid, callingPid, callingUserId, fromClient); + } + if ((mCurrentModality & TYPE_IRIS) != 0) { + Slog.w(TAG, "Iris unsupported"); + } + if ((mCurrentModality & TYPE_FACE) != 0) { + mFaceService.cancelAuthenticationFromService(token, opPackageName, + callingUid, callingPid, callingUserId, fromClient); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to cancel authentication"); + } + }); + } } private void checkAppOp(String opPackageName, int callingUid) { @@ -413,7 +848,7 @@ public class BiometricService extends SystemService { } private void checkInternalPermission() { - getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL, + getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL, "Must have USE_BIOMETRIC_INTERNAL permission"); } @@ -490,16 +925,19 @@ public class BiometricService extends SystemService { * returns errors through the callback (no biometric feature, hardware not detected, no * templates enrolled, etc). This service must not start authentication if errors are sent. * - * @Returns A pair [Modality, Error] with Modality being one of {@link #BIOMETRIC_NONE}, - * {@link #BIOMETRIC_FINGERPRINT}, {@link #BIOMETRIC_IRIS}, {@link #BIOMETRIC_FACE} + * @Returns A pair [Modality, Error] with Modality being one of + * {@link BiometricAuthenticator#TYPE_NONE}, + * {@link BiometricAuthenticator#TYPE_FINGERPRINT}, + * {@link BiometricAuthenticator#TYPE_IRIS}, + * {@link BiometricAuthenticator#TYPE_FACE} * and the error containing one of the {@link BiometricConstants} errors. */ private Pair<Integer, Integer> checkAndGetBiometricModality(int callingUid) { - int modality = BIOMETRIC_NONE; + int modality = TYPE_NONE; // No biometric features, send error if (mAuthenticators.isEmpty()) { - return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); + return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); } // Assuming that authenticators are listed in priority-order, the rest of this function @@ -512,13 +950,13 @@ public class BiometricService extends SystemService { boolean hasTemplatesEnrolled = false; boolean enabledForApps = false; - int firstHwAvailable = BIOMETRIC_NONE; + int firstHwAvailable = TYPE_NONE; for (int i = 0; i < mAuthenticators.size(); i++) { modality = mAuthenticators.get(i).getType(); BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator(); if (authenticator.isHardwareDetected()) { isHardwareDetected = true; - if (firstHwAvailable == BIOMETRIC_NONE) { + if (firstHwAvailable == TYPE_NONE) { // Store the first one since we want to return the error in correct priority // order. firstHwAvailable = modality; @@ -538,13 +976,13 @@ public class BiometricService extends SystemService { // Check error conditions if (!isHardwareDetected) { - return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } else if (!hasTemplatesEnrolled) { // Return the modality here so the correct error string can be sent. This error is // preferred over !enabledForApps return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); } else if (!enabledForApps) { - return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); @@ -552,11 +990,11 @@ public class BiometricService extends SystemService { private boolean isEnabledForApp(int modality) { switch(modality) { - case BIOMETRIC_FINGERPRINT: + case TYPE_FINGERPRINT: return true; - case BIOMETRIC_IRIS: + case TYPE_IRIS: return true; - case BIOMETRIC_FACE: + case TYPE_FACE: return mSettingObserver.getFaceEnabledForApps(); default: Slog.w(TAG, "Unsupported modality: " + modality); @@ -566,12 +1004,12 @@ public class BiometricService extends SystemService { private String getErrorString(int type, int error, int vendorCode) { switch (type) { - case BIOMETRIC_FINGERPRINT: + case TYPE_FINGERPRINT: return FingerprintManager.getErrorString(getContext(), error, vendorCode); - case BIOMETRIC_IRIS: + case TYPE_IRIS: Slog.w(TAG, "Modality not supported"); return null; // not supported - case BIOMETRIC_FACE: + case TYPE_FACE: return FaceManager.getErrorString(getContext(), error, vendorCode); default: Slog.w(TAG, "Unable to get error string for modality: " + type); @@ -581,12 +1019,12 @@ public class BiometricService extends SystemService { private BiometricAuthenticator getAuthenticator(int type) { switch (type) { - case BIOMETRIC_FINGERPRINT: + case TYPE_FINGERPRINT: return (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE); - case BIOMETRIC_IRIS: + case TYPE_IRIS: return null; - case BIOMETRIC_FACE: + case TYPE_FACE: return (FaceManager) getContext().getSystemService(Context.FACE_SERVICE); default: @@ -596,11 +1034,11 @@ public class BiometricService extends SystemService { private boolean hasFeature(int type) { switch (type) { - case BIOMETRIC_FINGERPRINT: + case TYPE_FINGERPRINT: return mHasFeatureFingerprint; - case BIOMETRIC_IRIS: + case TYPE_IRIS: return mHasFeatureIris; - case BIOMETRIC_FACE: + case TYPE_FACE: return mHasFeatureFace; default: return false; diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 74d742af5b84..9649ccd3c750 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -16,7 +16,6 @@ package com.android.server.biometrics; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; import android.app.ActivityManager; @@ -36,8 +35,9 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.fingerprint.Fingerprint; import android.os.Binder; import android.os.Bundle; @@ -56,7 +56,6 @@ import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; -import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.server.SystemService; @@ -106,6 +105,7 @@ public abstract class BiometricServiceBase extends SystemService protected final AppOpsManager mAppOps; protected final H mHandler = new H(); + private IBiometricService mBiometricService; private ClientMonitor mCurrentClient; private ClientMonitor mPendingClient; private PerformanceStats mPerformanceStats; @@ -223,12 +223,9 @@ public abstract class BiometricServiceBase extends SystemService public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, - IStatusBarService statusBarService, boolean requireConfirmation) { - super(context, getMetrics(), daemon, halDeviceId, token, listener, - targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver, - statusBarService, requireConfirmation); + boolean restricted, String owner, int cookie, boolean requireConfirmation) { + super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId, + groupId, opId, restricted, owner, cookie, requireConfirmation); } @Override @@ -279,11 +276,6 @@ public abstract class BiometricServiceBase extends SystemService } return AuthenticationClient.LOCKOUT_NONE; } - - @Override - public void onAuthenticationConfirmed() { - removeClient(mCurrentClient); - } } protected class EnrollClientImpl extends EnrollClient { @@ -345,18 +337,28 @@ public abstract class BiometricServiceBase extends SystemService default void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) throws RemoteException {}; - void onAcquired(long deviceId, int acquiredInfo, int vendorCode) - throws RemoteException; + void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException; - void onAuthenticationSucceeded(long deviceId, - BiometricAuthenticator.Identifier biometric, int userId) - throws RemoteException; + default void onAuthenticationSucceeded(long deviceId, + BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException { + throw new UnsupportedOperationException("Stub!"); + } - void onAuthenticationFailed(long deviceId) - throws RemoteException; + default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token) + throws RemoteException { + throw new UnsupportedOperationException("Stub!"); + } - void onError(long deviceId, int error, int vendorCode) - throws RemoteException; + default void onAuthenticationFailed(long deviceId) throws RemoteException { + throw new UnsupportedOperationException("Stub!"); + } + + default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation) + throws RemoteException { + throw new UnsupportedOperationException("Stub!"); + } + + void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException; default void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining) throws RemoteException {}; @@ -366,6 +368,37 @@ public abstract class BiometricServiceBase extends SystemService } /** + * Wraps the callback interface from Service -> BiometricPrompt + */ + protected abstract class BiometricServiceListener implements ServiceListener { + private IBiometricServiceReceiverInternal mWrapperReceiver; + + public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) { + mWrapperReceiver = wrapperReceiver; + } + + public IBiometricServiceReceiverInternal getWrapperReceiver() { + return mWrapperReceiver; + } + + @Override + public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token) + throws RemoteException { + if (getWrapperReceiver() != null) { + getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token); + } + } + + @Override + public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation) + throws RemoteException { + if (getWrapperReceiver() != null) { + getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation); + } + } + } + + /** * Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor * subclasses. */ @@ -706,30 +739,6 @@ public abstract class BiometricServiceBase extends SystemService } mHandler.post(() -> { - if (client.isBiometricPrompt()) { - try { - final List<ActivityManager.RunningAppProcessInfo> procs = - ActivityManager.getService().getRunningAppProcesses(); - for (int i = 0; i < procs.size(); i++) { - final ActivityManager.RunningAppProcessInfo info = procs.get(i); - if (info.uid == callingUid && info.importance == IMPORTANCE_FOREGROUND) { - PackageManager pm = getContext().getPackageManager(); - final CharSequence label = pm.getApplicationLabel( - pm.getApplicationInfo(info.processName, - PackageManager.GET_META_DATA)); - final String title = getContext() - .getString(R.string.biometric_dialog_default_title, label); - client.setTitleIfEmpty(title); - break; - } - } - } catch (RemoteException e) { - Slog.e(getTag(), "Unable to get application name", e); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(getTag(), "Unable to get application name", e); - } - } - mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0); // Get performance stats object for this user. @@ -751,29 +760,37 @@ public abstract class BiometricServiceBase extends SystemService final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); - cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId); + cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId, + true /* fromClient */); } protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName, - int callingUid, int callingPid, int callingUserId) { - if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid, - callingUserId)) { - if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); - return; + int callingUid, int callingPid, int callingUserId, boolean fromClient) { + if (fromClient) { + // Only check this if cancel was called from the client (app). If cancel was called + // from BiometricService, it means the dialog was dismissed due to user interaction. + if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid, + callingUserId)) { + if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); + return; + } } mHandler.post(() -> { ClientMonitor client = mCurrentClient; if (client instanceof AuthenticationClient) { - if (client.getToken() == token) { - if (DEBUG) Slog.v(getTag(), "stop client " + client.getOwnerString()); + if (client.getToken() == token || !fromClient) { + if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString() + + ", fromClient: " + fromClient); + // If cancel was from BiometricService, it means the dialog was dismissed + // and authentication should be canceled. client.stop(client.getToken() == token); } else { - if (DEBUG) Slog.v(getTag(), "can't stop client " - + client.getOwnerString() + " since tokens don't match"); + if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString() + + " since tokens don't match. fromClient: " + fromClient); } } else if (client != null) { - if (DEBUG) Slog.v(getTag(), "can't cancel non-authenticating client " + if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client " + client.getOwnerString()); } }); @@ -805,8 +822,7 @@ public abstract class BiometricServiceBase extends SystemService int lockoutMode = getLockoutMode(); if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) { - Slog.v(getTag(), "In lockout mode(" + lockoutMode + - ") ; disallowing authentication"); + Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication"); int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; @@ -919,7 +935,6 @@ public abstract class BiometricServiceBase extends SystemService if (currentClient != null) { if (DEBUG) Slog.v(getTag(), "request stop current client " + currentClient.getOwnerString()); - // This check only matters for FingerprintService, since enumerate may call back // multiple times. if (currentClient instanceof FingerprintService.EnumerateClientImpl || @@ -940,15 +955,49 @@ public abstract class BiometricServiceBase extends SystemService mHandler.removeCallbacks(mResetClientState); mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT); } else if (newClient != null) { + // For BiometricPrompt clients, do not start until + // <Biometric>Service#startPreparedClient is called. BiometricService waits until all + // modalities are ready before initiating authentication. + if (newClient instanceof AuthenticationClient) { + AuthenticationClient client = (AuthenticationClient) newClient; + if (client.isBiometricPrompt()) { + if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie()); + mCurrentClient = newClient; + if (mBiometricService == null) { + mBiometricService = IBiometricService.Stub.asInterface( + ServiceManager.getService(Context.BIOMETRIC_SERVICE)); + } + try { + mBiometricService.onReadyForAuthentication(client.getCookie(), + client.getRequireConfirmation(), client.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(getTag(), "Remote exception", e); + } + return; + } + } + + // We are not a BiometricPrompt client, start the client immediately mCurrentClient = newClient; - if (DEBUG) Slog.v(getTag(), "starting client " - + newClient.getClass().getSuperclass().getSimpleName() - + "(" + newClient.getOwnerString() + ")" - + ", initiatedByClient = " + initiatedByClient); - notifyClientActiveCallbacks(true); + startCurrentClient(mCurrentClient.getCookie()); + } + } - newClient.start(); + protected void startCurrentClient(int cookie) { + if (mCurrentClient == null) { + Slog.e(getTag(), "Trying to start null client!"); + return; + } + if (DEBUG) Slog.v(getTag(), "starting client " + + mCurrentClient.getClass().getSuperclass().getSimpleName() + + "(" + mCurrentClient.getOwnerString() + ")" + + " cookie: " + cookie + "/" + mCurrentClient.getCookie()); + if (cookie != mCurrentClient.getCookie()) { + Slog.e(getTag(), "Mismatched cookie"); + return; } + notifyClientActiveCallbacks(true); + mCurrentClient.start(); } protected void removeClient(ClientMonitor client) { diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java index a7ada2f6556c..d19aff69b832 100644 --- a/services/core/java/com/android/server/biometrics/ClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java @@ -58,6 +58,9 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { private IBinder mToken; private BiometricServiceBase.ServiceListener mListener; + // Currently only used for authentication client. The cookie generated by BiometricService + // is never 0. + private final int mCookie; protected final MetricsLogger mMetricsLogger; protected final Metrics mMetrics; @@ -80,7 +83,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { public ClientMonitor(Context context, Metrics metrics, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int userId, int groupId, - boolean restricted, String owner) { + boolean restricted, String owner, int cookie) { mContext = context; mMetrics = metrics; mDaemon = daemon; @@ -91,6 +94,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { mGroupId = groupId; mIsRestricted = restricted; mOwner = owner; + mCookie = cookie; mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); mMetricsLogger = new MetricsLogger(); @@ -107,6 +111,10 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { return mMetrics.logTag(); } + public int getCookie() { + return mCookie; + } + /** * Contacts the biometric's HAL to start the client. * @return 0 on success, errno from driver on failure @@ -174,7 +182,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { public boolean onError(long deviceId, int error, int vendorCode) { try { if (mListener != null) { - mListener.onError(deviceId, error, vendorCode); + mListener.onError(deviceId, error, vendorCode, getCookie()); } } catch (RemoteException e) { Slog.w(getLogTag(), "Failed to invoke sendError", e); diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java index 76dc5a91bdbe..f858ef5ec6f8 100644 --- a/services/core/java/com/android/server/biometrics/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/EnrollClient.java @@ -40,7 +40,7 @@ public abstract class EnrollClient extends ClientMonitor { BiometricServiceBase.ServiceListener listener, int userId, int groupId, byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) { super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, - owner); + owner, 0 /* cookie */); mBiometricUtils = utils; mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length); } diff --git a/services/core/java/com/android/server/biometrics/EnumerateClient.java b/services/core/java/com/android/server/biometrics/EnumerateClient.java index 47dc7ffda456..df6220cfd94b 100644 --- a/services/core/java/com/android/server/biometrics/EnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/EnumerateClient.java @@ -34,7 +34,7 @@ public abstract class EnumerateClient extends ClientMonitor { BiometricServiceBase.ServiceListener listener, int groupId, int userId, boolean restricted, String owner) { super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, - owner); + owner, 0 /* cookie */); } @Override diff --git a/services/core/java/com/android/server/biometrics/RemovalClient.java b/services/core/java/com/android/server/biometrics/RemovalClient.java index 15b3773314bf..be233ec89342 100644 --- a/services/core/java/com/android/server/biometrics/RemovalClient.java +++ b/services/core/java/com/android/server/biometrics/RemovalClient.java @@ -37,7 +37,7 @@ public abstract class RemovalClient extends ClientMonitor { BiometricServiceBase.ServiceListener listener, int biometricId, int groupId, int userId, boolean restricted, String owner, BiometricUtils utils) { super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, - owner); + owner, 0 /* cookie */); mBiometricId = biometricId; mBiometricUtils = utils; } diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 7aa2e47300dd..557af0478b87 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -27,9 +27,8 @@ import android.content.Context; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; -import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.biometrics.face.V1_0.Status; @@ -38,7 +37,6 @@ import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; -import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; @@ -50,7 +48,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; import com.android.server.biometrics.BiometricServiceBase; @@ -89,27 +86,9 @@ public class FaceService extends BiometricServiceBase { public FaceAuthClient(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, - boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, owner, bundle, dialogReceiver, statusBarService, - requireConfirmation); - } - - @Override - public String getErrorString(int error, int vendorCode) { - return FaceManager.getErrorString(getContext(), error, vendorCode); - } - - @Override - public String getAcquiredString(int acquireInfo, int vendorCode) { - return FaceManager.getAcquiredString(getContext(), acquireInfo, vendorCode); - } - - @Override - public int getBiometricType() { - return BiometricAuthenticator.TYPE_FACE; + restricted, owner, cookie, requireConfirmation); } } @@ -162,28 +141,33 @@ public class FaceService extends BiometricServiceBase { final AuthenticationClientImpl client = new FaceAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - null /* bundle */, null /* dialogReceiver */, mStatusBarService, - false /* requireConfirmation */); + 0 /* cookie */, false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName); } @Override // Binder call - public void authenticateFromService(boolean requireConfirmation, IBinder token, long opId, - int groupId, IBiometricServiceReceiver receiver, int flags, - String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver, - int callingUid, int callingPid, int callingUserId) { + public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId, + int groupId, IBiometricServiceReceiverInternal wrapperReceiver, + String opPackageName, int cookie, int callingUid, int callingPid, + int callingUserId) { checkPermission(USE_BIOMETRIC_INTERNAL); final boolean restricted = true; // BiometricPrompt is always restricted final AuthenticationClientImpl client = new FaceAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, - new BiometricPromptServiceListenerImpl(receiver), - mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - bundle, dialogReceiver, mStatusBarService, true /* requireConfirmation */); + new BiometricPromptServiceListenerImpl(wrapperReceiver), + mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie, + true /* requireConfirmation */); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @Override // Binder call + public void startPreparedClient(int cookie) { + checkPermission(MANAGE_BIOMETRIC); + startCurrentClient(cookie); + } + + @Override // Binder call public void cancelAuthentication(final IBinder token, final String opPackageName) { checkPermission(USE_BIOMETRIC_INTERNAL); cancelAuthenticationInternal(token, opPackageName); @@ -191,10 +175,10 @@ public class FaceService extends BiometricServiceBase { @Override // Binder call public void cancelAuthenticationFromService(final IBinder token, final String opPackageName, - int callingUid, int callingPid, int callingUserId) { + int callingUid, int callingPid, int callingUserId, boolean fromClient) { checkPermission(USE_BIOMETRIC_INTERNAL); - cancelAuthenticationInternal(token, opPackageName, - callingUid, callingPid, callingUserId); + cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, + callingUserId, fromClient); } @Override // Binder call @@ -405,12 +389,9 @@ public class FaceService extends BiometricServiceBase { * Receives callbacks from the ClientMonitor implementations. The results are forwarded to * BiometricPrompt. */ - private class BiometricPromptServiceListenerImpl implements ServiceListener { - - private IBiometricServiceReceiver mBiometricServiceReceiver; - - public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) { - mBiometricServiceReceiver = receiver; + private class BiometricPromptServiceListenerImpl extends BiometricServiceListener { + BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) { + super(wrapperReceiver); } @Override @@ -419,32 +400,18 @@ public class FaceService extends BiometricServiceBase { /** * Map the acquired codes onto existing {@link BiometricConstants} acquired codes. */ - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAcquired(deviceId, + if (getWrapperReceiver() != null) { + getWrapperReceiver().onAcquired( FaceManager.getMappedAcquiredInfo(acquiredInfo, vendorCode), FaceManager.getAcquiredString(getContext(), acquiredInfo, vendorCode)); } } @Override - public void onAuthenticationSucceeded(long deviceId, - BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId); - } - } - - @Override - public void onAuthenticationFailed(long deviceId) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAuthenticationFailed(deviceId); - } - } - - @Override - public void onError(long deviceId, int error, int vendorCode) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onError(deviceId, error, + public void onError(long deviceId, int error, int vendorCode, int cookie) + throws RemoteException { + if (getWrapperReceiver() != null) { + getWrapperReceiver().onError(cookie, error, FaceManager.getErrorString(getContext(), error, vendorCode)); } } @@ -455,7 +422,6 @@ public class FaceService extends BiometricServiceBase { * the FaceManager. */ private class ServiceListenerImpl implements ServiceListener { - private IFaceServiceReceiver mFaceServiceReceiver; public ServiceListenerImpl(IFaceServiceReceiver receiver) { @@ -501,7 +467,8 @@ public class FaceService extends BiometricServiceBase { } @Override - public void onError(long deviceId, int error, int vendorCode) throws RemoteException { + public void onError(long deviceId, int error, int vendorCode, int cookie) + throws RemoteException { if (mFaceServiceReceiver != null) { mFaceServiceReceiver.onError(deviceId, error, vendorCode); } diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index b0b788fbe589..6a5bc61f2eb6 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -30,9 +30,8 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.IBiometricPromptReceiver; -import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; @@ -42,7 +41,6 @@ import android.hardware.fingerprint.IFingerprintService; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.os.Binder; import android.os.Build; -import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; @@ -55,7 +53,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; import com.android.server.biometrics.AuthenticationClient; @@ -109,27 +106,10 @@ public class FingerprintService extends BiometricServiceBase { public FingerprintAuthClient(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, + boolean restricted, String owner, int cookie, boolean requireConfirmation) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, owner, bundle, dialogReceiver, statusBarService, - requireConfirmation); - } - - @Override - public String getErrorString(int error, int vendorCode) { - return FingerprintManager.getErrorString(getContext(), error, vendorCode); - } - - @Override - public String getAcquiredString(int acquireInfo, int vendorCode) { - return FingerprintManager.getAcquiredString(getContext(), acquireInfo, vendorCode); - } - - @Override - public int getBiometricType() { - return BiometricAuthenticator.TYPE_FINGERPRINT; + restricted, owner, cookie, requireConfirmation); } } @@ -182,38 +162,44 @@ public class FingerprintService extends BiometricServiceBase { final boolean restricted = isRestricted(); final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), - mCurrentUserId, groupId, opId, restricted, opPackageName, null /* bundle */, - null /* dialogReceiver */, mStatusBarService, false /* requireConfirmation */); + mCurrentUserId, groupId, opId, restricted, opPackageName, + 0 /* cookie */, false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName); } @Override // Binder call - public void authenticateFromService(IBinder token, long opId, int groupId, - IBiometricServiceReceiver receiver, int flags, String opPackageName, - Bundle bundle, IBiometricPromptReceiver dialogReceiver, - int callingUid, int callingPid, int callingUserId) { + public void prepareForAuthentication(IBinder token, long opId, int groupId, + IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, + int cookie, int callingUid, int callingPid, int callingUserId) { checkPermission(MANAGE_BIOMETRIC); final boolean restricted = true; // BiometricPrompt is always restricted final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, - new BiometricPromptServiceListenerImpl(receiver), - mCurrentUserId, groupId, opId, restricted, opPackageName, bundle, - dialogReceiver, mStatusBarService, false /* requireConfirmation */); + new BiometricPromptServiceListenerImpl(wrapperReceiver), + mCurrentUserId, groupId, opId, restricted, opPackageName, cookie, + false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @Override // Binder call + public void startPreparedClient(int cookie) { + checkPermission(MANAGE_BIOMETRIC); + startCurrentClient(cookie); + } + + + @Override // Binder call public void cancelAuthentication(final IBinder token, final String opPackageName) { cancelAuthenticationInternal(token, opPackageName); } @Override // Binder call public void cancelAuthenticationFromService(final IBinder token, final String opPackageName, - int callingUid, int callingPid, int callingUserId) { + int callingUid, int callingPid, int callingUserId, boolean fromClient) { checkPermission(MANAGE_BIOMETRIC); - cancelAuthenticationInternal(token, opPackageName, - callingUid, callingPid, callingUserId); + cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, + callingUserId, fromClient); } @Override // Binder call @@ -388,43 +374,25 @@ public class FingerprintService extends BiometricServiceBase { * Receives callbacks from the ClientMonitor implementations. The results are forwarded to * BiometricPrompt. */ - private class BiometricPromptServiceListenerImpl implements ServiceListener { - - private IBiometricServiceReceiver mBiometricServiceReceiver; - - public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) { - mBiometricServiceReceiver = receiver; + private class BiometricPromptServiceListenerImpl extends BiometricServiceListener { + BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) { + super(wrapperReceiver); } @Override public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAcquired(deviceId, acquiredInfo, - FingerprintManager.getAcquiredString( + if (getWrapperReceiver() != null) { + getWrapperReceiver().onAcquired(acquiredInfo, FingerprintManager.getAcquiredString( getContext(), acquiredInfo, vendorCode)); } } @Override - public void onAuthenticationSucceeded(long deviceId, - BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId); - } - } - - @Override - public void onAuthenticationFailed(long deviceId) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onAuthenticationFailed(deviceId); - } - } - - @Override - public void onError(long deviceId, int error, int vendorCode) throws RemoteException { - if (mBiometricServiceReceiver != null) { - mBiometricServiceReceiver.onError(deviceId, error, + public void onError(long deviceId, int error, int vendorCode, int cookie) + throws RemoteException { + if (getWrapperReceiver() != null) { + getWrapperReceiver().onError(cookie, error, FingerprintManager.getErrorString(getContext(), error, vendorCode)); } } @@ -435,7 +403,6 @@ public class FingerprintService extends BiometricServiceBase { * the FingerprintManager. */ private class ServiceListenerImpl implements ServiceListener { - private IFingerprintServiceReceiver mFingerprintServiceReceiver; public ServiceListenerImpl(IFingerprintServiceReceiver receiver) { @@ -483,7 +450,8 @@ public class FingerprintService extends BiometricServiceBase { } @Override - public void onError(long deviceId, int error, int vendorCode) throws RemoteException { + public void onError(long deviceId, int error, int vendorCode, int cookie) + throws RemoteException { if (mFingerprintServiceReceiver != null) { mFingerprintServiceReceiver.onError(deviceId, error, vendorCode); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ae27d0c07ea8..6c2549e9b04d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -198,6 +198,7 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; +import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; @@ -206,6 +207,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.DeviceIdleController; import com.android.server.EventLogTags; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.lights.Light; @@ -264,11 +266,12 @@ public class NotificationManagerService extends SystemService { // message codes static final int MESSAGE_DURATION_REACHED = 2; - static final int MESSAGE_SAVE_POLICY_FILE = 3; + // 3: removed to a different handler static final int MESSAGE_SEND_RANKING_UPDATE = 4; static final int MESSAGE_LISTENER_HINTS_CHANGED = 5; static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 6; static final int MESSAGE_FINISH_TOKEN_TIMEOUT = 7; + static final int MESSAGE_ON_PACKAGE_CHANGED = 8; // ranking thread messages private static final int MESSAGE_RECONSIDER_RANKING = 1000; @@ -570,7 +573,7 @@ public class NotificationManagerService extends SystemService { mListeners.migrateToXml(); mAssistants.migrateToXml(); mConditionProviders.migrateToXml(); - savePolicyFile(); + handleSavePolicyFile(); } mAssistants.ensureAssistant(); @@ -600,31 +603,28 @@ public class NotificationManagerService extends SystemService { } } - public void savePolicyFile() { - mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE); - mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE); - } - private void handleSavePolicyFile() { - if (DBG) Slog.d(TAG, "handleSavePolicyFile"); - synchronized (mPolicyFile) { - final FileOutputStream stream; - try { - stream = mPolicyFile.startWrite(); - } catch (IOException e) { - Slog.w(TAG, "Failed to save policy file", e); - return; - } + IoThread.getHandler().post(() -> { + if (DBG) Slog.d(TAG, "handleSavePolicyFile"); + synchronized (mPolicyFile) { + final FileOutputStream stream; + try { + stream = mPolicyFile.startWrite(); + } catch (IOException e) { + Slog.w(TAG, "Failed to save policy file", e); + return; + } - try { - writePolicyXml(stream, false /*forBackup*/); - mPolicyFile.finishWrite(stream); - } catch (IOException e) { - Slog.w(TAG, "Failed to save policy file, restoring backup", e); - mPolicyFile.failWrite(stream); + try { + writePolicyXml(stream, false /*forBackup*/); + mPolicyFile.finishWrite(stream); + } catch (IOException e) { + Slog.w(TAG, "Failed to save policy file, restoring backup", e); + mPolicyFile.failWrite(stream); + } } - } - BackupManager.dataChanged(getContext().getPackageName()); + BackupManager.dataChanged(getContext().getPackageName()); + }); } private void writePolicyXml(OutputStream stream, boolean forBackup) throws IOException { @@ -1133,12 +1133,7 @@ public class NotificationManagerService extends SystemService { } } - mListeners.onPackagesChanged(removingPackage, pkgList, uidList); - mAssistants.onPackagesChanged(removingPackage, pkgList, uidList); - mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList); - mPreferencesHelper.onPackagesChanged( - removingPackage, changeUserId, pkgList, uidList); - savePolicyFile(); + mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList); } } }; @@ -1205,7 +1200,7 @@ public class NotificationManagerService extends SystemService { mListeners.onUserRemoved(userId); mConditionProviders.onUserRemoved(userId); mAssistants.onUserRemoved(userId); - savePolicyFile(); + handleSavePolicyFile(); } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); @@ -1456,7 +1451,7 @@ public class NotificationManagerService extends SystemService { mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override public void onConfigChanged() { - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -1755,7 +1750,7 @@ public class NotificationManagerService extends SystemService { modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); } - savePolicyFile(); + handleSavePolicyFile(); } private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate, @@ -2223,7 +2218,7 @@ public class NotificationManagerService extends SystemService { Slog.w(TAG, "Can't notify app about app block change", e); } - savePolicyFile(); + handleSavePolicyFile(); } /** @@ -2280,7 +2275,7 @@ public class NotificationManagerService extends SystemService { public void setShowBadge(String pkg, int uid, boolean showBadge) { checkCallerIsSystem(); mPreferencesHelper.setShowBadge(pkg, uid, showBadge); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2296,7 +2291,7 @@ public class NotificationManagerService extends SystemService { if (info != null) { mPreferencesHelper.setNotificationDelegate( callingPkg, callingUid, delegate, info.uid); - savePolicyFile(); + handleSavePolicyFile(); } } catch (RemoteException e) { // :( @@ -2307,7 +2302,7 @@ public class NotificationManagerService extends SystemService { public void revokeNotificationDelegate(String callingPkg) { checkCallerIsSameApp(callingPkg); mPreferencesHelper.revokeNotificationDelegate(callingPkg, Binder.getCallingUid()); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2342,7 +2337,7 @@ public class NotificationManagerService extends SystemService { NotificationChannelGroup group) throws RemoteException { enforceSystemOrSystemUI("Caller not system or systemui"); createNotificationChannelGroup(pkg, uid, group, false, false); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2355,7 +2350,7 @@ public class NotificationManagerService extends SystemService { final NotificationChannelGroup group = groups.get(i); createNotificationChannelGroup(pkg, Binder.getCallingUid(), group, true, false); } - savePolicyFile(); + handleSavePolicyFile(); } private void createNotificationChannelsImpl(String pkg, int uid, @@ -2373,7 +2368,7 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false), NOTIFICATION_CHANNEL_OR_GROUP_ADDED); } - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2418,7 +2413,7 @@ public class NotificationManagerService extends SystemService { UserHandle.getUserHandleForUid(callingUid), mPreferencesHelper.getNotificationChannel(pkg, callingUid, channelId, true), NOTIFICATION_CHANNEL_OR_GROUP_DELETED); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2460,7 +2455,7 @@ public class NotificationManagerService extends SystemService { mListeners.notifyNotificationChannelGroupChanged( pkg, UserHandle.getUserHandleForUid(callingUid), groupToDelete, NOTIFICATION_CHANNEL_OR_GROUP_DELETED); - savePolicyFile(); + handleSavePolicyFile(); } } @@ -2593,7 +2588,7 @@ public class NotificationManagerService extends SystemService { true, UserHandle.getCallingUserId(), packages, uids); } - savePolicyFile(); + handleSavePolicyFile(); } @@ -3381,7 +3376,7 @@ public class NotificationManagerService extends SystemService { final ByteArrayInputStream bais = new ByteArrayInputStream(payload); try { readPolicyXml(bais, true /*forRestore*/); - savePolicyFile(); + handleSavePolicyFile(); } catch (NumberFormatException | XmlPullParserException | IOException e) { Slog.w(TAG, "applyRestore: error reading payload", e); } @@ -3422,7 +3417,7 @@ public class NotificationManagerService extends SystemService { .setPackage(pkg) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3566,7 +3561,7 @@ public class NotificationManagerService extends SystemService { .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3592,7 +3587,7 @@ public class NotificationManagerService extends SystemService { .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3675,7 +3670,7 @@ public class NotificationManagerService extends SystemService { verifyPrivilegedListener(token, user, false); createNotificationChannelGroup( pkg, getUidForPackageAndUser(pkg, user), group, false, true); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -3724,7 +3719,7 @@ public class NotificationManagerService extends SystemService { } if (allow != mLockScreenAllowSecureNotifications) { mLockScreenAllowSecureNotifications = allow; - savePolicyFile(); + handleSavePolicyFile(); } } @@ -4247,18 +4242,7 @@ public class NotificationManagerService extends SystemService { // Fix the notification as best we can. try { - final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( - pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId); - Notification.addFieldsFromContext(ai, notification); - - int canColorize = mPackageManagerClient.checkPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg); - if (canColorize == PERMISSION_GRANTED) { - notification.flags |= Notification.FLAG_CAN_COLORIZE; - } else { - notification.flags &= ~Notification.FLAG_CAN_COLORIZE; - } + fixNotification(notification, pkg, userId); } catch (NameNotFoundException e) { Slog.e(TAG, "Cannot create a context for sending app", e); @@ -4359,6 +4343,33 @@ public class NotificationManagerService extends SystemService { mHandler.post(new EnqueueNotificationRunnable(userId, r)); } + @VisibleForTesting + protected void fixNotification(Notification notification, String pkg, int userId) + throws NameNotFoundException { + final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( + pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId); + Notification.addFieldsFromContext(ai, notification); + + int canColorize = mPackageManagerClient.checkPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg); + if (canColorize == PERMISSION_GRANTED) { + notification.flags |= Notification.FLAG_CAN_COLORIZE; + } else { + notification.flags &= ~Notification.FLAG_CAN_COLORIZE; + } + + if (ai.targetSdkVersion >= Build.VERSION_CODES.Q) { + int fullscreenIntentPermission = mPackageManagerClient.checkPermission( + android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg); + if (fullscreenIntentPermission != PERMISSION_GRANTED) { + notification.fullScreenIntent = null; + Log.w(TAG, "Package " + pkg + + ": Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission"); + } + } + } + private void doChannelWarningToast(CharSequence toastText) { Binder.withCleanCallingIdentity(() -> { final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0; @@ -4467,7 +4478,7 @@ public class NotificationManagerService extends SystemService { Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey()); } mSnoozeHelper.update(userId, r); - savePolicyFile(); + handleSavePolicyFile(); return false; } @@ -4598,7 +4609,7 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.snooze(r, mDuration); } r.recordSnoozed(); - savePolicyFile(); + handleSavePolicyFile(); } } @@ -4676,7 +4687,7 @@ public class NotificationManagerService extends SystemService { if (mReason != REASON_SNOOZED) { final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId); if (wasSnoozed) { - savePolicyFile(); + handleSavePolicyFile(); } } } @@ -5691,6 +5702,16 @@ public class NotificationManagerService extends SystemService { } } + private void handleOnPackageChanged(boolean removingPackage, int changeUserId, + String[] pkgList, int[] uidList) { + mListeners.onPackagesChanged(removingPackage, pkgList, uidList); + mAssistants.onPackagesChanged(removingPackage, pkgList, uidList); + mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList); + mPreferencesHelper.onPackagesChanged( + removingPackage, changeUserId, pkgList, uidList); + handleSavePolicyFile(); + } + protected class WorkerHandler extends Handler { public WorkerHandler(Looper looper) { @@ -5703,13 +5724,10 @@ public class NotificationManagerService extends SystemService { switch (msg.what) { case MESSAGE_DURATION_REACHED: - handleDurationReached((ToastRecord)msg.obj); + handleDurationReached((ToastRecord) msg.obj); break; case MESSAGE_FINISH_TOKEN_TIMEOUT: - handleKillTokenTimeout((ToastRecord)msg.obj); - break; - case MESSAGE_SAVE_POLICY_FILE: - handleSavePolicyFile(); + handleKillTokenTimeout((ToastRecord) msg.obj); break; case MESSAGE_SEND_RANKING_UPDATE: handleSendRankingUpdate(); @@ -5720,6 +5738,12 @@ public class NotificationManagerService extends SystemService { case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED: handleListenerInterruptionFilterChanged(msg.arg1); break; + case MESSAGE_ON_PACKAGE_CHANGED: + SomeArgs args = (SomeArgs) msg.obj; + handleOnPackageChanged((boolean) args.arg1, args.argi1, (String[]) args.arg2, + (int[]) args.arg3); + args.recycle(); + break; } } @@ -5735,6 +5759,16 @@ public class NotificationManagerService extends SystemService { sendMessage(Message.obtain(this, cancelRunnable)); } } + + protected void scheduleOnPackageChanged(boolean removingPackage, int changeUserId, + String[] pkgList, int[] uidList) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = removingPackage; + args.argi1 = changeUserId; + args.arg2 = pkgList; + args.arg3 = uidList; + sendMessage(Message.obtain(this, MESSAGE_ON_PACKAGE_CHANGED, args)); + } } private final class RankingHandlerWorker extends Handler implements RankingHandler @@ -6212,7 +6246,7 @@ public class NotificationManagerService extends SystemService { Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName)); } mSnoozeHelper.repost(key); - savePolicyFile(); + handleSavePolicyFile(); } @GuardedBy("mNotificationLock") diff --git a/services/core/java/com/android/server/oemlock/OemLock.java b/services/core/java/com/android/server/oemlock/OemLock.java index ee70c29b7da4..352884b606a0 100644 --- a/services/core/java/com/android/server/oemlock/OemLock.java +++ b/services/core/java/com/android/server/oemlock/OemLock.java @@ -19,6 +19,9 @@ package com.android.server.oemlock; import android.annotation.Nullable; abstract class OemLock { + @Nullable + abstract String getLockName(); + abstract void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature); abstract boolean isOemUnlockAllowedByCarrier(); diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java index a6200bf433e4..6e82c24a6e9b 100644 --- a/services/core/java/com/android/server/oemlock/OemLockService.java +++ b/services/core/java/com/android/server/oemlock/OemLockService.java @@ -113,6 +113,19 @@ public class OemLockService extends SystemService { */ private final IBinder mService = new IOemLockService.Stub() { @Override + @Nullable + public String getLockName() { + enforceManageCarrierOemUnlockPermission(); + + final long token = Binder.clearCallingIdentity(); + try { + return mOemLock.getLockName(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) { enforceManageCarrierOemUnlockPermission(); enforceUserIsAdmin(); diff --git a/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java b/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java index d9362d4b0f28..a1c27d6432f1 100644 --- a/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java +++ b/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java @@ -40,6 +40,12 @@ class PersistentDataBlockLock extends OemLock { } @Override + @Nullable + String getLockName() { + return ""; + } + + @Override void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) { // Note: this implementation does not require a signature if (signature != null) { diff --git a/services/core/java/com/android/server/oemlock/VendorLock.java b/services/core/java/com/android/server/oemlock/VendorLock.java index 1b9de3612367..37540d039b9e 100644 --- a/services/core/java/com/android/server/oemlock/VendorLock.java +++ b/services/core/java/com/android/server/oemlock/VendorLock.java @@ -22,8 +22,6 @@ import android.hardware.oemlock.V1_0.IOemLock; import android.hardware.oemlock.V1_0.OemLockSecureStatus; import android.hardware.oemlock.V1_0.OemLockStatus; import android.os.RemoteException; -import android.os.UserHandle; -import android.os.UserManager; import android.util.Slog; import java.util.ArrayList; @@ -55,14 +53,49 @@ class VendorLock extends OemLock { } @Override + @Nullable + String getLockName() { + final Integer[] requestStatus = new Integer[1]; + final String[] lockName = new String[1]; + + try { + mOemLock.getName((status, name) -> { + requestStatus[0] = status; + lockName[0] = name; + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get name from HAL", e); + throw e.rethrowFromSystemServer(); + } + + switch (requestStatus[0]) { + case OemLockStatus.OK: + // Success + return lockName[0]; + + case OemLockStatus.FAILED: + Slog.e(TAG, "Failed to get OEM lock name."); + return null; + + default: + Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL"); + return null; + } + } + + @Override void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) { try { - switch (mOemLock.setOemUnlockAllowedByCarrier(allowed, toByteArrayList(signature))) { + ArrayList<Byte> signatureBytes = toByteArrayList(signature); + switch (mOemLock.setOemUnlockAllowedByCarrier(allowed, signatureBytes)) { case OemLockSecureStatus.OK: Slog.i(TAG, "Updated carrier allows OEM lock state to: " + allowed); return; case OemLockSecureStatus.INVALID_SIGNATURE: + if (signatureBytes.isEmpty()) { + throw new IllegalArgumentException("Signature required for carrier unlock"); + } throw new SecurityException( "Invalid signature used in attempt to carrier unlock"); @@ -154,9 +187,9 @@ class VendorLock extends OemLock { } } - private ArrayList toByteArrayList(byte[] data) { + private ArrayList<Byte> toByteArrayList(byte[] data) { if (data == null) { - return null; + return new ArrayList<Byte>(); } ArrayList<Byte> result = new ArrayList<Byte>(data.length); for (final byte b : data) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6a6a5be53409..8abb5000d544 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -116,8 +116,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures; import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE; import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS; -import static com.android.server.pm.permission.PermissionsState - .PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; +import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; import android.Manifest; import android.annotation.IntDef; @@ -314,8 +313,7 @@ import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.permission.BasePermission; import com.android.server.pm.permission.DefaultPermissionGrantPolicy; -import com.android.server.pm.permission.DefaultPermissionGrantPolicy - .DefaultPermissionGrantedCallback; +import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback; import com.android.server.pm.permission.PermissionManagerInternal; import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback; import com.android.server.pm.permission.PermissionManagerService; @@ -374,6 +372,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -20136,7 +20135,7 @@ public class PackageManagerService extends IPackageManager.Stub if (Process.isIsolated(uid)) { return Zygote.MOUNT_EXTERNAL_NONE; } - if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (StorageManager.hasIsolatedStorage()) { return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED ? Zygote.MOUNT_EXTERNAL_FULL : Zygote.MOUNT_EXTERNAL_WRITE; @@ -23207,6 +23206,45 @@ public class PackageManagerService extends IPackageManager.Stub throws IOException { PackageManagerService.this.freeStorage(volumeUuid, bytes, storageFlags); } + + @Override + public void forEachPackage(Consumer<PackageParser.Package> actionLocked) { + PackageManagerService.this.forEachPackage(actionLocked); + } + + @Override + public ArraySet<String> getEnabledComponents(String packageName, int userId) { + synchronized (mPackages) { + PackageSetting setting = mSettings.getPackageLPr(packageName); + if (setting == null) { + return new ArraySet<>(); + } + return setting.getEnabledComponents(userId); + } + } + + @Override + public ArraySet<String> getDisabledComponents(String packageName, int userId) { + synchronized (mPackages) { + PackageSetting setting = mSettings.getPackageLPr(packageName); + if (setting == null) { + return new ArraySet<>(); + } + return setting.getDisabledComponents(userId); + } + } + + @Override + public @PackageManager.EnabledState int getApplicationEnabledState( + String packageName, int userId) { + synchronized (mPackages) { + PackageSetting setting = mSettings.getPackageLPr(packageName); + if (setting == null) { + return COMPONENT_ENABLED_STATE_DEFAULT; + } + return setting.getEnabled(userId); + } + } } @GuardedBy("mPackages") @@ -23329,6 +23367,15 @@ public class PackageManagerService extends IPackageManager.Stub } } + void forEachPackage(Consumer<PackageParser.Package> actionLocked) { + synchronized (mPackages) { + int numPackages = mPackages.size(); + for (int i = 0; i < numPackages; i++) { + actionLocked.accept(mPackages.valueAt(i)); + } + } + } + private static void enforceSystemOrPhoneCaller(String tag) { int callingUid = Binder.getCallingUid(); if (callingUid != Process.PHONE_UID && callingUid != Process.SYSTEM_UID) { diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index e9b9930600a0..68fe1d8a05f8 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -189,7 +189,7 @@ public final class DefaultPermissionGrantPolicy { private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>(); static { // STOPSHIP(b/112545973): remove once feature enabled by default - if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (!StorageManager.hasIsolatedStorage()) { STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE); STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } @@ -198,7 +198,7 @@ public final class DefaultPermissionGrantPolicy { private static final Set<String> MEDIA_AURAL_PERMISSIONS = new ArraySet<>(); static { // STOPSHIP(b/112545973): remove once feature enabled by default - if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (StorageManager.hasIsolatedStorage()) { MEDIA_AURAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO); } } @@ -206,7 +206,7 @@ public final class DefaultPermissionGrantPolicy { private static final Set<String> MEDIA_VISUAL_PERMISSIONS = new ArraySet<>(); static { // STOPSHIP(b/112545973): remove once feature enabled by default - if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + if (StorageManager.hasIsolatedStorage()) { MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO); MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES); } diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index b390eebf3d7e..b065470fe98c 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -30,20 +30,27 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManagerInternal; +import android.content.pm.Signature; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManagerInternal; import android.text.TextUtils; import android.util.ArraySet; +import android.util.PackageUtils; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.BitUtils; +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.FunctionalUtils; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; +import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.util.ArrayList; import java.util.Collections; @@ -117,13 +124,18 @@ public class RoleManagerService extends SystemService { @Override public void onStartUser(@UserIdInt int userId) { + RoleUserState userState; synchronized (mLock) { - //TODO only call into PermissionController if it or system upgreaded (for boot time) - getUserStateLocked(userId); + userState = getUserStateLocked(userId); } - //TODO consider calling grants only when certain conditions are met - // such as OS or PermissionController upgrade - if (RemoteRoleControllerService.DEBUG) { + String packagesHash = computeComponentStateHash(userId); + boolean needGrant; + synchronized (mLock) { + needGrant = !packagesHash.equals(userState.getLastGrantPackagesHashLocked()); + } + if (needGrant) { + // Some vital packages state has changed since last role grant + // Run grants again Slog.i(LOG_TAG, "Granting default permissions..."); CompletableFuture<Void> result = new CompletableFuture<>(); getControllerService(userId).onGrantDefaultRoles( @@ -140,12 +152,47 @@ public class RoleManagerService extends SystemService { }); try { result.get(5, TimeUnit.SECONDS); + synchronized (mLock) { + userState.setLastGrantPackagesHashLocked(packagesHash); + } } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e); } + } else if (RemoteRoleControllerService.DEBUG) { + Slog.i(LOG_TAG, "Already ran grants for package state " + packagesHash); } } + private String computeComponentStateHash(int userId) { + PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + pm.forEachPackage(FunctionalUtils.uncheckExceptions(pkg -> { + out.write(pkg.packageName.getBytes()); + out.write(BitUtils.toBytes(pkg.getLongVersionCode())); + out.write(pm.getApplicationEnabledState(pkg.packageName, userId)); + + ArraySet<String> enabledComponents = + pm.getEnabledComponents(pkg.packageName, userId); + int numComponents = CollectionUtils.size(enabledComponents); + for (int i = 0; i < numComponents; i++) { + out.write(enabledComponents.valueAt(i).getBytes()); + } + + ArraySet<String> disabledComponents = + pm.getDisabledComponents(pkg.packageName, userId); + numComponents = CollectionUtils.size(disabledComponents); + for (int i = 0; i < numComponents; i++) { + out.write(disabledComponents.valueAt(i).getBytes()); + } + for (Signature signature : pkg.mSigningDetails.signatures) { + out.write(signature.toByteArray()); + } + })); + + return PackageUtils.computeSha256Digest(out.toByteArray()); + } + @GuardedBy("mLock") @NonNull private RoleUserState getUserStateLocked(@UserIdInt int userId) { diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java index 9c43f4d02ad0..f218d3a5834b 100644 --- a/services/core/java/com/android/server/role/RoleUserState.java +++ b/services/core/java/com/android/server/role/RoleUserState.java @@ -31,6 +31,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.function.pooled.PooledLambda; import libcore.io.IoUtils; @@ -63,6 +64,7 @@ public class RoleUserState { private static final String TAG_HOLDER = "holder"; private static final String ATTRIBUTE_VERSION = "version"; private static final String ATTRIBUTE_NAME = "name"; + private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash"; @UserIdInt private final int mUserId; @@ -70,11 +72,15 @@ public class RoleUserState { @GuardedBy("RoleManagerService.mLock") private int mVersion; + @GuardedBy("RoleManagerService.mLock") + private String mLastGrantPackagesHash = null; + /** * Maps role names to its holders' package names. The values should never be null. */ @GuardedBy("RoleManagerService.mLock") - private ArrayMap<String, ArraySet<String>> mRoles; + @Nullable + private ArrayMap<String, ArraySet<String>> mRoles = null; @GuardedBy("RoleManagerService.mLock") private boolean mDestroyed; @@ -110,6 +116,23 @@ public class RoleUserState { } /** + * Get the hash representing the state of packages during the last time initial grants was run + */ + @GuardedBy("RoleManagerService.mLock") + public String getLastGrantPackagesHashLocked() { + return mLastGrantPackagesHash; + } + + /** + * Set the hash representing the state of packages during the last time initial grants was run + */ + @GuardedBy("RoleManagerService.mLock") + public void setLastGrantPackagesHashLocked(String lastGrantPackagesHash) { + mLastGrantPackagesHash = lastGrantPackagesHash; + writeAsyncLocked(); + } + + /** * Get whether the role is available. * * @param roleName the name of the role to get the holders for @@ -227,11 +250,11 @@ public class RoleUserState { * Schedule writing the state to file. */ @GuardedBy("RoleManagerService.mLock") - private void writeAsyncLocked() { + void writeAsyncLocked() { throwIfDestroyedLocked(); int version = mVersion; ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>(); - for (int i = 0, size = mRoles.size(); i < size; ++i) { + for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) { String roleName = mRoles.keyAt(i); ArraySet<String> roleHolders = mRoles.valueAt(i); roleHolders = new ArraySet<>(roleHolders); @@ -240,11 +263,12 @@ public class RoleUserState { mWriteHandler.removeCallbacksAndMessages(null); // TODO: Throttle writes. mWriteHandler.sendMessage(PooledLambda.obtainMessage( - RoleUserState::writeSync, this, version, roles)); + RoleUserState::writeSync, this, version, roles, mLastGrantPackagesHash)); } @WorkerThread - private void writeSync(int version, @NonNull ArrayMap<String, ArraySet<String>> roles) { + private void writeSync(int version, @NonNull ArrayMap<String, ArraySet<String>> roles, + String packagesHash) { AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId); FileOutputStream out = null; try { @@ -256,7 +280,7 @@ public class RoleUserState { "http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startDocument(null, true); - serializeRoles(serializer, version, roles); + serializeRoles(serializer, version, roles, packagesHash); serializer.endDocument(); atomicFile.finishWrite(out); @@ -272,9 +296,11 @@ public class RoleUserState { @WorkerThread private void serializeRoles(@NonNull XmlSerializer serializer, int version, - @NonNull ArrayMap<String, ArraySet<String>> roles) throws IOException { + @NonNull ArrayMap<String, ArraySet<String>> roles, String packagesHash) + throws IOException { serializer.startTag(null, TAG_ROLES); serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); + serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash); for (int i = 0, size = roles.size(); i < size; ++i) { String roleName = roles.keyAt(i); ArraySet<String> roleHolders = roles.valueAt(i); @@ -341,6 +367,7 @@ public class RoleUserState { private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION)); + mLastGrantPackagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH); mRoles = new ArrayMap<>(); int type; diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 21adc47b1e30..f0ebb7512015 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -195,10 +195,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { "/system/bin/traced", // Perfetto. "/system/bin/traced_probes", // Perfetto. "webview_zygote", - // Temporarily excluded zygote to investigate its forking consequences in - // NativeProcessMemoryState. - // "zygote", - // "zygote64", + "zygote", + "zygote64", }; private static final int CPU_TIME_PER_THREAD_FREQ_NUM_FREQUENCIES = 8; @@ -1090,6 +1088,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullNativeProcessMemoryState( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { + final List<String> processNames = Arrays.asList(MEMORY_INTERESTING_NATIVE_PROCESSES); int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES); for (int i = 0; i < pids.length; i++) { int pid = pids[i]; @@ -1099,6 +1098,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } int uid = getUidForPid(pid); String processName = readCmdlineFromProcfs(pid); + // Sometimes we get here processName that is not included in the whitelist. It comes + // from forking the zygote for an app. We can ignore that sample because this process + // is collected by ProcessMemoryState. + if (!processNames.contains(processName)) { + continue; + } StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); e.writeInt(uid); e.writeString(processName); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 0d66a2c8b442..e645b84e83a0 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -24,7 +24,7 @@ import android.app.StatusBarManager; import android.content.ComponentName; import android.content.Context; import android.graphics.Rect; -import android.hardware.biometrics.IBiometricPromptReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -598,8 +598,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, - boolean requireConfirmation, int userId) { + public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int type, boolean requireConfirmation, int userId) { enforceBiometricDialog(); if (mBar != null) { try { @@ -654,6 +654,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override + public void showBiometricTryAgain() { + enforceBiometricDialog(); + if (mBar != null) { + try { + mBar.showBiometricTryAgain(); + } catch (RemoteException ex) { + } + } + } + + @Override public void disable(int what, IBinder token, String pkg) { disableForUser(what, token, pkg, mCurrentUserId); } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index d5e59c8dfd6a..1163d3916cb1 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -626,7 +626,7 @@ public final class TvInputManagerService extends SystemService { updateServiceConnectionLocked(serviceState.component, userId); } - private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken, + private boolean createSessionInternalLocked(ITvInputService service, IBinder sessionToken, int userId) { UserState userState = getOrCreateUserStateLocked(userId); SessionState sessionState = userState.sessionStateMap.get(sessionToken); @@ -638,6 +638,7 @@ public final class TvInputManagerService extends SystemService { // Set up a callback to send the session token. ITvInputSessionCallback callback = new SessionCallback(sessionState, channels); + boolean created = true; // Create a session. When failed, send a null token immediately. try { if (sessionState.isRecordingSession) { @@ -647,11 +648,12 @@ public final class TvInputManagerService extends SystemService { } } catch (RemoteException e) { Slog.e(TAG, "error in createSession", e); - removeSessionStateLocked(sessionToken, userId); sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null, null, sessionState.seq); + created = false; } channels[1].dispose(); + return created; } private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId, @@ -1193,8 +1195,10 @@ public final class TvInputManagerService extends SystemService { serviceState.sessionTokens.add(sessionToken); if (serviceState.service != null) { - createSessionInternalLocked(serviceState.service, sessionToken, - resolvedUserId); + if (!createSessionInternalLocked(serviceState.service, sessionToken, + resolvedUserId)) { + removeSessionStateLocked(sessionToken, resolvedUserId); + } } else { updateServiceConnectionLocked(info.getComponent(), resolvedUserId); } @@ -2282,9 +2286,17 @@ public final class TvInputManagerService extends SystemService { } } + List<IBinder> tokensToBeRemoved = new ArrayList<>(); + // And create sessions, if any. for (IBinder sessionToken : serviceState.sessionTokens) { - createSessionInternalLocked(serviceState.service, sessionToken, mUserId); + if (!createSessionInternalLocked(serviceState.service, sessionToken, mUserId)) { + tokensToBeRemoved.add(sessionToken); + } + } + + for (IBinder sessionToken : tokensToBeRemoved) { + removeSessionStateLocked(sessionToken, mUserId); } for (TvInputState inputState : userState.inputMap.values()) { diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 8045fd50adee..f3c5630b53f0 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -662,31 +662,29 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags, int filterCallingUid) { - synchronized (mService.mGlobalLock) { - try { - Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent"); - int modifiedFlags = flags - | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS; - if (intent.isWebIntent() - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { - modifiedFlags |= PackageManager.MATCH_INSTANT; - } + try { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent"); + int modifiedFlags = flags + | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS; + if (intent.isWebIntent() + || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { + modifiedFlags |= PackageManager.MATCH_INSTANT; + } - // In order to allow cross-profile lookup, we clear the calling identity here. - // Note the binder identity won't affect the result, but filterCallingUid will. + // In order to allow cross-profile lookup, we clear the calling identity here. + // Note the binder identity won't affect the result, but filterCallingUid will. - // Cross-user/profile call check are done at the entry points - // (e.g. AMS.startActivityAsUser). - final long token = Binder.clearCallingIdentity(); - try { - return mService.getPackageManagerInternalLocked().resolveIntent( - intent, resolvedType, modifiedFlags, userId, true, filterCallingUid); - } finally { - Binder.restoreCallingIdentity(token); - } + // Cross-user/profile call check are done at the entry points + // (e.g. AMS.startActivityAsUser). + final long token = Binder.clearCallingIdentity(); + try { + return mService.getPackageManagerInternalLocked().resolveIntent( + intent, resolvedType, modifiedFlags, userId, true, filterCallingUid); } finally { - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + Binder.restoreCallingIdentity(token); } + } finally { + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 4ad97bb99b7e..0967afda6d2d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -190,6 +190,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IUserManager; import android.os.LocaleList; +import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; import android.os.PowerManager; @@ -246,7 +247,6 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.AppOpsService; import com.android.server.AttributeCache; -import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; @@ -753,9 +753,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mGlobalLock; } - public void setActivityManagerService(IntentFirewall intentFirewall, - PendingIntentController intentController) { - mH = new H(); + public void initialize(IntentFirewall intentFirewall, PendingIntentController intentController, + Looper looper) { + mH = new H(looper); mUiHandler = new UiHandler(); mIntentFirewall = intentFirewall; final File systemDir = SystemServiceManager.ensureSystemDir(); @@ -5593,8 +5593,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_SUPERVISOR_STACK_MSG = 200; - public H() { - super(DisplayThread.get().getLooper()); + H(Looper looper) { + super(looper); } @Override diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 871ceaf4d231..05e82676a40a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -29,12 +29,12 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; +import static android.view.InsetsState.TYPE_IME; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.View.GONE; -import static android.view.InsetsState.TYPE_IME; import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_TOP; @@ -4736,4 +4736,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo || mDisplayId == mWmService.mVr2dDisplayId || mWmService.mForceDesktopModeOnExternalDisplays; } + + /** + * Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and + * {@link #mOverlayLayer} to the specified surfaceControl. + * + * @param surfaceControlHandle The handle for the new SurfaceControl, where the DisplayContent's + * surfaces will be re-parented to. + */ + void reparentDisplayContent(IBinder surfaceControlHandle) { + mPendingTransaction.reparent(mWindowingLayer, surfaceControlHandle) + .reparent(mOverlayLayer, surfaceControlHandle); + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4085f3d8062a..52b24b3e7307 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -66,6 +66,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN; import static com.android.server.LockGuard.INDEX_WINDOW; @@ -208,6 +209,7 @@ import android.view.IWindowSessionCallback; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEventReceiver; +import android.view.InsetsState; import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.MotionEvent; @@ -218,7 +220,6 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; import android.view.WindowContentFrameStats; -import android.view.InsetsState; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.RemoveContentMode; @@ -5640,22 +5641,12 @@ public class WindowManagerService extends IWindowManager.Stub } } - // TODO: Make the callers use getNavBarPosition(int) only. - /** - * Used by SystemUI and shared SystemUI libraries. - * @see DisplayPolicy#getNavBarPosition() - */ - @Override - @WindowManagerPolicy.NavigationBarPosition - public int getNavBarPosition() { - return getNavBarPosition(Display.DEFAULT_DISPLAY); - } - /** * Used by ActivityManager to determine where to position an app with aspect ratio shorter then * the screen is. * @see DisplayPolicy#getNavBarPosition() */ + @Override @WindowManagerPolicy.NavigationBarPosition public int getNavBarPosition(int displayId) { synchronized (mGlobalLock) { @@ -5665,7 +5656,7 @@ public class WindowManagerService extends IWindowManager.Stub if (displayContent == null) { Slog.w(TAG, "getNavBarPosition with invalid displayId=" + displayId + " callers=" + Debug.getCallers(3)); - return -1; + return NAV_BAR_INVALID; } displayContent.performLayout(false /* initial */, false /* updateInputWindows */); @@ -7457,4 +7448,29 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + @Override + public void reparentDisplayContent(int displayId, IBinder surfaceControlHandle) { + final Display display = mDisplayManager.getDisplay(displayId); + if (display == null) { + throw new IllegalArgumentException( + "Can't reparent display for non-existent displayId: " + displayId); + } + + final int callingUid = Binder.getCallingUid(); + final int displayOwnerUid = display.getOwnerUid(); + if (callingUid != displayOwnerUid) { + throw new SecurityException("Only owner of the display can reparent surfaces to it."); + } + + synchronized (mGlobalLock) { + long token = Binder.clearCallingIdentity(); + try { + DisplayContent displayContent = getDisplayContentOrCreate(displayId, null); + displayContent.reparentDisplayContent(surfaceControlHandle); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index b36a8a7cdf19..43d2dcf7e0d1 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1750,7 +1750,7 @@ int register_android_server_InputManager(JNIEnv* env) { jclass clazz; FIND_CLASS(clazz, "com/android/server/input/InputManagerService"); - gServiceClassInfo.clazz = clazz; + gServiceClassInfo.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(clazz)); GET_METHOD_ID(gServiceClassInfo.notifyConfigurationChanged, clazz, "notifyConfigurationChanged", "(J)V"); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index ead9731782e8..3a56419f67ae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -34,7 +34,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; @@ -403,11 +402,10 @@ class ActivityTestsBase { doReturn(true).when(this).isBackgroundActivityStartsEnabled(); } - void setActivityManagerService(IntentFirewall intentFirewall, - PendingIntentController intentController, ActivityManagerInternal amInternal, - WindowManagerService wm) { + void setup(IntentFirewall intentFirewall, PendingIntentController intentController, + ActivityManagerInternal amInternal, WindowManagerService wm, Looper looper) { mAmInternal = amInternal; - setActivityManagerService(intentFirewall, intentController); + initialize(intentFirewall, intentController, looper); initRootActivityContainerMocks(wm); setWindowManager(wm); } @@ -517,8 +515,8 @@ class ActivityTestsBase { mWindowManager = prepareMockWindowManager(); mUgmInternal = mock(UriGrantsManagerInternal.class); - atm.setActivityManagerService(mIntentFirewall, mPendingIntentController, - new LocalService(), mWindowManager); + atm.setup(mIntentFirewall, mPendingIntentController, new LocalService(), mWindowManager, + testInjector.mHandlerThread.getLooper()); mActivityTaskManager = atm; mAtmInternal = atm.mInternal; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index ef290d23fcdb..d54da0905e18 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4217,7 +4217,8 @@ public class TelephonyManager { } /** - * Returns the voice mail count for a subscription. Return 0 if unavailable. + * Returns the voice mail count for a subscription. Return 0 if unavailable or the caller does + * not have the READ_PHONE_STATE permission. * @param subId whose voice message count is returned * @hide */ @@ -4228,7 +4229,7 @@ public class TelephonyManager { ITelephony telephony = getITelephony(); if (telephony == null) return 0; - return telephony.getVoiceMessageCountForSubscriber(subId); + return telephony.getVoiceMessageCountForSubscriber(subId, getOpPackageName()); } catch (RemoteException ex) { return 0; } catch (NullPointerException ex) { @@ -8582,7 +8583,8 @@ public class TelephonyManager { /** * Return the application ID for the uicc application type like {@link #APPTYPE_CSIM}. - * All uicc applications are uniquely identified by application ID. See ETSI 102.221 and 101.220 + * All uicc applications are uniquely identified by application ID, represented by the hex + * string. e.g, A00000015141434C00. See ETSI 102.221 and 101.220 * <p>Requires Permission: * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} * diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index fc42de58319e..32e939a0c925 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -393,16 +393,11 @@ interface ITelephony { int getDataActivationState(int subId, String callingPackage); /** - * Returns the unread count of voicemails - */ - int getVoiceMessageCount(); - - /** * Returns the unread count of voicemails for a subId. * @param subId user preferred subId. * Returns the unread count of voicemails */ - int getVoiceMessageCountForSubscriber(int subId); + int getVoiceMessageCountForSubscriber(int subId, String callingPackage); /** * Returns true if current state supports both voice and data diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py index fdc800bcc177..2f1e53ca5065 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ b/tools/hiddenapi/generate_hiddenapi_lists.py @@ -15,23 +15,54 @@ # limitations under the License. """ Generate API lists for non-SDK API enforcement. - -usage: generate-hiddenapi-lists.py [-h] - --input-public INPUT_PUBLIC - --input-private INPUT_PRIVATE - [--input-whitelists [INPUT_WHITELISTS [INPUT_WHITELISTS ...]]] - [--input-greylists [INPUT_GREYLISTS [INPUT_GREYLISTS ...]]] - [--input-blacklists [INPUT_BLACKLISTS [INPUT_BLACKLISTS ...]]] - --output-whitelist OUTPUT_WHITELIST - --output-light-greylist OUTPUT_LIGHT_GREYLIST - --output-dark-greylist OUTPUT_DARK_GREYLIST - --output-blacklist OUTPUT_BLACKLIST """ import argparse import os import sys import re +# Names of flags recognized by the `hiddenapi` tool. +FLAG_WHITELIST = "whitelist" +FLAG_GREYLIST = "greylist" +FLAG_BLACKLIST = "blacklist" +FLAG_GREYLIST_MAX_O = "greylist-max-o" + +# List of all known flags. +FLAGS = [ + FLAG_WHITELIST, + FLAG_GREYLIST, + FLAG_BLACKLIST, + FLAG_GREYLIST_MAX_O, +] +FLAGS_SET = set(FLAGS) + +# Suffix used in command line args to express that only known and +# otherwise unassigned entries should be assign the given flag. +# For example, the P dark greylist is checked in as it was in P, +# but signatures have changes since then. The flag instructs this +# script to skip any entries which do not exist any more. +FLAG_IGNORE_CONFLICTS_SUFFIX = "-ignore-conflicts" + +# Regex patterns of fields/methods used in serialization. These are +# considered public API despite being hidden. +SERIALIZATION_PATTERNS = [ + r'readObject\(Ljava/io/ObjectInputStream;\)V', + r'readObjectNoData\(\)V', + r'readResolve\(\)Ljava/lang/Object;', + r'serialVersionUID:J', + r'serialPersistentFields:\[Ljava/io/ObjectStreamField;', + r'writeObject\(Ljava/io/ObjectOutputStream;\)V', + r'writeReplace\(\)Ljava/lang/Object;', +] + +# Single regex used to match serialization API. It combines all the +# SERIALIZATION_PATTERNS into a single regular expression. +SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$') + +# Predicates to be used with filter_apis. +IS_UNASSIGNED = lambda api, flags: not flags +IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api) + def get_args(): """Parses command line arguments. @@ -39,21 +70,21 @@ def get_args(): Namespace: dictionary of parsed arguments """ parser = argparse.ArgumentParser() - parser.add_argument('--input-public', required=True, help='List of all public members') - parser.add_argument('--input-private', required=True, help='List of all private members') - parser.add_argument( - '--input-whitelists', nargs='*', - help='Lists of members to force on whitelist') - parser.add_argument( - '--input-greylists', nargs='*', - help='Lists of members to force on light greylist') - parser.add_argument( - '--input-blacklists', nargs='*', - help='Lists of members to force on blacklist') - parser.add_argument('--output-whitelist', required=True) - parser.add_argument('--output-light-greylist', required=True) - parser.add_argument('--output-dark-greylist', required=True) - parser.add_argument('--output-blacklist', required=True) + parser.add_argument('--output', required=True) + parser.add_argument('--public', required=True, help='list of all public entries') + parser.add_argument('--private', required=True, help='list of all private entries') + parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE', + help='CSV files to be merged into output') + + for flag in FLAGS: + ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX + parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE', + help='lists of entries with flag "' + flag + '"') + parser.add_argument('--' + ignore_conflicts_flag, dest=ignore_conflicts_flag, nargs='*', + default=[], metavar='TXT_FILE', + help='lists of entries with flag "' + flag + + '". skip entry if missing or flag conflict.') + return parser.parse_args() def read_lines(filename): @@ -65,10 +96,13 @@ def read_lines(filename): filename (string): Path to the file to read from. Returns: - list: Lines of the loaded file as a list of strings. + Lines of the file as a list of string. """ with open(filename, 'r') as f: - return filter(lambda line: not line.startswith('#'), f.readlines()) + lines = f.readlines(); + lines = filter(lambda line: not line.startswith('#'), lines) + lines = map(lambda line: line.strip(), lines) + return set(lines) def write_lines(filename, lines): """Writes list of lines into a file, overwriting the file it it exists. @@ -77,167 +111,168 @@ def write_lines(filename, lines): filename (string): Path to the file to be writting into. lines (list): List of strings to write into the file. """ + lines = map(lambda line: line + '\n', lines) with open(filename, 'w') as f: f.writelines(lines) -def move_between_sets(subset, src, dst, source = "<unknown>"): - """Removes a subset of elements from one set and add it to another. - - Args: - subset (set): The subset of `src` to be moved from `src` to `dst`. - src (set): Source set. Must be a superset of `subset`. - dst (set): Destination set. Must be disjoint with `subset`. - """ - assert src.issuperset(subset), ( - "Error processing: {}\n" - "The following entries were not found:\n" - "{}" - "Please visit go/hiddenapi for more information.").format( - source, "".join(map(lambda x: " " + str(x), subset.difference(src)))) - assert dst.isdisjoint(subset) - # Order matters if `src` and `subset` are the same object. - dst.update(subset) - src.difference_update(subset) - -def get_package_name(signature): - """Returns the package name prefix of a class member signature. - - Example: "Ljava/lang/String;->hashCode()J" --> "Ljava/lang/" - - Args: - signature (string): Member signature - - Returns - string: Package name of the given member - """ - class_name_end = signature.find("->") - assert class_name_end != -1, "Invalid signature: {}".format(signature) - package_name_end = signature.rfind("/", 0, class_name_end) - assert package_name_end != -1, "Invalid signature: {}".format(signature) - return signature[:package_name_end + 1] - -def all_package_names(*args): - """Returns a set of packages names in given lists of member signatures. - - Example: args = [ set([ "Lpkg1/ClassA;->foo()V", "Lpkg2/ClassB;->bar()J" ]), - set([ "Lpkg1/ClassC;->baz()Z" ]) ] - return value = set([ "Lpkg1/", "Lpkg2" ]) - - Args: - *args (list): List of sets to iterate over and extract the package names - of its elements (member signatures) - - Returns: - set: All package names extracted from the given lists of signatures. - """ - packages = set() - for arg in args: - packages = packages.union(map(get_package_name, arg)) - return packages - -def move_all(src, dst): - """Moves all elements of one set to another. - - Args: - src (set): Source set. Will become empty. - dst (set): Destination set. Will contain all elements of `src`. - """ - move_between_sets(src, src, dst) - -def move_from_files(filenames, src, dst): - """Loads member signatures from a list of files and moves them to a given set. - - Opens files in `filenames`, reads all their lines and moves those from `src` - set to `dst` set. - - Args: - filenames (list): List of paths to files to be loaded. - src (set): Set that loaded lines should be moved from. - dst (set): Set that loaded lines should be moved to. - """ - if filenames: - for filename in filenames: - move_between_sets(set(read_lines(filename)), src, dst, filename) - -def move_serialization(src, dst): - """Moves all members matching serialization API signatures between given sets. - - Args: - src (set): Set that will be searched for serialization API and that API - will be removed from it. - dst (set): Set that serialization API will be moved to. - """ - serialization_patterns = [ - r'readObject\(Ljava/io/ObjectInputStream;\)V', - r'readObjectNoData\(\)V', - r'readResolve\(\)Ljava/lang/Object;', - r'serialVersionUID:J', - r'serialPersistentFields:\[Ljava/io/ObjectStreamField;', - r'writeObject\(Ljava/io/ObjectOutputStream;\)V', - r'writeReplace\(\)Ljava/lang/Object;', - ] - regex = re.compile(r'.*->(' + '|'.join(serialization_patterns) + r')$') - move_between_sets(filter(lambda api: regex.match(api), src), src, dst) - -def move_from_packages(packages, src, dst): - """Moves all members of given package names from one set to another. - - Args: - packages (list): List of string package names. - src (set): Set that will be searched for API matching one of the given - package names. Surch API will be removed from the set. - dst (set): Set that matching API will be moved to. - """ - move_between_sets(filter(lambda api: get_package_name(api) in packages, src), src, dst) +class FlagsDict: + def __init__(self, public_api, private_api): + # Bootstrap the entries dictionary. + + # Check that the two sets do not overlap. + public_api_set = set(public_api) + private_api_set = set(private_api) + assert public_api_set.isdisjoint(private_api_set), ( + "Lists of public and private API overlap. " + + "This suggests an issue with the `hiddenapi` build tool.") + + # Compute the whole key set + self._dict_keyset = public_api_set.union(private_api_set) + + # Create a dict that creates entries for both public and private API, + # and assigns public API to the whitelist. + self._dict = {} + for api in public_api: + self._dict[api] = set([ FLAG_WHITELIST ]) + for api in private_api: + self._dict[api] = set() + + def _check_entries_set(self, keys_subset, source): + assert isinstance(keys_subset, set) + assert keys_subset.issubset(self._dict_keyset), ( + "Error processing: {}\n" + "The following entries were unexpected:\n" + "{}" + "Please visit go/hiddenapi for more information.").format( + source, "".join(map(lambda x: " " + str(x), keys_subset - self._dict_keyset))) + + def _check_flags_set(self, flags_subset, source): + assert isinstance(flags_subset, set) + assert flags_subset.issubset(FLAGS_SET), ( + "Error processing: {}\n" + "The following flags were not recognized: \n" + "{}\n" + "Please visit go/hiddenapi for more information.").format( + source, "\n".join(flags_subset - FLAGS_SET)) + + def filter_apis(self, filter_fn): + """Returns APIs which match a given predicate. + + This is a helper function which allows to filter on both signatures (keys) and + flags (values). The built-in filter() invokes the lambda only with dict's keys. + + Args: + filter_fn : Function which takes two arguments (signature/flags) and returns a boolean. + + Returns: + A set of APIs which match the predicate. + """ + return set(filter(lambda x: filter_fn(x, self._dict[x]), self._dict_keyset)) + + def get_valid_subset_of_unassigned_apis(self, api_subset): + """Sanitizes a key set input to only include keys which exist in the dictionary + and have not been assigned any flags. + + Args: + entries_subset (set/list): Key set to be sanitized. + + Returns: + Sanitized key set. + """ + assert isinstance(api_subset, set) + return api_subset.intersection(self.filter_apis(IS_UNASSIGNED)) + + def generate_csv(self): + """Constructs CSV entries from a dictionary. + + Returns: + List of lines comprising a CSV file. See "parse_and_merge_csv" for format description. + """ + return sorted(map(lambda api: ",".join([api] + sorted(self._dict[api])), self._dict)) + + def parse_and_merge_csv(self, csv_lines, source = "<unknown>"): + """Parses CSV entries and merges them into a given dictionary. + + The expected CSV format is: + <api signature>,<flag1>,<flag2>,...,<flagN> + + Args: + csv_lines (list of strings): Lines read from a CSV file. + source (string): Origin of `csv_lines`. Will be printed in error messages. + + Throws: + AssertionError if parsed API signatures of flags are invalid. + """ + # Split CSV lines into arrays of values. + csv_values = [ line.split(',') for line in csv_lines ] + + # Check that all entries exist in the dict. + csv_keys = set([ csv[0] for csv in csv_values ]) + self._check_entries_set(csv_keys, source) + + # Check that all flags are known. + csv_flags = set(reduce(lambda x, y: set(x).union(y), [ csv[1:] for csv in csv_values ], [])) + self._check_flags_set(csv_flags, source) + + # Iterate over all CSV lines, find entry in dict and append flags to it. + for csv in csv_values: + self._dict[csv[0]].update(csv[1:]) + + def assign_flag(self, flag, apis, source="<unknown>"): + """Assigns a flag to given subset of entries. + + Args: + flag (string): One of FLAGS. + apis (set): Subset of APIs to recieve the flag. + source (string): Origin of `entries_subset`. Will be printed in error messages. + + Throws: + AssertionError if parsed API signatures of flags are invalid. + """ + # Check that all APIs exist in the dict. + self._check_entries_set(apis, source) + + # Check that the flag is known. + self._check_flags_set(set([ flag ]), source) + + # Iterate over the API subset, find each entry in dict and assign the flag to it. + for api in apis: + self._dict[api].add(flag) def main(argv): - args = get_args() - - # Initialize API sets by loading lists of public and private API. Public API - # are all members resolvable from SDK API stubs, other members are private. - # As an optimization, skip the step of moving public API from a full set of - # members and start with a populated whitelist. - whitelist = set(read_lines(args.input_public)) - uncategorized = set(read_lines(args.input_private)) - light_greylist = set() - dark_greylist = set() - blacklist = set() - - # Assert that there is no overlap between public and private API. - assert whitelist.isdisjoint(uncategorized) - num_all_api = len(whitelist) + len(uncategorized) - - # Read all files which manually assign members to specific lists. - move_from_files(args.input_whitelists, uncategorized, whitelist) - move_from_files(args.input_greylists, uncategorized, light_greylist) - move_from_files(args.input_blacklists, uncategorized, blacklist) - - # Iterate over all uncategorized members and move serialization API to whitelist. - move_serialization(uncategorized, whitelist) - - # Extract package names of members from whitelist and light greylist, which - # are assumed to have been finalized at this point. Assign all uncategorized - # members from the same packages to the dark greylist. - dark_greylist_packages = all_package_names(whitelist, light_greylist) - move_from_packages(dark_greylist_packages, uncategorized, dark_greylist) - - # Assign all uncategorized members to the blacklist. - move_all(uncategorized, blacklist) - - # Assert we have not missed anything. - assert whitelist.isdisjoint(light_greylist) - assert whitelist.isdisjoint(dark_greylist) - assert whitelist.isdisjoint(blacklist) - assert light_greylist.isdisjoint(dark_greylist) - assert light_greylist.isdisjoint(blacklist) - assert dark_greylist.isdisjoint(blacklist) - assert num_all_api == len(whitelist) + len(light_greylist) + len(dark_greylist) + len(blacklist) - - # Write final lists to disk. - write_lines(args.output_whitelist, whitelist) - write_lines(args.output_light_greylist, light_greylist) - write_lines(args.output_dark_greylist, dark_greylist) - write_lines(args.output_blacklist, blacklist) + # Parse arguments. + args = vars(get_args()) + + flags = FlagsDict(read_lines(args["public"]), read_lines(args["private"])) + + # Combine inputs which do not require any particular order. + # (1) Assign serialization API to whitelist. + flags.assign_flag(FLAG_WHITELIST, flags.filter_apis(IS_SERIALIZATION)) + + # (2) Merge input CSV files into the dictionary. + for filename in args["csv"]: + flags.parse_and_merge_csv(read_lines(filename), filename) + + # (3) Merge text files with a known flag into the dictionary. + for flag in FLAGS: + for filename in args[flag]: + flags.assign_flag(flag, read_lines(filename), filename) + + # Merge text files where conflicts should be ignored. + # This will only assign the given flag if: + # (a) the entry exists, and + # (b) it has not been assigned any other flag. + # Because of (b), this must run after all strict assignments have been performed. + for flag in FLAGS: + for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]: + valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename)) + flags.assign_flag(flag, valid_entries, filename) + + # Assign all remaining entries to the blacklist. + flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(IS_UNASSIGNED)) + + # Write output. + write_lines(args["output"], flags.generate_csv()) if __name__ == "__main__": main(sys.argv) diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py index 4716241940b5..249f37db5a82 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists_test.py +++ b/tools/hiddenapi/generate_hiddenapi_lists_test.py @@ -2,14 +2,14 @@ # # Copyright (C) 2018 The Android Open Source Project # -# Licensed under the Apache License, Version 2.0 (the "License"); +# Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, +# distributed under the License is distributed on an 'AS IS' BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. @@ -18,90 +18,90 @@ import unittest from generate_hiddenapi_lists import * class TestHiddenapiListGeneration(unittest.TestCase): + def test_init(self): + # Check empty lists + flags = FlagsDict([], []) + self.assertEquals(flags.generate_csv(), []) - def test_move_between_sets(self): - A = set([1, 2, 3, 4]) - B = set([5, 6, 7, 8]) - move_between_sets(set([2, 4]), A, B) - self.assertEqual(A, set([1, 3])) - self.assertEqual(B, set([2, 4, 5, 6, 7, 8])) - - def test_move_between_sets_fail_not_superset(self): - A = set([1, 2, 3, 4]) - B = set([5, 6, 7, 8]) - with self.assertRaises(AssertionError) as ar: - move_between_sets(set([0, 2]), A, B) - - def test_move_between_sets_fail_not_disjoint(self): - A = set([1, 2, 3, 4]) - B = set([4, 5, 6, 7, 8]) - with self.assertRaises(AssertionError) as ar: - move_between_sets(set([1, 4]), A, B) - - def test_get_package_name(self): - self.assertEqual(get_package_name("Ljava/lang/String;->clone()V"), "Ljava/lang/") - - def test_get_package_name_fail_no_arrow(self): - with self.assertRaises(AssertionError) as ar: - get_package_name("Ljava/lang/String;-clone()V") - with self.assertRaises(AssertionError) as ar: - get_package_name("Ljava/lang/String;>clone()V") - with self.assertRaises(AssertionError) as ar: - get_package_name("Ljava/lang/String;__clone()V") - - def test_get_package_name_fail_no_package(self): - with self.assertRaises(AssertionError) as ar: - get_package_name("LString;->clone()V") - - def test_all_package_names(self): - self.assertEqual(all_package_names(), set()) - self.assertEqual(all_package_names(set(["Lfoo/Bar;->baz()V"])), set(["Lfoo/"])) - self.assertEqual( - all_package_names(set(["Lfoo/Bar;->baz()V", "Lfoo/BarX;->bazx()I"])), - set(["Lfoo/"])) - self.assertEqual( - all_package_names( - set(["Lfoo/Bar;->baz()V"]), - set(["Lfoo/BarX;->bazx()I", "Labc/xyz/Mno;->ijk()J"])), - set(["Lfoo/", "Labc/xyz/"])) - - def test_move_all(self): - src = set([ "abc", "xyz" ]) - dst = set([ "def" ]) - move_all(src, dst) - self.assertEqual(src, set()) - self.assertEqual(dst, set([ "abc", "def", "xyz" ])) - - def test_move_from_packages(self): - src = set([ "Lfoo/bar/ClassA;->abc()J", # will be moved - "Lfoo/bar/ClassA;->def()J", # will be moved - "Lcom/pkg/example/ClassD;->ijk:J", # not moved: different package - "Lfoo/bar/xyz/ClassC;->xyz()Z" ]) # not moved: subpackage - dst = set() - packages = set([ "Lfoo/bar/" ]) - move_from_packages(packages, src, dst) - self.assertEqual( - src, set([ "Lfoo/bar/xyz/ClassC;->xyz()Z", "Lcom/pkg/example/ClassD;->ijk:J" ])) + # Check valid input - two public and two private API signatures. + flags = FlagsDict(['A', 'B'], ['C', 'D']) + self.assertEquals(flags.generate_csv(), + [ 'A,' + FLAG_WHITELIST, 'B,' + FLAG_WHITELIST, 'C', 'D' ]) + + # Check invalid input - overlapping public/private API signatures. + with self.assertRaises(AssertionError): + flags = FlagsDict(['A', 'B'], ['B', 'C', 'D']) + + def test_filter_apis(self): + # Initialize flags so that A and B are put on the whitelist and + # C, D, E are left unassigned. Try filtering for the unassigned ones. + flags = FlagsDict(['A', 'B'], ['C', 'D', 'E']) + filter_set = flags.filter_apis(lambda api, flags: not flags) + self.assertTrue(isinstance(filter_set, set)) + self.assertEqual(filter_set, set([ 'C', 'D', 'E' ])) + + def test_get_valid_subset_of_unassigned_keys(self): + # Create flags where only A is unassigned. + flags = FlagsDict(['A'], ['B', 'C']) + flags.assign_flag(FLAG_GREYLIST, set(['C'])) + self.assertEquals(flags.generate_csv(), + [ 'A,' + FLAG_WHITELIST, 'B', 'C,' + FLAG_GREYLIST ]) + + # Check three things: + # (1) B is selected as valid unassigned + # (2) A is not selected because it is assigned 'whitelist' + # (3) D is not selected because it is not a valid key self.assertEqual( - dst, set([ "Lfoo/bar/ClassA;->abc()J", "Lfoo/bar/ClassA;->def()J" ])) - - def test_move_serialization(self): - # All the entries should be moved apart from the last one - src = set([ "Lfoo/bar/ClassA;->readObject(Ljava/io/ObjectInputStream;)V", - "Lfoo/bar/ClassA;->readObjectNoData()V", - "Lfoo/bar/ClassA;->readResolve()Ljava/lang/Object;", - "Lfoo/bar/ClassA;->serialVersionUID:J", - "Lfoo/bar/ClassA;->serialPersistentFields:[Ljava/io/ObjectStreamField;", - "Lfoo/bar/ClassA;->writeObject(Ljava/io/ObjectOutputStream;)V", - "Lfoo/bar/ClassA;->writeReplace()Ljava/lang/Object;", - # Should not be moved as signature does not match - "Lfoo/bar/ClassA;->readObject(Ljava/io/ObjectInputStream;)I"]) - expectedToMove = len(src) - 1 - dst = set() - packages = set([ "Lfoo/bar/" ]) - move_serialization(src, dst) - self.assertEqual(len(src), 1) - self.assertEqual(len(dst), expectedToMove) + flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])), set([ 'B' ])) + + def test_parse_and_merge_csv(self): + flags = FlagsDict(['A'], ['B']) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test empty CSV entry. + flags.parse_and_merge_csv(['B']) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test assigning an already assigned flag. + flags.parse_and_merge_csv(['A,' + FLAG_WHITELIST]) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test new additions. + flags.parse_and_merge_csv([ + 'A,' + FLAG_GREYLIST, + 'B,' + FLAG_BLACKLIST + ',' + FLAG_GREYLIST_MAX_O ]) + self.assertEqual(flags.generate_csv(), + [ 'A,' + FLAG_GREYLIST + "," + FLAG_WHITELIST, + 'B,' + FLAG_BLACKLIST + "," + FLAG_GREYLIST_MAX_O ]) + + # Test unknown API signature. + with self.assertRaises(AssertionError): + flags.parse_and_merge_csv([ 'C' ]) + + # Test unknown flag. + with self.assertRaises(AssertionError): + flags.parse_and_merge_csv([ 'A,foo' ]) + + def test_assign_flag(self): + flags = FlagsDict(['A'], ['B']) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test assigning an already assigned flag. + flags.assign_flag(FLAG_WHITELIST, set([ 'A' ])) + self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ]) + + # Test new additions. + flags.assign_flag(FLAG_GREYLIST, set([ 'A', 'B' ])) + self.assertEquals(flags.generate_csv(), + [ 'A,' + FLAG_GREYLIST + "," + FLAG_WHITELIST, 'B,' + FLAG_GREYLIST ]) + + # Test invalid API signature. + with self.assertRaises(AssertionError): + flags.assign_flag(FLAG_WHITELIST, set([ 'C' ])) + + # Test invalid flag. + with self.assertRaises(AssertionError): + flags.assign_flag('foo', set([ 'A' ])) if __name__ == '__main__': unittest.main() diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 364d5084fbc9..c6acd026bd39 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -190,8 +190,8 @@ interface IWifiManager void unregisterNetworkRequestMatchCallback(int callbackIdentifier); - boolean addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); + int addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); - boolean removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); + int removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 8dd6c771a924..9789319ed6b9 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -25,6 +25,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.UnsupportedAppUsage; +import android.app.ActivityManager; import android.content.Context; import android.content.pm.ParceledListSlice; import android.net.ConnectivityManager; @@ -137,6 +138,55 @@ public class WifiManager { public static final int ERROR_AUTH_FAILURE_EAP_FAILURE = 3; /** + * Maximum number of active network suggestions allowed per app. + * @hide + */ + public static final int NETWORK_SUGGESTIONS_MAX_PER_APP = + ActivityManager.isLowRamDeviceStatic() ? 256 : 1024; + + /** + * Reason code if all of the network suggestions were successfully added or removed. + */ + public static final int STATUS_NETWORK_SUGGESTIONS_SUCCESS = 0; + + /** + * Reason code if there was an internal error in the platform while processing the addition or + * removal of suggestions. + */ + public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL = 1; + + /** + * Reason code if one or more of the network suggestions added already exists in platform's + * database. + * @see WifiNetworkSuggestion#equals(Object) + */ + public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 2; + + /** + * Reason code if the number of network suggestions provided by the app crosses the max + * threshold set per app. + * @see #getMaxNumberOfNetworkSuggestionsPerApp() + */ + public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 3; + + /** + * Reason code if one or more of the network suggestions removed does not exist in platform's + * database. + */ + public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 4; + + @IntDef(prefix = { "STATUS_NETWORK_SUGGESTIONS_" }, value = { + STATUS_NETWORK_SUGGESTIONS_SUCCESS, + STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL, + STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE, + STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP, + STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID, + }) + + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkSuggestionsStatusCode {} + + /** * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently * @hide */ @@ -1126,7 +1176,6 @@ public class WifiManager { * @throws UnsupportedOperationException if Passpoint is not enabled on the device. * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) { try { @@ -1497,12 +1546,13 @@ public class WifiManager { * suggestion back using this API.</li> * * @param networkSuggestions List of network suggestions provided by the app. - * @return true on success, false if any of the suggestions match (See + * @return Status code corresponding to the values in {@link NetworkSuggestionsStatusCode}. * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app. * @throws {@link SecurityException} if the caller is missing required permissions. */ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) - public boolean addNetworkSuggestions(@NonNull List<WifiNetworkSuggestion> networkSuggestions) { + public @NetworkSuggestionsStatusCode int addNetworkSuggestions( + @NonNull List<WifiNetworkSuggestion> networkSuggestions) { try { return mService.addNetworkSuggestions(networkSuggestions, mContext.getOpPackageName()); } catch (RemoteException e) { @@ -1510,21 +1560,20 @@ public class WifiManager { } } - /** - * Remove a subset of or all of networks from previously provided suggestions by the app to the - * device. + * Remove some or all of the network suggestions that were previously provided by the app. * See {@link WifiNetworkSuggestion} for a detailed explanation of the parameters. * See {@link WifiNetworkSuggestion#equals(Object)} for the equivalence evaluation used. * * @param networkSuggestions List of network suggestions to be removed. Pass an empty list * to remove all the previous suggestions provided by the app. - * @return true on success, false if any of the suggestions do not match any suggestions - * previously provided by the app. Any matching suggestions are removed from the device and - * will not be considered for any further connection attempts. + * @return Status code corresponding to the values in + * {@link NetworkSuggestionsStatusCode}. + * Any matching suggestions are removed from the device and will not be considered for any + * further connection attempts. */ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) - public boolean removeNetworkSuggestions( + public @NetworkSuggestionsStatusCode int removeNetworkSuggestions( @NonNull List<WifiNetworkSuggestion> networkSuggestions) { try { return mService.removeNetworkSuggestions( @@ -1535,6 +1584,15 @@ public class WifiManager { } /** + * Returns the max number of network suggestions that are allowed per app on the device. + * @see #addNetworkSuggestions(List) + * @see #removeNetworkSuggestions(List) + */ + public int getMaxNumberOfNetworkSuggestionsPerApp() { + return NETWORK_SUGGESTIONS_MAX_PER_APP; + } + + /** * Add or update a Passpoint configuration. The configuration provides a credential * for connecting to Passpoint networks that are operated by the Passpoint * service provider specified in the configuration. diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java index 87706b936f03..f73b9e5e2a00 100644 --- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java +++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java @@ -42,8 +42,10 @@ import java.util.List; public class WifiNetworkConfigBuilder { private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*"; private static final String MATCH_EMPTY_SSID_PATTERN_PATH = ""; - private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN = + private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN1 = new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); + private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN2 = + new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.BROADCAST_ADDRESS); private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN = new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); private static final MacAddress MATCH_EXACT_BSSID_PATTERN_MASK = @@ -189,7 +191,13 @@ public class WifiNetworkConfigBuilder { * Set the BSSID to use for filtering networks from scan results. Will only match network whose * BSSID is identical to the specified value. * <p> - * <li>Only allowed for creating network specifier, i.e {@link #buildNetworkSpecifier()}. </li> + * <li>For network requests ({@link NetworkSpecifier}), built using + * {@link #buildNetworkSpecifier}, sets the BSSID to use for filtering networks from scan + * results. Will only match networks whose BSSID is identical to specified value.</li> + * <li>For network suggestions ({@link WifiNetworkSuggestion}), built using + * {@link #buildNetworkSuggestion()}, sets a specific BSSID for the network suggestion. + * If set, only the specified BSSID with the specified SSID will be considered for connection. + * If not set, all BSSIDs with the specified SSID will be considered for connection.</li> * <li>Overrides any previous value set using {@link #setBssid(MacAddress)} or * {@link #setBssidPattern(MacAddress, MacAddress)}.</li> * @@ -432,6 +440,9 @@ public class WifiNetworkConfigBuilder { if (mSsidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) { wifiConfiguration.SSID = "\"" + mSsidPatternMatcher.getPath() + "\""; } + if (mBssidPatternMatcher.second == MATCH_EXACT_BSSID_PATTERN_MASK) { + wifiConfiguration.BSSID = mBssidPatternMatcher.first.toString(); + } setSecurityParamsInWifiConfiguration(wifiConfiguration); wifiConfiguration.hiddenSSID = mIsHiddenSSID; wifiConfiguration.priority = mPriority; @@ -460,7 +471,10 @@ public class WifiNetworkConfigBuilder { && mSsidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) { return true; } - if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN)) { + if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN1)) { + return true; + } + if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN2)) { return true; } return false; @@ -474,6 +488,16 @@ public class WifiNetworkConfigBuilder { return false; } + private boolean hasSetMatchExactPattern() { + // exact ssid match with either match-all bssid or match-exact bssid. + if (mSsidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL + && (mBssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN) + || mBssidPatternMatcher.second.equals(MATCH_EXACT_BSSID_PATTERN_MASK))) { + return true; + } + return false; + } + private void validateSecurityParams() { int numSecurityTypes = 0; numSecurityTypes += mIsEnhancedOpen ? 1 : 0; @@ -566,9 +590,42 @@ public class WifiNetworkConfigBuilder { } /** - * Create a network suggestion object use in - * {@link WifiManager#addNetworkSuggestions(List)}. + * Create a network suggestion object use in {@link WifiManager#addNetworkSuggestions(List)}. * See {@link WifiNetworkSuggestion}. + *<p> + * Note: Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID + * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to the + * platform. + * </p> + * + * For example: + * To provide credentials for one open, one WPA2 and one WPA3 network with their + * corresponding SSID's: + * {@code + * final WifiNetworkSuggestion suggestion1 = + * new WifiNetworkConfigBuilder() + * .setSsid("test111111") + * .buildNetworkSuggestion() + * final WifiNetworkSuggestion suggestion2 = + * new WifiNetworkConfigBuilder() + * .setSsid("test222222") + * .setWpa2Passphrase("test123456") + * .buildNetworkSuggestion() + * final WifiNetworkSuggestion suggestion3 = + * new WifiNetworkConfigBuilder() + * .setSsid("test333333") + * .setWpa3Passphrase("test6789") + * .buildNetworkSuggestion() + * final List<WifiNetworkSuggestion> suggestionsList = new ArrayList<WifiNetworkSuggestion> {{ + * add(suggestion1); + * add(suggestion2); + * add(suggestion3); + * }}; + * final WifiManager wifiManager = + * context.getSystemService(Context.WIFI_SERVICE); + * wifiManager.addNetworkSuggestions(suggestionsList); + * ... + * } * * @return Instance of {@link WifiNetworkSuggestion}. * @throws IllegalStateException on invalid params set. @@ -577,11 +634,14 @@ public class WifiNetworkConfigBuilder { if (mSsidPatternMatcher == null) { throw new IllegalStateException("setSsid should be invoked for suggestion"); } - if (mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_LITERAL - || mBssidPatternMatcher != null) { - throw new IllegalStateException("none of setSsidPattern/setBssidPattern/setBssid are" + setMatchAnyPatternIfUnset(); + if (!hasSetMatchExactPattern()) { + throw new IllegalStateException("none of setSsidPattern/setBssidPattern are" + " allowed for suggestion"); } + if (hasSetMatchNonePattern()) { + throw new IllegalStateException("cannot set match-none for suggestion"); + } validateSecurityParams(); return new WifiNetworkSuggestion( diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index 25121e2dc8c7..760f1e6bc5e2 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -104,8 +104,8 @@ public final class WifiNetworkSuggestion implements Parcelable { @Override public int hashCode() { - return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.allowedKeyManagement, - suggestorUid); + return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, + wifiConfiguration.allowedKeyManagement, suggestorUid); } /** @@ -121,6 +121,7 @@ public final class WifiNetworkSuggestion implements Parcelable { } WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj; return Objects.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID) + && Objects.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) && Objects.equals(this.wifiConfiguration.allowedKeyManagement, lhs.wifiConfiguration.allowedKeyManagement) && suggestorUid == lhs.suggestorUid; diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 6622a2571870..fc5caf0a47d7 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -253,6 +253,14 @@ public class WifiScanner { */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public int type = TYPE_LOW_LATENCY; + /** + * This scan request may ignore location settings while receiving scans. This should only + * be used in emergency situations. + * {@hide} + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public boolean ignoreLocationSettings; /** Implement the Parcelable interface {@hide} */ public int describeContents() { @@ -270,6 +278,7 @@ public class WifiScanner { dest.writeInt(stepCount); dest.writeInt(isPnoScan ? 1 : 0); dest.writeInt(type); + dest.writeInt(ignoreLocationSettings ? 1 : 0); if (channels != null) { dest.writeInt(channels.length); for (int i = 0; i < channels.length; i++) { @@ -304,6 +313,7 @@ public class WifiScanner { settings.stepCount = in.readInt(); settings.isPnoScan = in.readInt() == 1; settings.type = in.readInt(); + settings.ignoreLocationSettings = in.readInt() == 1; int num_channels = in.readInt(); settings.channels = new ChannelSpec[num_channels]; for (int i = 0; i < num_channels; i++) { diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java index aa526d248d14..0f4e3a8ba20f 100644 --- a/wifi/java/com/android/server/wifi/AbstractWifiService.java +++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java @@ -442,13 +442,13 @@ public abstract class AbstractWifiService extends IWifiManager.Stub { } @Override - public boolean addNetworkSuggestions( + public int addNetworkSuggestions( List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) { throw new UnsupportedOperationException(); } @Override - public boolean removeNetworkSuggestions( + public int removeNetworkSuggestions( List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) { throw new UnsupportedOperationException(); } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 8fe5af998dcf..13c8c9ea7ead 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1298,13 +1298,26 @@ i * Verify that a call to cancel WPS immediately returns a failure. */ @Test public void addRemoveNetworkSuggestions() throws Exception { - when(mWifiService.addNetworkSuggestions(any(List.class), anyString())).thenReturn(true); - when(mWifiService.removeNetworkSuggestions(any(List.class), anyString())).thenReturn(true); + when(mWifiService.addNetworkSuggestions(any(List.class), anyString())) + .thenReturn(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); + when(mWifiService.removeNetworkSuggestions(any(List.class), anyString())) + .thenReturn(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); - assertTrue(mWifiManager.addNetworkSuggestions(new ArrayList<>())); + assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, + mWifiManager.addNetworkSuggestions(new ArrayList<>())); verify(mWifiService).addNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME)); - assertTrue(mWifiManager.removeNetworkSuggestions(new ArrayList<>())); + assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, + mWifiManager.removeNetworkSuggestions(new ArrayList<>())); verify(mWifiService).removeNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME)); } + + /** + * Verify call to {@link WifiManager#getMaxNumberOfNetworkSuggestionsPerApp()}. + */ + @Test + public void getMaxNumberOfNetworkSuggestionsPerApp() { + assertEquals(WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP, + mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp()); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkConfigBuilderTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkConfigBuilderTest.java index c455c6f0836d..2505499c85d7 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkConfigBuilderTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkConfigBuilderTest.java @@ -244,7 +244,7 @@ public class WifiNetworkConfigBuilderTest { * when match-none SSID pattern is set. */ @Test(expected = IllegalStateException.class) - public void testWifiNetworkSpecifierBuilderWithMatchNoneSsidPattern() { + public void testWifiNetworkSpecifierBuilderWithMatchNoneSsidPattern1() { new WifiNetworkConfigBuilder() .setSsidPattern(new PatternMatcher("", PatternMatcher.PATTERN_LITERAL)) .buildNetworkSpecifier(); @@ -252,10 +252,21 @@ public class WifiNetworkConfigBuilderTest { /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception + * when match-none SSID pattern is set. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSpecifierBuilderWithMatchNoneSsidPattern2() { + new WifiNetworkConfigBuilder() + .setSsid("") + .buildNetworkSpecifier(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception * when match-none BSSID pattern is set. */ @Test(expected = IllegalStateException.class) - public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern() { + public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern1() { new WifiNetworkConfigBuilder() .setBssidPattern(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS) .buildNetworkSpecifier(); @@ -263,6 +274,28 @@ public class WifiNetworkConfigBuilderTest { /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception + * when match-none BSSID pattern is set. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern2() { + new WifiNetworkConfigBuilder() + .setBssid(MacAddress.BROADCAST_ADDRESS) + .buildNetworkSpecifier(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception + * when match-none BSSID pattern is set. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern3() { + new WifiNetworkConfigBuilder() + .setBssid(MacAddress.ALL_ZEROS_ADDRESS) + .buildNetworkSpecifier(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} throws an exception * when SSID pattern is set for hidden network. */ @Test(expected = IllegalStateException.class) @@ -429,13 +462,15 @@ public class WifiNetworkConfigBuilderTest { * {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} for OWE network. */ @Test - public void testWifiNetworkSuggestionBuilderForEnhancedOpenNetwork() { + public void testWifiNetworkSuggestionBuilderForEnhancedOpenNetworkWithBssid() { WifiNetworkSuggestion suggestion = new WifiNetworkConfigBuilder() .setSsid(TEST_SSID) + .setBssid(MacAddress.fromString(TEST_BSSID)) .setIsEnhancedOpen() .buildNetworkSuggestion(); assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID); + assertEquals(TEST_BSSID, suggestion.wifiConfiguration.BSSID); assertTrue(suggestion.wifiConfiguration.allowedKeyManagement .get(WifiConfiguration.KeyMgmt.OWE)); assertNull(suggestion.wifiConfiguration.preSharedKey); @@ -505,7 +540,7 @@ public class WifiNetworkConfigBuilderTest { /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception - * when {@link WifiNetworkConfigBuilder#setBssid(MacAddress)} is set. + * when {@link WifiNetworkConfigBuilder#setBssidPattern(MacAddress, MacAddress)} is set. */ @Test(expected = IllegalStateException.class) public void testWifiNetworkSuggestionBuilderWithBssidPattern() { @@ -518,23 +553,46 @@ public class WifiNetworkConfigBuilderTest { /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception - * when {@link WifiNetworkConfigBuilder#setBssidPattern(MacAddress, MacAddress)} is set. + * when {@link WifiNetworkConfigBuilder#setSsid(String)} is not set. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSuggestionBuilderWithNoSsid() { + new WifiNetworkConfigBuilder() + .buildNetworkSuggestion(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception + * when {@link WifiNetworkConfigBuilder#setSsid(String)} is invoked with an invalid value. */ @Test(expected = IllegalStateException.class) - public void testWifiNetworkSuggestionBuilderWithBssid() { + public void testWifiNetworkSuggestionBuilderWithInvalidSsid() { + new WifiNetworkConfigBuilder() + .setSsid("") + .buildNetworkSuggestion(); + } + + /** + * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception + * when {@link WifiNetworkConfigBuilder#setBssid(MacAddress)} is invoked with an invalid value. + */ + @Test(expected = IllegalStateException.class) + public void testWifiNetworkSuggestionBuilderWithInvalidBroadcastBssid() { new WifiNetworkConfigBuilder() .setSsid(TEST_SSID) - .setBssid(MacAddress.fromString(TEST_BSSID)) + .setBssid(MacAddress.BROADCAST_ADDRESS) .buildNetworkSuggestion(); } /** * Ensure {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} throws an exception - * when {@link WifiNetworkConfigBuilder#setSsid(String)} is not set. + * when {@link WifiNetworkConfigBuilder#setBssid(MacAddress)} is invoked with an invalid value. */ @Test(expected = IllegalStateException.class) - public void testWifiNetworkSuggestionBuilderWithNoSsid() { + public void testWifiNetworkSuggestionBuilderWithInvalidAllZeroBssid() { new WifiNetworkConfigBuilder() + .setSsid(TEST_SSID) + .setBssid(MacAddress.ALL_ZEROS_ADDRESS) .buildNetworkSuggestion(); } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java index 6bab60dd480b..5cc821717462 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java @@ -29,6 +29,7 @@ import org.junit.Test; @SmallTest public class WifiNetworkSuggestionTest { private static final String TEST_SSID = "\"Test123\""; + private static final String TEST_BSSID = "12:12:12:12:12:12"; private static final String TEST_SSID_1 = "\"Test1234\""; /** @@ -38,6 +39,7 @@ public class WifiNetworkSuggestionTest { public void testWifiNetworkSuggestionParcel() { WifiConfiguration configuration = new WifiConfiguration(); configuration.SSID = TEST_SSID; + configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(configuration, false, true, 0); @@ -65,18 +67,20 @@ public class WifiNetworkSuggestionTest { /** * Check NetworkSuggestion equals returns {@code true} for 2 network suggestions with the same - * SSID, key mgmt and UID. + * SSID, BSSID, key mgmt and UID. */ @Test public void testWifiNetworkSuggestionEqualsSame() { WifiConfiguration configuration = new WifiConfiguration(); configuration.SSID = TEST_SSID; + configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(configuration, true, false, 0); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID; + configuration1.BSSID = TEST_BSSID; configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(configuration1, false, true, 0); @@ -86,7 +90,7 @@ public class WifiNetworkSuggestionTest { /** * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same - * key mgmt and UID, but different SSID. + * BSSID, key mgmt and UID, but different SSID. */ @Test public void testWifiNetworkSuggestionEqualsFailsWhenSsidIsDifferent() { @@ -107,7 +111,29 @@ public class WifiNetworkSuggestionTest { /** * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same - * SSID and UID, but different key mgmt. + * SSID, key mgmt and UID, but different BSSID. + */ + @Test + public void testWifiNetworkSuggestionEqualsFailsWhenBssidIsDifferent() { + WifiConfiguration configuration = new WifiConfiguration(); + configuration.SSID = TEST_SSID; + configuration.BSSID = TEST_BSSID; + configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + WifiNetworkSuggestion suggestion = + new WifiNetworkSuggestion(configuration, false, false, 0); + + WifiConfiguration configuration1 = new WifiConfiguration(); + configuration1.SSID = TEST_SSID; + configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + WifiNetworkSuggestion suggestion1 = + new WifiNetworkSuggestion(configuration1, false, false, 0); + + assertNotEquals(suggestion, suggestion1); + } + + /** + * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same + * SSID, BSSID and UID, but different key mgmt. */ @Test public void testWifiNetworkSuggestionEqualsFailsWhenKeyMgmtIsDifferent() { @@ -128,7 +154,7 @@ public class WifiNetworkSuggestionTest { /** * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same - * SSID and key mgmt, but different UID. + * SSID, BSSID and key mgmt, but different UID. */ @Test public void testWifiNetworkSuggestionEqualsFailsWhenUidIsDifferent() { |