Merge "Make ModemActivityInfo isValid system"
diff --git a/Android.bp b/Android.bp
index a26135e..21552695 100644
--- a/Android.bp
+++ b/Android.bp
@@ -200,34 +200,9 @@
}
filegroup {
- name: "framework-wifi-sources",
- srcs: [
- "wifi/java/**/*.java",
- "wifi/java/**/*.aidl",
- ],
- exclude_srcs: [
- ":framework-wifi-non-updatable-sources"
- ],
- path: "wifi/java",
-}
-
-filegroup {
- name: "framework-wifi-non-updatable-sources",
- srcs: [
- // TODO(b/146011398) package android.net.wifi is now split amongst 2 jars: framework.jar and
- // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
- // to a separate package.
- "wifi/java/android/net/wifi/WifiNetworkScoreCache.java",
- "wifi/java/android/net/wifi/WifiCondManager.java",
- "wifi/java/android/net/wifi/wificond/*.java",
- ],
-}
-
-filegroup {
name: "framework-non-updatable-sources",
srcs: [
// Java/AIDL sources under frameworks/base
- ":framework-appsearch-sources",
":framework-blobstore-sources",
":framework-core-sources",
":framework-drm-sources",
@@ -247,9 +222,8 @@
":framework-telecomm-sources",
":framework-telephony-common-sources",
":framework-telephony-sources",
- ":framework-wifi-sources",
+ ":framework-wifi-annotations",
":framework-wifi-non-updatable-sources",
- ":libwificond_ipc_aidl",
":PacProcessor-aidl-sources",
":ProxyHandler-aidl-sources",
@@ -287,9 +261,11 @@
filegroup {
name: "framework-updatable-sources",
srcs: [
+ ":framework-appsearch-sources",
":framework-sdkext-sources",
":framework-statsd-sources",
":updatable-media-srcs",
+ ":framework-wifi-updatable-sources",
]
}
@@ -425,7 +401,7 @@
filegroup {
name: "framework-jarjar-rules",
- srcs: ["jarjar_rules_hidl.txt"],
+ srcs: ["framework-jarjar-rules.txt"],
}
filegroup {
@@ -451,8 +427,10 @@
defaults: ["framework-defaults"],
srcs: [":framework-non-updatable-sources"],
libs: [
+ "framework-appsearch-stubs",
// TODO(b/146167933): Use framework-statsd-stubs
"framework-statsd",
+ "framework-wifi-stubs",
],
installable: true,
javac_shard_size: 150,
@@ -489,9 +467,12 @@
installable: false, // this lib is a build-only library
static_libs: [
"framework-minus-apex",
+ "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
"framework-sdkext-stubs-systemapi",
// TODO(b/146167933): Use framework-statsd-stubs instead.
"framework-statsd",
+ // TODO(b/140299412): should be framework-wifi-stubs
+ "framework-wifi",
// TODO(jiyong): add more stubs for APEXes here
],
sdk_version: "core_platform",
diff --git a/apex/Android.bp b/apex/Android.bp
index 9ea3953..56f7db2 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -26,6 +26,9 @@
"--hide Typo " +
"--hide UnavailableSymbol "
+// TODO: remove this server classes are cleaned up.
+mainline_stubs_args += "--hide-package com.android.server "
+
stubs_defaults {
name: "framework-module-stubs-defaults-publicapi",
args: mainline_stubs_args,
@@ -34,6 +37,6 @@
stubs_defaults {
name: "framework-module-stubs-defaults-systemapi",
- args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi ",
+ args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ",
installable: false,
}
diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp
index bcdcc7d..b014fdc 100644
--- a/apex/appsearch/Android.bp
+++ b/apex/appsearch/Android.bp
@@ -14,9 +14,11 @@
apex {
name: "com.android.appsearch",
-
manifest: "apex_manifest.json",
-
+ java_libs: [
+ "framework-appsearch",
+ "service-appsearch",
+ ],
key: "com.android.appsearch.key",
certificate: ":com.android.appsearch.certificate",
}
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 0a65f73..3dc5a2c 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -23,17 +23,52 @@
java_library {
name: "framework-appsearch",
- installable: false,
- sdk_version: "core_platform",
- srcs: [
- ":framework-appsearch-sources",
- ],
- aidl: {
- export_include_dirs: [
- "java",
- ],
- },
+ installable: true,
+ sdk_version: "core_platform", // TODO(b/146218515) should be core_current
+ srcs: [":framework-appsearch-sources"],
libs: [
- "framework-minus-apex",
+ "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
],
}
+
+metalava_appsearch_docs_args =
+ "--hide-package com.android.server " +
+ "--error UnhiddenSystemApi " +
+ "--hide RequiresPermission " +
+ "--hide MissingPermission " +
+ "--hide BroadcastBehavior " +
+ "--hide HiddenSuperclass " +
+ "--hide DeprecationMismatch " +
+ "--hide UnavailableSymbol " +
+ "--hide SdkConstant " +
+ "--hide HiddenTypeParameter " +
+ "--hide Todo --hide Typo " +
+ "--hide HiddenTypedefConstant " +
+ "--show-annotation android.annotation.SystemApi "
+
+droidstubs {
+ name: "framework-appsearch-stubs-srcs",
+ srcs: [
+ ":framework-annotations",
+ ":framework-appsearch-sources",
+ ],
+ aidl: {
+ include_dirs: ["frameworks/base/core/java"],
+ },
+ args: metalava_appsearch_docs_args,
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+}
+
+java_library {
+ name: "framework-appsearch-stubs",
+ srcs: [":framework-appsearch-stubs-srcs"],
+ aidl: {
+ export_include_dirs: [
+ "java",
+ ],
+ },
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+ installable: false,
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
index fcebe3d..02cc967 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
@@ -15,19 +15,25 @@
*/
package android.app.appsearch;
+import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
import android.content.Context;
/**
- * This is where the AppSearchManagerService wrapper is registered.
+ * Class holding initialization code for the AppSearch module.
*
- * TODO(b/142567528): add comments when implement this class
* @hide
*/
+@SystemApi
public class AppSearchManagerFrameworkInitializer {
+ private AppSearchManagerFrameworkInitializer() {}
/**
- * TODO(b/142567528): add comments when implement this class
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers all AppSearch
+ * services to {@link Context}, so that {@link Context#getSystemService} can return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides
+ * {@link SystemServiceRegistry}
*/
public static void initialize() {
SystemServiceRegistry.registerStaticService(
diff --git a/apex/sdkext/Android.bp b/apex/sdkext/Android.bp
index b9071f8..5369a96 100644
--- a/apex/sdkext/Android.bp
+++ b/apex/sdkext/Android.bp
@@ -23,6 +23,7 @@
java_libs: [ "framework-sdkext" ],
prebuilts: [
"com.android.sdkext.ldconfig",
+ "cur_sdkinfo",
"derive_sdk.rc",
],
key: "com.android.sdkext.key",
@@ -51,3 +52,33 @@
filename: "ld.config.txt",
installable: false,
}
+
+python_binary_host {
+ name: "gen_sdkinfo",
+ srcs: [
+ "sdk.proto",
+ "gen_sdkinfo.py",
+ ],
+ proto: {
+ canonical_path_from_root: false,
+ },
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
+}
+
+gensrcs {
+ name: "cur_sdkinfo_src",
+ srcs: [""],
+ tools: [ "gen_sdkinfo" ],
+ cmd: "$(location) -v 0 -o $(out)",
+}
+
+prebuilt_etc {
+ name: "cur_sdkinfo",
+ src: ":cur_sdkinfo_src",
+ filename: "sdkinfo.binarypb",
+ installable: false,
+}
diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
index d3b9397..a8a7eff 100644
--- a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
+++ b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
@@ -60,7 +60,7 @@
*/
public static int getExtensionVersion(@SdkVersion int sdk) {
if (sdk < VERSION_CODES.R) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException(String.valueOf(sdk) + " does not have extensions");
}
return R_EXTENSION_INT;
}
diff --git a/apex/sdkext/gen_sdkinfo.py b/apex/sdkext/gen_sdkinfo.py
new file mode 100644
index 0000000..5af478b
--- /dev/null
+++ b/apex/sdkext/gen_sdkinfo.py
@@ -0,0 +1,19 @@
+import sdk_pb2
+import sys
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+ if not len(argv) == 4 or sorted([argv[0], argv[2]]) != ['-o', '-v']:
+ print('usage: gen_sdkinfo -v <version> -o <output-file>')
+ sys.exit(1)
+
+ for i in range(len(argv)):
+ if sys.argv[i] == '-o':
+ filename = sys.argv[i+1]
+ if sys.argv[i] == '-v':
+ version = int(sys.argv[i+1])
+
+ proto = sdk_pb2.SdkVersion()
+ proto.version = version
+ with open(filename, 'wb') as f:
+ f.write(proto.SerializeToString())
diff --git a/apex/sdkext/sdk.proto b/apex/sdkext/sdk.proto
new file mode 100644
index 0000000..d15b935
--- /dev/null
+++ b/apex/sdkext/sdk.proto
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+package com.android.sdkext.proto;
+
+option java_outer_classname = "SdkProto";
+option optimize_for = LITE_RUNTIME;
+
+message SdkVersion {
+ int32 version = 1;
+}
diff --git a/api/current.txt b/api/current.txt
index d15069a..6ddd847 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -17246,6 +17246,7 @@
field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1
field public static final int REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME = 12; // 0xc
field public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; // 0xa
+ field public static final int REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING = 15; // 0xf
field public static final int REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING = 4; // 0x4
field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3
field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5
@@ -35028,6 +35029,7 @@
method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException;
+ method public static int getSuggestedMaxIpcSizeBytes();
method public boolean isBinderAlive();
method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException;
method public boolean pingBinder();
@@ -35224,6 +35226,7 @@
method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+ method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
method @Nullable public android.os.PersistableBundle readPersistableBundle();
method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
@@ -35271,6 +35274,7 @@
method public void writeNoException();
method public void writeParcelable(@Nullable android.os.Parcelable, int);
method public <T extends android.os.Parcelable> void writeParcelableArray(@Nullable T[], int);
+ method public void writeParcelableCreator(@NonNull android.os.Parcelable);
method public <T extends android.os.Parcelable> void writeParcelableList(@Nullable java.util.List<T>, int);
method public void writePersistableBundle(@Nullable android.os.PersistableBundle);
method public void writeSerializable(@Nullable java.io.Serializable);
@@ -38398,11 +38402,11 @@
protected static interface ContactsContract.RawContactsColumns {
field public static final String ACCOUNT_TYPE_AND_DATA_SET = "account_type_and_data_set";
field public static final String AGGREGATION_MODE = "aggregation_mode";
- field public static final String BACKUP_ID = "backup_id";
+ field @Deprecated public static final String BACKUP_ID = "backup_id";
field public static final String CONTACT_ID = "contact_id";
field public static final String DATA_SET = "data_set";
field public static final String DELETED = "deleted";
- field public static final String METADATA_DIRTY = "metadata_dirty";
+ field @Deprecated public static final String METADATA_DIRTY = "metadata_dirty";
field public static final String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
field public static final String RAW_CONTACT_IS_USER_PROFILE = "raw_contact_is_user_profile";
}
@@ -38709,6 +38713,10 @@
public final class MediaStore {
ctor public MediaStore();
+ method @NonNull public static android.app.PendingIntent createDeleteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
+ method @NonNull public static android.app.PendingIntent createFavoriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+ method @NonNull public static android.app.PendingIntent createTrashRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+ method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
method public static android.net.Uri getMediaScannerUri();
@@ -38719,9 +38727,6 @@
method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
method @Deprecated @NonNull public static android.net.Uri setIncludePending(@NonNull android.net.Uri);
method @NonNull public static android.net.Uri setRequireOriginal(@NonNull android.net.Uri);
- method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
- method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
- method public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
field public static final String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
field public static final String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
field public static final String ACTION_REVIEW = "android.provider.action.REVIEW";
@@ -43891,6 +43896,7 @@
field public static final String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
field public static final String EVENT_CALL_REMOTELY_HELD = "android.telecom.event.CALL_REMOTELY_HELD";
field public static final String EVENT_CALL_REMOTELY_UNHELD = "android.telecom.event.CALL_REMOTELY_UNHELD";
+ field public static final String EVENT_CALL_SWITCH_FAILED = "android.telecom.event.CALL_SWITCH_FAILED";
field public static final String EVENT_MERGE_COMPLETE = "android.telecom.event.MERGE_COMPLETE";
field public static final String EVENT_MERGE_START = "android.telecom.event.MERGE_START";
field public static final String EVENT_ON_HOLD_TONE_END = "android.telecom.event.ON_HOLD_TONE_END";
@@ -44600,6 +44606,7 @@
method public void notifyConfigChangedForSubId(int);
field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+ field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
@@ -44610,6 +44617,7 @@
field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
field public static final String KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL = "allow_emergency_video_calls_bool";
+ field public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL = "allow_holding_video_call";
field public static final String KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL = "allow_hold_call_during_emergency_bool";
field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
@@ -44659,11 +44667,16 @@
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
- field public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING = "config_ims_mmtel_package_override_string";
+ field @Deprecated public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING = "config_ims_rcs_package_override_string";
field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
+ field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+ field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool";
+ field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool";
field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING = "default_vm_number_roaming_and_ims_unregistered_string";
@@ -45223,6 +45236,7 @@
method public void onDataConnectionStateChanged(int);
method public void onDataConnectionStateChanged(int, int);
method public void onMessageWaitingIndicatorChanged(boolean);
+ method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
method public void onServiceStateChanged(android.telephony.ServiceState);
method @Deprecated public void onSignalStrengthChanged(int);
method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
@@ -45237,12 +45251,23 @@
field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
field public static final int LISTEN_NONE = 0; // 0x0
+ field @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
}
+ public final class PreciseDataConnectionState implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getLastCauseCode();
+ method @Nullable public android.net.LinkProperties getLinkProperties();
+ method public int getNetworkType();
+ method public int getState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+ }
+
public final class RadioAccessSpecifier implements android.os.Parcelable {
ctor public RadioAccessSpecifier(int, int[], int[]);
method public int describeContents();
@@ -45755,6 +45780,7 @@
field public static final int DATA_CONNECTED = 2; // 0x2
field public static final int DATA_CONNECTING = 1; // 0x1
field public static final int DATA_DISCONNECTED = 0; // 0x0
+ field public static final int DATA_DISCONNECTING = 4; // 0x4
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final int DATA_UNKNOWN = -1; // 0xffffffff
field public static final String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
@@ -51081,6 +51107,9 @@
method public boolean dispatchUnhandledMove(android.view.View, int);
method protected void dispatchVisibilityChanged(@NonNull android.view.View, int);
method public void dispatchWindowFocusChanged(boolean);
+ method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+ method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
+ method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
method public void dispatchWindowSystemUiVisiblityChanged(int);
method public void dispatchWindowVisibilityChanged(int);
method @CallSuper public void draw(android.graphics.Canvas);
@@ -51261,6 +51290,7 @@
method @android.view.ViewDebug.ExportedProperty(category="layout") public final int getWidth();
method protected int getWindowAttachCount();
method public android.view.WindowId getWindowId();
+ method @Nullable public android.view.WindowInsetsController getWindowInsetsController();
method public int getWindowSystemUiVisibility();
method public android.os.IBinder getWindowToken();
method public int getWindowVisibility();
@@ -51599,6 +51629,7 @@
method public void setVisibility(int);
method @Deprecated public void setWillNotCacheDrawing(boolean);
method public void setWillNotDraw(boolean);
+ method public void setWindowInsetsAnimationCallback(@Nullable android.view.WindowInsetsAnimationCallback);
method public void setX(float);
method public void setY(float);
method public void setZ(float);
@@ -52485,6 +52516,7 @@
method public android.transition.Transition getExitTransition();
method protected final int getFeatures();
method protected final int getForcedWindowFlags();
+ method @Nullable public android.view.WindowInsetsController getInsetsController();
method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
method public android.media.session.MediaController getMediaController();
@@ -52700,7 +52732,9 @@
method @NonNull public android.view.WindowInsets consumeStableInsets();
method @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
method @Nullable public android.view.DisplayCutout getDisplayCutout();
+ method @NonNull public android.graphics.Insets getInsets(int);
method @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
+ method @NonNull public android.graphics.Insets getMaxInsets(int) throws java.lang.IllegalArgumentException;
method public int getStableInsetBottom();
method public int getStableInsetLeft();
method public int getStableInsetRight();
@@ -52719,6 +52753,7 @@
method @NonNull public android.view.WindowInsets inset(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int);
method public boolean isConsumed();
method public boolean isRound();
+ method public boolean isVisible(int);
method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(int, int, int, int);
method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(android.graphics.Rect);
}
@@ -52728,11 +52763,76 @@
ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
method @NonNull public android.view.WindowInsets build();
method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
+ method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
+ method @NonNull public android.view.WindowInsets.Builder setMaxInsets(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
method @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setTappableElementInsets(@NonNull android.graphics.Insets);
+ method @NonNull public android.view.WindowInsets.Builder setVisible(int, boolean);
+ }
+
+ public static final class WindowInsets.Type {
+ method public static int all();
+ method public static int captionBar();
+ method public static int ime();
+ method public static int mandatorySystemGestures();
+ method public static int navigationBars();
+ method public static int statusBars();
+ method public static int systemBars();
+ method public static int systemGestures();
+ method public static int tappableElement();
+ method public static int windowDecor();
+ }
+
+ public interface WindowInsetsAnimationCallback {
+ method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+ method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets);
+ method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+ }
+
+ public static final class WindowInsetsAnimationCallback.AnimationBounds {
+ ctor public WindowInsetsAnimationCallback.AnimationBounds(@NonNull android.graphics.Insets, @NonNull android.graphics.Insets);
+ method @NonNull public android.graphics.Insets getLowerBound();
+ method @NonNull public android.graphics.Insets getUpperBound();
+ method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds inset(@NonNull android.graphics.Insets);
+ }
+
+ public static final class WindowInsetsAnimationCallback.InsetsAnimation {
+ ctor public WindowInsetsAnimationCallback.InsetsAnimation(int, @Nullable android.view.animation.Interpolator, long);
+ method public long getDurationMillis();
+ method @FloatRange(from=0.0f, to=1.0f) public float getFraction();
+ method public float getInterpolatedFraction();
+ method @Nullable public android.view.animation.Interpolator getInterpolator();
+ method public int getTypeMask();
+ method public void setDuration(long);
+ method public void setFraction(@FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public interface WindowInsetsAnimationControlListener {
+ method public void onCancelled();
+ method public void onReady(@NonNull android.view.WindowInsetsAnimationController, int);
+ }
+
+ public interface WindowInsetsAnimationController {
+ method public void finish(boolean);
+ method @FloatRange(from=0.0f, to=1.0f) public float getCurrentFraction();
+ method @NonNull public android.graphics.Insets getCurrentInsets();
+ method @NonNull public android.graphics.Insets getHiddenStateInsets();
+ method @NonNull public android.graphics.Insets getShownStateInsets();
+ method public int getTypes();
+ method public void setInsetsAndAlpha(@Nullable android.graphics.Insets, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public interface WindowInsetsController {
+ method public default void controlInputMethodAnimation(long, @NonNull android.view.WindowInsetsAnimationControlListener);
+ method public default void hideInputMethod();
+ method public void setSystemBarsAppearance(int);
+ method public void setSystemBarsBehavior(int);
+ method public default void showInputMethod();
+ field public static final int APPEARANCE_LIGHT_NAVIGATION_BARS = 16; // 0x10
+ field public static final int APPEARANCE_LIGHT_STATUS_BARS = 8; // 0x8
}
public interface WindowManager extends android.view.ViewManager {
diff --git a/api/removed.txt b/api/removed.txt
index 1a2f434..4e8e325 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -441,6 +441,9 @@
method @Deprecated public static boolean getIncludePending(@NonNull android.net.Uri);
method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri);
method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri);
+ method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
+ method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
+ method @Deprecated public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
}
public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
diff --git a/api/system-current.txt b/api/system-current.txt
index e913665..8d55a33 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -546,10 +546,12 @@
method public final android.os.IBinder onBind(android.content.Intent);
method @Deprecated public void onGetInstantAppIntentFilter(@Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
method @Deprecated public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
- method public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method @Deprecated public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method public void onGetInstantAppIntentFilter(@NonNull android.content.pm.InstantAppRequestInfo, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
method @Deprecated public void onGetInstantAppResolveInfo(@Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
method @Deprecated public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
- method public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method @Deprecated public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method public void onGetInstantAppResolveInfo(@NonNull android.content.pm.InstantAppRequestInfo, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
}
public static final class InstantAppResolverService.InstantAppResolutionCallback {
@@ -809,6 +811,14 @@
}
+package android.app.appsearch {
+
+ public class AppSearchManagerFrameworkInitializer {
+ method public static void initialize();
+ }
+
+}
+
package android.app.assist {
public static class AssistStructure.ViewNode {
@@ -1350,6 +1360,14 @@
field public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; // 0xffffffff
}
+ public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile {
+ method public void finalize();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothAdapter {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice);
@@ -1497,6 +1515,11 @@
field public static final int REMOTE_PANU_ROLE = 2; // 0x2
}
+ public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
+ method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public interface BluetoothProfile {
field public static final int CONNECTION_POLICY_ALLOWED = 100; // 0x64
field public static final int CONNECTION_POLICY_FORBIDDEN = 0; // 0x0
@@ -1628,6 +1651,7 @@
field public static final String STATS_MANAGER = "stats";
field public static final String STATUS_BAR_SERVICE = "statusbar";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
+ field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
field public static final String VR_SERVICE = "vrmanager";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
@@ -1914,6 +1938,18 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstantAppIntentFilter> CREATOR;
}
+ public final class InstantAppRequestInfo implements android.os.Parcelable {
+ ctor public InstantAppRequestInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, boolean, @NonNull String);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstantAppRequestInfo> CREATOR;
+ field @Nullable public final int[] hostDigestPrefix;
+ field @NonNull public final android.content.Intent intent;
+ field public final boolean isRequesterInstantApp;
+ field @NonNull public final String token;
+ field @NonNull public final android.os.UserHandle userHandle;
+ }
+
public final class InstantAppResolveInfo implements android.os.Parcelable {
ctor public InstantAppResolveInfo(@NonNull android.content.pm.InstantAppResolveInfo.InstantAppDigest, @Nullable String, @Nullable java.util.List<android.content.pm.InstantAppIntentFilter>, int);
ctor public InstantAppResolveInfo(@NonNull android.content.pm.InstantAppResolveInfo.InstantAppDigest, @Nullable String, @Nullable java.util.List<android.content.pm.InstantAppIntentFilter>, long, @Nullable android.os.Bundle);
@@ -4167,18 +4203,20 @@
package android.media.session {
public final class MediaSessionManager {
- method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.Callback);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventDispatchedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventDispatchedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
- method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void unregisterCallback(@NonNull android.media.session.MediaSessionManager.Callback);
}
- public abstract static class MediaSessionManager.Callback {
- ctor public MediaSessionManager.Callback();
- method public abstract void onAddressedPlayerChanged(android.media.session.MediaSession.Token);
- method public abstract void onAddressedPlayerChanged(android.content.ComponentName);
- method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token);
- method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName);
+ public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener {
+ method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token);
+ }
+
+ public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+ method public default void onMediaKeyEventSessionChanged(@NonNull String, @Nullable android.media.session.MediaSession.Token);
}
public static interface MediaSessionManager.OnMediaKeyListener {
@@ -4532,6 +4570,14 @@
method public void onUpstreamChanged(@Nullable android.net.Network);
}
+ public class InvalidPacketException extends java.lang.Exception {
+ ctor public InvalidPacketException(int);
+ field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+ field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+ field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+ field public final int error;
+ }
+
public final class IpConfiguration implements android.os.Parcelable {
ctor public IpConfiguration();
ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
@@ -4582,6 +4628,15 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
}
+ public class KeepalivePacketData {
+ ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+ method @NonNull public byte[] getPacket();
+ field @NonNull public final java.net.InetAddress dstAddress;
+ field public final int dstPort;
+ field @NonNull public final java.net.InetAddress srcAddress;
+ field public final int srcPort;
+ }
+
public class LinkAddress implements android.os.Parcelable {
ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int);
ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
@@ -4861,7 +4916,7 @@
method @NonNull public java.util.List<android.net.LinkAddress> getInternalAddresses();
method @NonNull public java.util.List<java.net.InetAddress> getInternalDhcpServers();
method @NonNull public java.util.List<java.net.InetAddress> getInternalDnsServers();
- method @NonNull public java.util.List<android.net.LinkAddress> getInternalSubnets();
+ method @NonNull public java.util.List<android.net.IpPrefix> getInternalSubnets();
method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getOutboundTrafficSelectors();
}
@@ -4930,6 +4985,7 @@
public final class IkeSessionConfiguration {
ctor public IkeSessionConfiguration();
method @NonNull public String getRemoteApplicationVersion();
+ method @NonNull public java.util.List<byte[]> getRemoteVendorIDs();
method public boolean isIkeExtensionEnabled(int);
field public static final int EXTENSION_TYPE_FRAGMENTATION = 1; // 0x1
field public static final int EXTENSION_TYPE_MOBIKE = 2; // 0x2
@@ -4949,9 +5005,9 @@
ctor public IkeSessionParams.Builder();
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.IkeSaProposal);
method @NonNull public android.net.ipsec.ike.IkeSessionParams build();
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@NonNull java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
+ method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey);
+ method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
+ method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@Nullable java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthPsk(@NonNull byte[]);
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLocalIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setRemoteIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
@@ -4969,7 +5025,7 @@
}
public static class IkeSessionParams.IkeAuthDigitalSignRemoteConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
- method @NonNull public java.security.cert.X509Certificate getRemoteCaCert();
+ method @Nullable public java.security.cert.X509Certificate getRemoteCaCert();
}
public static class IkeSessionParams.IkeAuthEapConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
@@ -5033,12 +5089,10 @@
ctor public TunnelModeChildSessionParams.Builder();
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.InetAddress, int);
+ method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet4Address);
+ method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet6Address, int);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDhcpServerRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDhcpServerRequest(@NonNull java.net.InetAddress);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDnsServerRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDnsServerRequest(@NonNull java.net.InetAddress);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalSubnetRequest(int);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams build();
@@ -5062,9 +5116,6 @@
public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Netmask extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
}
- public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Subnet extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
- }
-
public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
method @Nullable public java.net.Inet6Address getAddress();
method public int getPrefixLength();
@@ -5074,9 +5125,6 @@
method @Nullable public java.net.Inet6Address getAddress();
}
- public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Subnet extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
- }
-
}
package android.net.ipsec.ike.exceptions {
@@ -7087,34 +7135,34 @@
field public static final int STATUS_NOT_BLOCKED = 0; // 0x0
}
- public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
- field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
- field public static final android.net.Uri CONTENT_URI;
- field public static final String METADATA_AUTHORITY = "com.android.contacts.metadata";
- field public static final android.net.Uri METADATA_AUTHORITY_URI;
+ @Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
+ field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
+ field @Deprecated public static final android.net.Uri CONTENT_URI;
+ field @Deprecated public static final String METADATA_AUTHORITY = "com.android.contacts.metadata";
+ field @Deprecated public static final android.net.Uri METADATA_AUTHORITY_URI;
}
- protected static interface ContactsContract.MetadataSyncColumns {
- field public static final String ACCOUNT_NAME = "account_name";
- field public static final String ACCOUNT_TYPE = "account_type";
- field public static final String DATA = "data";
- field public static final String DATA_SET = "data_set";
- field public static final String DELETED = "deleted";
- field public static final String RAW_CONTACT_BACKUP_ID = "raw_contact_backup_id";
+ @Deprecated protected static interface ContactsContract.MetadataSyncColumns {
+ field @Deprecated public static final String ACCOUNT_NAME = "account_name";
+ field @Deprecated public static final String ACCOUNT_TYPE = "account_type";
+ field @Deprecated public static final String DATA = "data";
+ field @Deprecated public static final String DATA_SET = "data_set";
+ field @Deprecated public static final String DELETED = "deleted";
+ field @Deprecated public static final String RAW_CONTACT_BACKUP_ID = "raw_contact_backup_id";
}
- public static final class ContactsContract.MetadataSyncState implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncStateColumns {
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata_sync_state";
- field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata_sync_state";
- field public static final android.net.Uri CONTENT_URI;
+ @Deprecated public static final class ContactsContract.MetadataSyncState implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncStateColumns {
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata_sync_state";
+ field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata_sync_state";
+ field @Deprecated public static final android.net.Uri CONTENT_URI;
}
- protected static interface ContactsContract.MetadataSyncStateColumns {
- field public static final String ACCOUNT_NAME = "account_name";
- field public static final String ACCOUNT_TYPE = "account_type";
- field public static final String DATA_SET = "data_set";
- field public static final String STATE = "state";
+ @Deprecated protected static interface ContactsContract.MetadataSyncStateColumns {
+ field @Deprecated public static final String ACCOUNT_NAME = "account_name";
+ field @Deprecated public static final String ACCOUNT_TYPE = "account_type";
+ field @Deprecated public static final String DATA_SET = "data_set";
+ field @Deprecated public static final String STATE = "state";
}
public final class DeviceConfig {
@@ -7322,6 +7370,7 @@
public final class Settings {
field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
+ field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION";
@@ -7998,6 +8047,7 @@
field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
field public static final String KEY_IMPORTANCE = "key_importance";
field public static final String KEY_PEOPLE = "key_people";
+ field public static final String KEY_RANKING_SCORE = "key_ranking_score";
field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final String KEY_TEXT_REPLIES = "key_text_replies";
field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -9280,7 +9330,6 @@
method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
- method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
method public void onRadioPowerStateChanged(int);
method public void onSrvccStateChanged(int);
method public void onVoiceActivationStateChanged(int);
@@ -9290,7 +9339,6 @@
field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
- field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
@@ -9316,13 +9364,12 @@
}
public final class PreciseDataConnectionState implements android.os.Parcelable {
- method public int describeContents();
- method @Nullable public String getDataConnectionApn();
- method public int getDataConnectionApnTypeBitMask();
- method public int getDataConnectionFailCause();
- method public int getDataConnectionState();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+ method @Deprecated @NonNull public String getDataConnectionApn();
+ method @Deprecated public int getDataConnectionApnTypeBitMask();
+ method @Deprecated public int getDataConnectionFailCause();
+ method @Deprecated @Nullable public android.net.LinkProperties getDataConnectionLinkProperties();
+ method @Deprecated public int getDataConnectionNetworkType();
+ method @Deprecated public int getDataConnectionState();
}
public final class PreciseDisconnectCause {
@@ -9631,9 +9678,6 @@
public class TelephonyManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionReportDefaultNetworkStatus(int, boolean);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionResetAll(int);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionSetRadioEnabled(int, boolean);
method public int checkCarrierPrivilegesForPackage(String);
method public int checkCarrierPrivilegesForPackageAnyPhone(String);
method public void dial(String);
@@ -9707,9 +9751,11 @@
method public boolean needsOtaServiceProvisioning();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method public void requestModemActivityInfo(@NonNull android.os.ResultReceiver);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig();
method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings();
@@ -9723,6 +9769,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
@@ -10333,6 +10380,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
}
+ public class ImsManager {
+ method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
+ method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+ }
+
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -10376,6 +10428,22 @@
ctor @Deprecated public ImsMmTelManager.RegistrationCallback();
}
+ public class ImsRcsManager implements android.telephony.ims.RegistrationManager {
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAvailable(int) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCapable(int, int) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerRcsAvailabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterRcsAvailabilityCallback(@NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+ }
+
+ public static class ImsRcsManager.AvailabilityCallback {
+ ctor public ImsRcsManager.AvailabilityCallback();
+ method public void onAvailabilityChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
+ }
+
public final class ImsReasonInfo implements android.os.Parcelable {
field public static final String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service";
}
@@ -10737,9 +10805,22 @@
public class RcsFeature extends android.telephony.ims.feature.ImsFeature {
ctor public RcsFeature();
- method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+ method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+ method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
method public void onFeatureReady();
method public void onFeatureRemoved();
+ method public boolean queryCapabilityConfiguration(int, int);
+ method @NonNull public final android.telephony.ims.feature.RcsFeature.RcsImsCapabilities queryCapabilityStatus();
+ }
+
+ public static class RcsFeature.RcsImsCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+ ctor public RcsFeature.RcsImsCapabilities(int);
+ method public void addCapabilities(int);
+ method public boolean isCapable(int);
+ method public void removeCapabilities(int);
+ field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0
+ field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1
+ field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
}
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 4942639..1c6bce0 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -711,6 +711,7 @@
field public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
field public static final String ROLLBACK_SERVICE = "rollback";
field public static final String STATUS_BAR_SERVICE = "statusbar";
+ field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
field public static final String TEST_NETWORK_SERVICE = "test_network";
}
@@ -2831,6 +2832,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
field public static final String KEY_IMPORTANCE = "key_importance";
+ field public static final String KEY_RANKING_SCORE = "key_ranking_score";
field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final String KEY_TEXT_REPLIES = "key_text_replies";
field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -3161,6 +3163,10 @@
field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
}
+ public final class PreciseDataConnectionState implements android.os.Parcelable {
+ ctor @Deprecated public PreciseDataConnectionState(int, int, int, @NonNull String, @Nullable android.net.LinkProperties, int);
+ }
+
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
method public void setCdmaSystemAndNetworkId(int, int);
@@ -3427,6 +3433,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
}
+ public class ImsManager {
+ method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
+ method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+ }
+
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -3470,6 +3481,22 @@
ctor @Deprecated public ImsMmTelManager.RegistrationCallback();
}
+ public class ImsRcsManager implements android.telephony.ims.RegistrationManager {
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isAvailable(int) throws android.telephony.ims.ImsException;
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isCapable(int, int) throws android.telephony.ims.ImsException;
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerRcsAvailabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterRcsAvailabilityCallback(@NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+ }
+
+ public static class ImsRcsManager.AvailabilityCallback {
+ ctor public ImsRcsManager.AvailabilityCallback();
+ method public void onAvailabilityChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
+ }
+
public class ImsService extends android.app.Service {
ctor public ImsService();
method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int);
@@ -3827,9 +3854,22 @@
public class RcsFeature extends android.telephony.ims.feature.ImsFeature {
ctor public RcsFeature();
- method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+ method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+ method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
method public void onFeatureReady();
method public void onFeatureRemoved();
+ method public boolean queryCapabilityConfiguration(int, int);
+ method @NonNull public final android.telephony.ims.feature.RcsFeature.RcsImsCapabilities queryCapabilityStatus();
+ }
+
+ public static class RcsFeature.RcsImsCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+ ctor public RcsFeature.RcsImsCapabilities(int);
+ method public void addCapabilities(int);
+ method public boolean isCapable(int);
+ method public void removeCapabilities(int);
+ field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0
+ field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1
+ field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
}
}
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index 6c3d197..eb2b98a 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -375,7 +375,7 @@
if (destination == DEST_STDOUT) {
// Call into the service
sp<StatusListener> listener(new StatusListener());
- status = service->reportIncidentToStream(args, listener, writeEnd);
+ status = service->reportIncidentToStream(args, listener, std::move(writeEnd));
if (!status.isOk()) {
fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
@@ -388,7 +388,7 @@
} else if (destination == DEST_DUMPSTATE) {
// Call into the service
sp<StatusListener> listener(new StatusListener());
- status = service->reportIncidentToDumpstate(writeEnd, listener);
+ status = service->reportIncidentToDumpstate(std::move(writeEnd), listener);
if (!status.isOk()) {
fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
return 1;
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 999936b..cfd77c2 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -279,7 +279,7 @@
Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener,
- const unique_fd& stream) {
+ unique_fd stream) {
IncidentReportArgs argsCopy(args);
// Streaming reports can not also be broadcast.
@@ -306,7 +306,7 @@
return Status::ok();
}
-Status IncidentService::reportIncidentToDumpstate(const unique_fd& stream,
+Status IncidentService::reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener) {
uid_t caller = IPCThreadState::self()->getCallingUid();
if (caller != AID_ROOT && caller != AID_SHELL) {
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index fb013d0..b2c7f23 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -121,9 +121,9 @@
virtual Status reportIncidentToStream(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener,
- const unique_fd& stream);
+ unique_fd stream);
- virtual Status reportIncidentToDumpstate(const unique_fd& stream,
+ virtual Status reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener);
virtual Status systemRunning();
diff --git a/cmds/svc/src/com/android/commands/svc/DataCommand.java b/cmds/svc/src/com/android/commands/svc/DataCommand.java
index 35510cf..b4dbd1d 100644
--- a/cmds/svc/src/com/android/commands/svc/DataCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/DataCommand.java
@@ -16,12 +16,16 @@
package com.android.commands.svc;
-import android.os.ServiceManager;
-import android.os.RemoteException;
-import android.content.Context;
-import com.android.internal.telephony.ITelephony;
-
+/**
+ * @deprecated Please use adb shell cmd phone data enabled/disable instead.
+ */
+@Deprecated
public class DataCommand extends Svc.Command {
+
+ private static final String DECPRECATED_MESSAGE =
+ "adb shell svc data enable/disable is deprecated;"
+ + "please use adb shell cmd phone data enable/disable instead.";
+
public DataCommand() {
super("data");
}
@@ -33,36 +37,10 @@
public String longHelp() {
return shortHelp() + "\n"
+ "\n"
- + "usage: svc data [enable|disable]\n"
- + " Turn mobile data on or off.\n\n";
+ + DECPRECATED_MESSAGE;
}
public void run(String[] args) {
- boolean validCommand = false;
- if (args.length >= 2) {
- boolean flag = false;
- if ("enable".equals(args[1])) {
- flag = true;
- validCommand = true;
- } else if ("disable".equals(args[1])) {
- flag = false;
- validCommand = true;
- }
- if (validCommand) {
- ITelephony phoneMgr
- = ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
- try {
- if (flag) {
- phoneMgr.enableDataConnectivity();
- } else
- phoneMgr.disableDataConnectivity();
- }
- catch (RemoteException e) {
- System.err.println("Mobile data operation failed: " + e);
- }
- return;
- }
- }
- System.err.println(longHelp());
+ System.err.println(DECPRECATED_MESSAGE);
}
}
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 8b62e2f..d82b151 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -17,6 +17,8 @@
package android.accessibilityservice;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
@@ -58,6 +60,8 @@
/** @hide */
@IntDef(prefix = { "GESTURE_" }, value = {
+ GESTURE_DOUBLE_TAP,
+ GESTURE_DOUBLE_TAP_AND_HOLD,
GESTURE_SWIPE_UP,
GESTURE_SWIPE_UP_AND_LEFT,
GESTURE_SWIPE_UP_AND_DOWN,
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 47fdcde..0f619c8 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -300,6 +300,18 @@
public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
/**
+ * The user has performed a double tap gesture on the touch screen.
+ * @hide
+ */
+ public static final int GESTURE_DOUBLE_TAP = 17;
+
+ /**
+ * The user has performed a double tap and hold gesture on the touch screen.
+ * @hide
+ */
+ public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18;
+
+ /**
* The {@link Intent} that must be declared as handled by the service.
*/
public static final String SERVICE_INTERFACE =
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index f4e465a..0f10c39 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1943,35 +1943,6 @@
}
/**
- * @hide
- * Removes the shared account.
- * @param account the account to remove
- * @param user the user to remove the account from
- * @return
- */
- public boolean removeSharedAccount(final Account account, UserHandle user) {
- try {
- boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
- return val;
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
- * @param user
- * @return
- */
- public Account[] getSharedAccounts(UserHandle user) {
- try {
- return mService.getSharedAccountsAsUser(user.getIdentifier());
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* Confirms that the user knows the password for an account to make extra
* sure they are the owner of the account. The user-entered password can
* be supplied directly, otherwise the authenticator for this account type
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 4cf0a20..0127138 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -80,14 +80,11 @@
String authTokenType);
/* Shared accounts */
- Account[] getSharedAccountsAsUser(int userId);
- boolean removeSharedAccountAsUser(in Account account, int userId);
void addSharedAccountsFromParentUser(int parentUserId, int userId, String opPackageName);
/* Account renaming. */
void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName);
String getPreviousName(in Account account);
- boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
/* Add account in two steps. */
void startAddAccountSession(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 577272e..1e3b950 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -766,9 +766,19 @@
private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";
private static final String AUTO_FILL_AUTH_WHO_PREFIX = "@android:autoFillAuth:";
-
private static final String KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME = "com.android.systemui";
+ private static final int LOG_AM_ON_CREATE_CALLED = 30057;
+ private static final int LOG_AM_ON_START_CALLED = 30059;
+ private static final int LOG_AM_ON_RESUME_CALLED = 30022;
+ private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
+ private static final int LOG_AM_ON_STOP_CALLED = 30049;
+ private static final int LOG_AM_ON_RESTART_CALLED = 30058;
+ private static final int LOG_AM_ON_DESTROY_CALLED = 30060;
+ private static final int LOG_AM_ON_ACTIVITY_RESULT_CALLED = 30062;
+ private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
+ private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
+
private static class ManagedDialog {
Dialog mDialog;
Bundle mArgs;
@@ -2439,8 +2449,11 @@
* {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
*/
public final void requestShowKeyboardShortcuts() {
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
- intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME);
+ intent.setPackage(sysuiComponent.getPackageName());
sendBroadcastAsUser(intent, Process.myUserHandle());
}
@@ -2448,8 +2461,11 @@
* Dismiss the Keyboard Shortcuts screen.
*/
public final void dismissKeyboardShortcutsHelper() {
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
Intent intent = new Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS);
- intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME);
+ intent.setPackage(sysuiComponent.getPackageName());
sendBroadcastAsUser(intent, Process.myUserHandle());
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 590b3db..795f51a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4417,4 +4417,18 @@
}
}
}
+
+ /**
+ * Get packages of bugreport-whitelisted apps to handle a bug report.
+ *
+ * @return packages of bugreport-whitelisted apps to handle a bug report.
+ * @hide
+ */
+ public List<String> getBugreportWhitelistedPackages() {
+ try {
+ return getService().getBugreportWhitelistedPackages();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index aa8a302..112bd30 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -388,6 +388,7 @@
void requestFullBugReport();
void requestRemoteBugReport();
boolean launchBugReportHandlerApp();
+ List<String> getBugreportWhitelistedPackages();
@UnsupportedAppUsage
Intent getIntentForIntentSender(in IIntentSender sender);
diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl
index 7318762..8618fbb 100644
--- a/core/java/android/app/IInstantAppResolver.aidl
+++ b/core/java/android/app/IInstantAppResolver.aidl
@@ -16,15 +16,13 @@
package android.app;
-import android.content.Intent;
+import android.content.pm.InstantAppRequestInfo;
import android.os.IRemoteCallback;
/** @hide */
oneway interface IInstantAppResolver {
- void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
- int userId, String token, int sequence, IRemoteCallback callback);
+ void getInstantAppResolveInfoList(in InstantAppRequestInfo request, int sequence,
+ IRemoteCallback callback);
- void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
- int userId, String token, IRemoteCallback callback);
-
+ void getInstantAppIntentFilterList(in InstantAppRequestInfo request, IRemoteCallback callback);
}
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index a7be542..a413c60 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -21,6 +21,7 @@
import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.InstantAppRequestInfo;
import android.content.pm.InstantAppResolveInfo;
import android.os.Build;
import android.os.Bundle;
@@ -59,8 +60,9 @@
* Called to retrieve resolve info for instant applications immediately.
*
* @param digestPrefix The hash prefix of the instant app's domain.
- * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
- * String, InstantAppResolutionCallback)}.
+ *
+ * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
@Deprecated
public void onGetInstantAppResolveInfo(@Nullable int[] digestPrefix, @NonNull String token,
@@ -73,8 +75,9 @@
* sources.
*
* @param digestPrefix The hash prefix of the instant app's domain.
- * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
- * String, InstantAppResolutionCallback)}.
+ *
+ * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
@Deprecated
public void onGetInstantAppIntentFilter(@Nullable int[] digestPrefix, @NonNull String token,
@@ -103,8 +106,8 @@
*
* @see InstantAppResolveInfo
*
- * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
- * String, InstantAppResolutionCallback)}.
+ * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
@Deprecated
public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
@@ -134,8 +137,8 @@
* {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
* @param callback The {@link InstantAppResolutionCallback} to provide results to.
*
- * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
- * String, InstantAppResolutionCallback)}.
+ * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
@Deprecated
public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
@@ -170,7 +173,11 @@
* @param callback The {@link InstantAppResolutionCallback} to provide results to.
*
* @see InstantAppResolveInfo
+ *
+ * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+ * InstantAppResolutionCallback
*/
+ @Deprecated
public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
@Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
@NonNull String token, @NonNull InstantAppResolutionCallback callback) {
@@ -193,7 +200,11 @@
* Intent, int[], UserHandle, String, InstantAppResolutionCallback)} and provided
* to the currently visible installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
* @param callback The {@link InstantAppResolutionCallback} to provide results to.
+ *
+ * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
+ @Deprecated
public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
@Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
@NonNull String token, @NonNull InstantAppResolutionCallback callback) {
@@ -202,6 +213,41 @@
}
/**
+ * Called to retrieve resolve info for instant applications immediately. The response will be
+ * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided
+ * in response to this method may be partial to request a second phase of resolution which will
+ * result in a subsequent call to {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
+ *
+ * @param request The parameters for this resolution request
+ * @param callback The {@link InstantAppResolutionCallback} to provide results to.
+ *
+ * @see InstantAppResolveInfo
+ */
+ public void onGetInstantAppResolveInfo(@NonNull InstantAppRequestInfo request,
+ @NonNull InstantAppResolutionCallback callback) {
+ // If not overridden, forward to the old method.
+ onGetInstantAppResolveInfo(request.intent, request.hostDigestPrefix, request.userHandle,
+ request.token, callback);
+ }
+
+ /**
+ * Called to retrieve intent filters for potentially matching instant applications. Unlike
+ * {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo, InstantAppResolutionCallback)},
+ * the response may take as long as necessary to respond. All {@link InstantAppResolveInfo}s
+ * provided in response to this method must be completely populated.
+ *
+ * @param request The parameters for this resolution request
+ * @param callback The {@link InstantAppResolutionCallback} to provide results to.
+ */
+ public void onGetInstantAppIntentFilter(@NonNull InstantAppRequestInfo request,
+ @NonNull InstantAppResolutionCallback callback) {
+ // If not overridden, forward to the old method.
+ onGetInstantAppIntentFilter(request.intent, request.hostDigestPrefix, request.userHandle,
+ request.token, callback);
+ }
+
+ /**
* Returns a {@link Looper} to perform service operations on.
*/
Looper getLooper() {
@@ -218,35 +264,29 @@
public final IBinder onBind(Intent intent) {
return new IInstantAppResolver.Stub() {
@Override
- public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix,
- int userId, String token, int sequence, IRemoteCallback callback) {
+ public void getInstantAppResolveInfoList(InstantAppRequestInfo request, int sequence,
+ IRemoteCallback callback) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "[" + token + "] Phase1 called; posting");
+ Slog.v(TAG, "[" + request.token + "] Phase1 called; posting");
}
final SomeArgs args = SomeArgs.obtain();
- args.arg1 = callback;
- args.arg2 = digestPrefix;
- args.arg3 = userId;
- args.arg4 = token;
- args.arg5 = sanitizedIntent;
- mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO,
- sequence, 0, args).sendToTarget();
+ args.arg1 = request;
+ args.arg2 = callback;
+ mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence,
+ 0, args).sendToTarget();
}
@Override
- public void getInstantAppIntentFilterList(Intent sanitizedIntent,
- int[] digestPrefix, int userId, String token, IRemoteCallback callback) {
+ public void getInstantAppIntentFilterList(InstantAppRequestInfo request,
+ IRemoteCallback callback) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "[" + token + "] Phase2 called; posting");
+ Slog.v(TAG, "[" + request.token + "] Phase2 called; posting");
}
final SomeArgs args = SomeArgs.obtain();
- args.arg1 = callback;
- args.arg2 = digestPrefix;
- args.arg3 = userId;
- args.arg4 = token;
- args.arg5 = sanitizedIntent;
- mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
- args).sendToTarget();
+ args.arg1 = request;
+ args.arg2 = callback;
+ mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, args)
+ .sendToTarget();
}
};
}
@@ -287,36 +327,33 @@
switch (action) {
case MSG_GET_INSTANT_APP_RESOLVE_INFO: {
final SomeArgs args = (SomeArgs) message.obj;
- final IRemoteCallback callback = (IRemoteCallback) args.arg1;
- final int[] digestPrefix = (int[]) args.arg2;
- final int userId = (int) args.arg3;
- final String token = (String) args.arg4;
- final Intent intent = (Intent) args.arg5;
+ final InstantAppRequestInfo request = (InstantAppRequestInfo) args.arg1;
+ final IRemoteCallback callback = (IRemoteCallback) args.arg2;
+ args.recycle();
final int sequence = message.arg1;
if (DEBUG_INSTANT) {
- Slog.d(TAG, "[" + token + "] Phase1 request;"
- + " prefix: " + Arrays.toString(digestPrefix)
- + ", userId: " + userId);
+ Slog.d(TAG, "[" + request.token + "] Phase1 request;"
+ + " prefix: " + Arrays.toString(request.hostDigestPrefix)
+ + ", userId: " + request.userHandle.getIdentifier());
}
- onGetInstantAppResolveInfo(intent, digestPrefix, UserHandle.of(userId), token,
+ onGetInstantAppResolveInfo(request,
new InstantAppResolutionCallback(sequence, callback));
} break;
case MSG_GET_INSTANT_APP_INTENT_FILTER: {
final SomeArgs args = (SomeArgs) message.obj;
- final IRemoteCallback callback = (IRemoteCallback) args.arg1;
- final int[] digestPrefix = (int[]) args.arg2;
- final int userId = (int) args.arg3;
- final String token = (String) args.arg4;
- final Intent intent = (Intent) args.arg5;
+ final InstantAppRequestInfo request = (InstantAppRequestInfo) args.arg1;
+ final IRemoteCallback callback = (IRemoteCallback) args.arg2;
+ args.recycle();
if (DEBUG_INSTANT) {
- Slog.d(TAG, "[" + token + "] Phase2 request;"
- + " prefix: " + Arrays.toString(digestPrefix)
- + ", userId: " + userId);
+ Slog.d(TAG, "[" + request.token + "] Phase2 request;"
+ + " prefix: " + Arrays.toString(request.hostDigestPrefix)
+ + ", userId: " + request.userHandle.getIdentifier());
}
- onGetInstantAppIntentFilter(intent, digestPrefix, UserHandle.of(userId), token,
+ onGetInstantAppIntentFilter(request,
new InstantAppResolutionCallback(-1 /*sequence*/, callback));
- } break;
+ }
+ break;
default: {
throw new IllegalArgumentException("Unknown message: " + action);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index bd948ec5..a1305da 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1145,6 +1145,14 @@
return new TimeZoneDetector();
}});
+ registerService(Context.TELEPHONY_IMS_SERVICE, android.telephony.ims.ImsManager.class,
+ new CachedServiceFetcher<android.telephony.ims.ImsManager>() {
+ @Override
+ public android.telephony.ims.ImsManager createService(ContextImpl ctx) {
+ return new android.telephony.ims.ImsManager(ctx.getOuterContext());
+ }
+ });
+
registerService(Context.PERMISSION_SERVICE, PermissionManager.class,
new CachedServiceFetcher<PermissionManager>() {
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6e7ead1..7332978 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1349,6 +1349,9 @@
* Broadcast action: send when any policy admin changes a policy.
* This is generally used to find out when a new policy is in effect.
*
+ * If the profile owner of an organization-owned managed profile changes some user
+ * restriction explicitly on the parent user, this broadcast will <em>not</em> be
+ * sent to the parent user.
* @hide
*/
@UnsupportedAppUsage
@@ -7958,18 +7961,23 @@
* <p>
* The calling device admin must be a profile or device owner; if it is not, a security
* exception will be thrown.
+ * <p>
+ * The profile owner of an organization-owned managed profile may invoke this method on
+ * the {@link DevicePolicyManager} instance it obtained from
+ * {@link #getParentProfileInstance(ComponentName)}, for enforcing device-wide restrictions.
+ * <p>
+ * See the constants in {@link android.os.UserManager} for the list of restrictions that can
+ * be enforced device-wide.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param key The key of the restriction. See the constants in {@link android.os.UserManager}
- * for the list of keys.
+ * @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public void addUserRestriction(@NonNull ComponentName admin,
@UserManager.UserRestrictionKey String key) {
- throwIfParentInstance("addUserRestriction");
if (mService != null) {
try {
- mService.setUserRestriction(admin, key, true);
+ mService.setUserRestriction(admin, key, true, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -7981,18 +7989,22 @@
* <p>
* The calling device admin must be a profile or device owner; if it is not, a security
* exception will be thrown.
+ * <p>
+ * The profile owner of an organization-owned managed profile may invoke this method on
+ * the {@link DevicePolicyManager} instance it obtained from
+ * {@link #getParentProfileInstance(ComponentName)}, for clearing device-wide restrictions.
+ * <p>
+ * See the constants in {@link android.os.UserManager} for the list of restrictions.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param key The key of the restriction. See the constants in {@link android.os.UserManager}
- * for the list of keys.
+ * @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public void clearUserRestriction(@NonNull ComponentName admin,
@UserManager.UserRestrictionKey String key) {
- throwIfParentInstance("clearUserRestriction");
if (mService != null) {
try {
- mService.setUserRestriction(admin, key, false);
+ mService.setUserRestriction(admin, key, false, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8006,16 +8018,20 @@
* The target user may have more restrictions set by the system or other device owner / profile
* owner. To get all the user restrictions currently set, use
* {@link UserManager#getUserRestrictions()}.
+ * <p>
+ * The profile owner of an organization-owned managed profile may invoke this method on
+ * the {@link DevicePolicyManager} instance it obtained from
+ * {@link #getParentProfileInstance(ComponentName)}, for retrieving device-wide restrictions
+ * it previously set with {@link #addUserRestriction(ComponentName, String)}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public @NonNull Bundle getUserRestrictions(@NonNull ComponentName admin) {
- throwIfParentInstance("getUserRestrictions");
Bundle ret = null;
if (mService != null) {
try {
- ret = mService.getUserRestrictions(admin);
+ ret = mService.getUserRestrictions(admin, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 949e8ab..9c82ff6 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -208,8 +208,8 @@
void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
ComponentName getRestrictionsProvider(int userHandle);
- void setUserRestriction(in ComponentName who, in String key, boolean enable);
- Bundle getUserRestrictions(in ComponentName who);
+ void setUserRestriction(in ComponentName who, in String key, boolean enable, boolean parent);
+ Bundle getUserRestrictions(in ComponentName who, boolean parent);
void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
void clearCrossProfileIntentFilters(in ComponentName admin);
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 5e530ee..bd1eea5 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -515,8 +515,8 @@
public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
String callingPackage) {
Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
- intent.setComponent(new ComponentName("com.android.systemui",
- "com.android.systemui.SlicePermissionActivity"));
+ intent.setComponent(ComponentName.unflattenFromString(context.getResources().getString(
+ com.android.internal.R.string.config_slicePermissionComponent)));
intent.putExtra(EXTRA_BIND_URI, sliceUri);
intent.putExtra(EXTRA_PKG, callingPackage);
intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index cf33676..c6957e1 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -17,7 +17,9 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -39,6 +41,7 @@
*
* @hide
*/
+@SystemApi
public final class BluetoothA2dpSink implements BluetoothProfile {
private static final String TAG = "BluetoothA2dpSink";
private static final boolean DBG = true;
@@ -59,71 +62,14 @@
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
+ * @hide
*/
+ @SystemApi
+ @SuppressLint("ActionValue")
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
- /**
- * Intent used to broadcast the change in the Playing state of the A2DP Sink
- * profile.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
- * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * </ul>
- *
- * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
- * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- */
- public static final String ACTION_PLAYING_STATE_CHANGED =
- "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED";
-
- /**
- * A2DP sink device is streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
- */
- public static final int STATE_PLAYING = 10;
-
- /**
- * A2DP sink device is NOT streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
- */
- public static final int STATE_NOT_PLAYING = 11;
-
- /**
- * Intent used to broadcast the change in the Playing state of the A2DP Sink
- * profile.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link #EXTRA_AUDIO_CONFIG} - The audio configuration for the remote device. </li>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * </ul>
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- */
- public static final String ACTION_AUDIO_CONFIG_CHANGED =
- "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED";
-
- /**
- * Extra for the {@link #ACTION_AUDIO_CONFIG_CHANGED} intent.
- *
- * This extra represents the current audio configuration of the A2DP source device.
- * {@see BluetoothAudioConfig}
- */
- public static final String EXTRA_AUDIO_CONFIG =
- "android.bluetooth.a2dp-sink.profile.extra.AUDIO_CONFIG";
-
private BluetoothAdapter mAdapter;
private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector =
new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK,
@@ -170,13 +116,11 @@
* the state. Users can get the connection state of the profile
* from this intent.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothA2dpSink service = getService();
@@ -210,14 +154,12 @@
* {@link #STATE_DISCONNECTING} can be used to distinguish between the
* two scenarios.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothA2dpSink service = getService();
@@ -235,6 +177,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getConnectedDevices() {
@@ -254,6 +198,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -273,6 +219,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public int getConnectionState(BluetoothDevice device) {
@@ -300,6 +248,8 @@
* @return audio configuration for the device, or null
*
* {@see BluetoothAudioConfig}
+ *
+ * @hide
*/
public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
if (VDBG) log("getAudioConfig(" + device + ")");
@@ -347,7 +297,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
- public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothA2dpSink service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -395,7 +345,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public int getConnectionPolicy(BluetoothDevice device) {
+ public int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothA2dpSink service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -411,13 +361,16 @@
}
/**
- * Check if A2DP profile is streaming music.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ * Check if audio is playing on the bluetooth device (A2DP profile is streaming music).
*
* @param device BluetoothDevice device
+ * @return true if audio is playing (A2dp is streaming music), false otherwise
+ *
+ * @hide
*/
- public boolean isA2dpPlaying(BluetoothDevice device) {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ public boolean isAudioPlaying(@Nullable BluetoothDevice device) {
final IBluetoothA2dpSink service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
try {
@@ -448,9 +401,9 @@
return "connected";
case STATE_DISCONNECTING:
return "disconnecting";
- case STATE_PLAYING:
+ case BluetoothA2dp.STATE_PLAYING:
return "playing";
- case STATE_NOT_PLAYING:
+ case BluetoothA2dp.STATE_NOT_PLAYING:
return "not playing";
default:
return "<unknown state " + state + ">";
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index d94c657..df02896 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -16,7 +16,10 @@
package android.bluetooth;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -54,6 +57,7 @@
*
* @hide
*/
+@SystemApi
public class BluetoothPbap implements BluetoothProfile {
private static final String TAG = "BluetoothPbap";
@@ -75,7 +79,11 @@
* {@link BluetoothProfile#STATE_DISCONNECTING}.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
* receive.
+ *
+ * @hide
*/
+ @SuppressLint("ActionValue")
+ @SystemApi
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
@@ -85,33 +93,16 @@
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
+ /** @hide */
public static final int RESULT_FAILURE = 0;
+ /** @hide */
public static final int RESULT_SUCCESS = 1;
- /** Connection canceled before completion. */
- public static final int RESULT_CANCELED = 2;
-
/**
- * An interface for notifying Bluetooth PCE IPC clients when they have
- * been connected to the BluetoothPbap service.
+ * Connection canceled before completion.
+ *
+ * @hide
*/
- public interface ServiceListener {
- /**
- * Called to notify the client when this proxy object has been
- * connected to the BluetoothPbap service. Clients must wait for
- * this callback before making IPC calls on the BluetoothPbap
- * service.
- */
- public void onServiceConnected(BluetoothPbap proxy);
-
- /**
- * Called to notify the client that this proxy object has been
- * disconnected from the BluetoothPbap service. Clients must not
- * make IPC calls on the BluetoothPbap service after this callback.
- * This callback will currently only occur if the application hosting
- * the BluetoothPbap service, but may be called more often in future.
- */
- public void onServiceDisconnected();
- }
+ public static final int RESULT_CANCELED = 2;
private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
new IBluetoothStateChangeCallback.Stub() {
@@ -127,6 +118,8 @@
/**
* Create a BluetoothPbap proxy object.
+ *
+ * @hide
*/
public BluetoothPbap(Context context, ServiceListener l) {
mContext = context;
@@ -181,6 +174,7 @@
}
}
+ /** @hide */
protected void finalize() throws Throwable {
try {
close();
@@ -194,6 +188,8 @@
* Other public functions of BluetoothPbap will return default error
* results once close() has been called. Multiple invocations of close()
* are ok.
+ *
+ * @hide
*/
public synchronized void close() {
IBluetoothManager mgr = mAdapter.getBluetoothManager();
@@ -210,6 +206,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getConnectedDevices() {
@@ -229,17 +227,22 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
+ @SystemApi
@Override
- public int getConnectionState(BluetoothDevice device) {
+ public int getConnectionState(@Nullable BluetoothDevice device) {
log("getConnectionState: device=" + device);
- final IBluetoothPbap service = mService;
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
- }
try {
- return service.getConnectionState(device);
+ final IBluetoothPbap service = mService;
+ if (service != null && isEnabled() && isValidDevice(device)) {
+ return service.getConnectionState(device);
+ }
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
@@ -248,6 +251,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -266,22 +271,12 @@
}
/**
- * Returns true if the specified Bluetooth device is connected (does not
- * include connecting). Returns false if not connected, or if this proxy
- * object is not currently connected to the Pbap service.
- */
- // TODO: This is currently being used by SettingsLib and internal app.
- public boolean isConnected(BluetoothDevice device) {
- return getConnectionState(device) == BluetoothAdapter.STATE_CONNECTED;
- }
-
- /**
* Disconnects the current Pbap client (PCE). Currently this call blocks,
* it may soon be made asynchronous. Returns false if this proxy object is
* not currently connected to the Pbap service.
+ *
+ * @hide
*/
- // TODO: This is currently being used by SettingsLib and will be used in the future.
- // TODO: Must specify target device. Implement this in the service.
@UnsupportedAppUsage
public boolean disconnect(BluetoothDevice device) {
log("disconnect()");
@@ -304,7 +299,7 @@
log("Proxy object connected");
mService = IBluetoothPbap.Stub.asInterface(service);
if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothPbap.this);
+ mServiceListener.onServiceConnected(BluetoothProfile.PBAP, BluetoothPbap.this);
}
}
@@ -312,11 +307,23 @@
log("Proxy object disconnected");
doUnbind();
if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected();
+ mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP);
}
}
};
+ private boolean isEnabled() {
+ if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+ return false;
+ }
+
+ private boolean isValidDevice(BluetoothDevice device) {
+ if (device == null) return false;
+
+ if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+ return false;
+ }
+
private static void log(String msg) {
if (DBG) {
Log.d(TAG, msg);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 85027d9..d084449 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4336,6 +4336,15 @@
public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
/**
+ * Use with {@link #getSystemService(String)} to access the
+ * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
+
+ /**
* Official published name of the (internal) permission service.
*
* @see #getSystemService(String)
@@ -4916,6 +4925,8 @@
* {@link android.telephony.ims.ImsManager}.
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
/**
diff --git a/core/java/android/content/pm/AuxiliaryResolveInfo.java b/core/java/android/content/pm/AuxiliaryResolveInfo.java
index 202df50..7d07e1d 100644
--- a/core/java/android/content/pm/AuxiliaryResolveInfo.java
+++ b/core/java/android/content/pm/AuxiliaryResolveInfo.java
@@ -46,17 +46,21 @@
public final Intent failureIntent;
/** The matching filters for this resolve info. */
public final List<AuxiliaryFilter> filters;
+ /** Stored {@link InstantAppRequest#hostDigestPrefixSecure} to prevent re-generation */
+ public final int[] hostDigestPrefixSecure;
/** Create a response for installing an instant application. */
public AuxiliaryResolveInfo(@NonNull String token,
boolean needsPhase2,
@Nullable Intent failureIntent,
- @Nullable List<AuxiliaryFilter> filters) {
+ @Nullable List<AuxiliaryFilter> filters,
+ @Nullable int[] hostDigestPrefix) {
this.token = token;
this.needsPhaseTwo = needsPhase2;
this.failureIntent = failureIntent;
this.filters = filters;
this.installFailureActivity = null;
+ this.hostDigestPrefixSecure = hostDigestPrefix;
}
/** Create a response for installing a split on demand. */
@@ -69,6 +73,7 @@
this.token = null;
this.needsPhaseTwo = false;
this.failureIntent = failureIntent;
+ this.hostDigestPrefixSecure = null;
}
/** Create a response for installing a split on demand. */
@@ -126,4 +131,4 @@
+ ", splitName='" + splitName + '\'' + '}';
}
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index 4178309..ffbca16 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -47,7 +47,7 @@
* TODO get this number from somewhere else. For now set it to a quarter of
* the 1MB limit.
*/
- private static final int MAX_IPC_SIZE = IBinder.MAX_IPC_SIZE;
+ private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
private final List<T> mList;
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 0b3c765..e954635 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -39,6 +39,9 @@
void transfer(in String packageName, in IntentSender statusReceiver);
void abandon();
+ void addFile(String name, long lengthBytes, in byte[] metadata);
+ void removeFile(String name);
+
boolean isMultiPackage();
int[] getChildSessionIds();
void addChildSessionId(in int sessionId);
@@ -46,5 +49,4 @@
int getParentSessionId();
boolean isStaged();
- void addFile(in String name, long size, in byte[] metadata);
}
diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java
index 361d4e4..f692db1 100644
--- a/core/java/android/content/pm/InstantAppRequest.java
+++ b/core/java/android/content/pm/InstantAppRequest.java
@@ -16,9 +16,10 @@
package android.content.pm;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Intent;
import android.os.Bundle;
-import android.text.TextUtils;
/**
* Information needed to make an instant application resolution request.
@@ -34,32 +35,40 @@
public final String resolvedType;
/** The name of the package requesting the instant application */
public final String callingPackage;
+ /** Whether or not the requesting package was an instant app */
+ public final boolean isRequesterInstantApp;
/** ID of the user requesting the instant application */
public final int userId;
/**
* Optional extra bundle provided by the source application to the installer for additional
- * verification. */
+ * verification.
+ */
public final Bundle verificationBundle;
/** Whether resolution occurs because an application is starting */
public final boolean resolveForStart;
- /** The instant app digest for this request */
- public final InstantAppResolveInfo.InstantAppDigest digest;
+ /**
+ * The hash prefix of an instant app's domain or null if no host is defined.
+ * Secure version that should be carried through for external use.
+ */
+ @Nullable
+ public final int[] hostDigestPrefixSecure;
+ /** A unique identifier */
+ @NonNull
+ public final String token;
public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
- String resolvedType, String callingPackage, int userId, Bundle verificationBundle,
- boolean resolveForStart) {
+ String resolvedType, String callingPackage, boolean isRequesterInstantApp,
+ int userId, Bundle verificationBundle, boolean resolveForStart,
+ @Nullable int[] hostDigestPrefixSecure, @NonNull String token) {
this.responseObj = responseObj;
this.origIntent = origIntent;
this.resolvedType = resolvedType;
this.callingPackage = callingPackage;
+ this.isRequesterInstantApp = isRequesterInstantApp;
this.userId = userId;
this.verificationBundle = verificationBundle;
this.resolveForStart = resolveForStart;
- if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) {
- digest = new InstantAppResolveInfo.InstantAppDigest(
- origIntent.getData().getHost(), 5 /*maxDigests*/);
- } else {
- digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED;
- }
+ this.hostDigestPrefixSecure = hostDigestPrefixSecure;
+ this.token = token;
}
}
diff --git a/core/java/android/content/pm/InstantAppRequestInfo.aidl b/core/java/android/content/pm/InstantAppRequestInfo.aidl
new file mode 100644
index 0000000..0f94220
--- /dev/null
+++ b/core/java/android/content/pm/InstantAppRequestInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable InstantAppRequestInfo;
diff --git a/core/java/android/content/pm/InstantAppRequestInfo.java b/core/java/android/content/pm/InstantAppRequestInfo.java
new file mode 100644
index 0000000..83d5536
--- /dev/null
+++ b/core/java/android/content/pm/InstantAppRequestInfo.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information exposed to {@link android.app.InstantAppResolverService} to complete
+ * an instant application resolution request.
+ * @hide
+ */
+@SystemApi
+@DataClass(genParcelable = true, genConstructor = true, genAidl = true)
+public final class InstantAppRequestInfo implements Parcelable {
+
+ /**
+ * The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+ * potential PII removed from the original intent. Fields removed include extras and the
+ * host + path of the data, if defined.
+ */
+ @NonNull
+ public final Intent intent;
+
+ /** The hash prefix of the instant app's domain or null if no host is defined. */
+ @Nullable
+ public final int[] hostDigestPrefix;
+
+ /** The user requesting the instant application */
+ @NonNull
+ public final UserHandle userHandle;
+
+ /** Whether or not the requesting package was an instant app itself */
+ public final boolean isRequesterInstantApp;
+
+ /** A unique identifier */
+ @NonNull
+ public final String token;
+
+
+
+ // Code below generated by codegen v1.0.13.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/InstantAppRequestInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new InstantAppRequestInfo.
+ *
+ * @param intent
+ * The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+ * potential PII removed from the original intent. Fields removed include extras and the
+ * host + path of the data, if defined.
+ * @param hostDigestPrefix
+ * The hash prefix of the instant app's domain or null if no host is defined.
+ * @param userHandle
+ * The user requesting the instant application
+ * @param isRequesterInstantApp
+ * Whether or not the requesting package was an instant app itself
+ * @param token
+ * A unique identifier
+ */
+ @DataClass.Generated.Member
+ public InstantAppRequestInfo(
+ @NonNull Intent intent,
+ @Nullable int[] hostDigestPrefix,
+ @NonNull UserHandle userHandle,
+ boolean isRequesterInstantApp,
+ @NonNull String token) {
+ this.intent = intent;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, intent);
+ this.hostDigestPrefix = hostDigestPrefix;
+ this.userHandle = userHandle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, userHandle);
+ this.isRequesterInstantApp = isRequesterInstantApp;
+ this.token = token;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, token);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (isRequesterInstantApp) flg |= 0x8;
+ if (hostDigestPrefix != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeTypedObject(intent, flags);
+ if (hostDigestPrefix != null) dest.writeIntArray(hostDigestPrefix);
+ dest.writeTypedObject(userHandle, flags);
+ dest.writeString(token);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InstantAppRequestInfo(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean _isRequesterInstantApp = (flg & 0x8) != 0;
+ Intent _intent = (Intent) in.readTypedObject(Intent.CREATOR);
+ int[] _hostDigestPrefix = (flg & 0x2) == 0 ? null : in.createIntArray();
+ UserHandle _userHandle = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
+ String _token = in.readString();
+
+ this.intent = _intent;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, intent);
+ this.hostDigestPrefix = _hostDigestPrefix;
+ this.userHandle = _userHandle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, userHandle);
+ this.isRequesterInstantApp = _isRequesterInstantApp;
+ this.token = _token;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, token);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InstantAppRequestInfo> CREATOR
+ = new Parcelable.Creator<InstantAppRequestInfo>() {
+ @Override
+ public InstantAppRequestInfo[] newArray(int size) {
+ return new InstantAppRequestInfo[size];
+ }
+
+ @Override
+ public InstantAppRequestInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new InstantAppRequestInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1574373347443L,
+ codegenVersion = "1.0.13",
+ sourceFile = "frameworks/base/core/java/android/content/pm/InstantAppRequestInfo.java",
+ inputSignatures = "public final @android.annotation.NonNull android.content.Intent intent\npublic final @android.annotation.Nullable int[] hostDigestPrefix\npublic final @android.annotation.NonNull android.os.UserHandle userHandle\npublic final boolean isRequesterInstantApp\npublic final @android.annotation.NonNull java.lang.String token\nclass InstantAppRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=true, genAidl=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 8f51435..898631e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -231,6 +231,15 @@
public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
/**
+ * Streaming installation pending.
+ * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
+ *
+ * @see #EXTRA_SESSION_ID
+ * {@hide}
+ */
+ public static final int STATUS_PENDING_STREAMING = -2;
+
+ /**
* User action is currently required to proceed. You can launch the intent
* activity described by {@link Intent#EXTRA_INTENT} to involve the user and
* continue.
@@ -1059,13 +1068,56 @@
}
}
+
+ /**
+ * Adds a file to session. On commit this file will be pulled from dataLoader.
+ *
+ * @param name arbitrary, unique name of your choosing to identify the
+ * APK being written. You can open a file again for
+ * additional writes (such as after a reboot) by using the
+ * same name. This name is only meaningful within the context
+ * of a single install session.
+ * @param lengthBytes total size of the file being written.
+ * The system may clear various caches as needed to allocate
+ * this space.
+ * @param metadata additional info use by dataLoader to pull data for the file.
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ * @throws IllegalStateException if called for non-callback session
+ * {@hide}
+ */
+ public void addFile(@NonNull String name, long lengthBytes, @NonNull byte[] metadata) {
+ try {
+ mSession.addFile(name, lengthBytes, metadata);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a file.
+ *
+ * @param name name of a file, e.g. split.
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ * @throws IllegalStateException if called for non-callback session
+ * {@hide}
+ */
+ public void removeFile(@NonNull String name) {
+ try {
+ mSession.removeFile(name);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Attempt to commit everything staged in this session. This may require
* user intervention, and so it may not happen immediately. The final
* result of the commit will be reported through the given callback.
* <p>
- * Once this method is called, the session is sealed and no additional
- * mutations may be performed on the session. If the device reboots
+ * Once this method is called, the session is sealed and no additional mutations may be
+ * performed on the session. In case of device reboot or data loader transient failure
* before the session has been finalized, you may commit the session again.
* <p>
* If the installer is the device owner or the affiliated profile owner, there will be no
@@ -1220,27 +1272,6 @@
}
/**
- * Configure files for an installation session.
- *
- * Currently only for Incremental installation session. Once this method is called,
- * the files and their paths, as specified in the parameters, will be created and properly
- * configured in the Incremental File System.
- *
- * TODO(b/136132412): update this and InstallationFile class with latest API design.
- *
- * @throws IllegalStateException if {@link SessionParams#incrementalParams} is null.
- *
- * @hide
- */
- public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
- try {
- mSession.addFile(name, size, metadata);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Release this session object. You can open the session again if it
* hasn't been finalized.
*/
@@ -1429,6 +1460,9 @@
public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
/** {@hide} */
public IncrementalDataLoaderParams incrementalParams;
+ /** TODO(b/146080380): add a class name to make it fully compatible with ComponentName.
+ * {@hide} */
+ public String dataLoaderPackageName;
/**
* Construct parameters for a new package install session.
@@ -1468,6 +1502,7 @@
incrementalParams = new IncrementalDataLoaderParams(
dataLoaderParamsParcel);
}
+ dataLoaderPackageName = source.readString();
}
/** {@hide} */
@@ -1492,6 +1527,7 @@
ret.isStaged = isStaged;
ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
ret.incrementalParams = incrementalParams;
+ ret.dataLoaderPackageName = dataLoaderPackageName;
return ret;
}
@@ -1831,6 +1867,20 @@
this.incrementalParams = incrementalParams;
}
+ /**
+ * Set the data provider params for the session.
+ * This also switches installation into callback mode and disallow direct writes into
+ * staging folder.
+ * TODO(b/146080380): unify dataprovider params with Incremental.
+ *
+ * @param dataLoaderPackageName name of the dataLoader package
+ * {@hide}
+ */
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+ public void setDataLoaderPackageName(String dataLoaderPackageName) {
+ this.dataLoaderPackageName = dataLoaderPackageName;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
@@ -1851,6 +1901,7 @@
pw.printPair("isMultiPackage", isMultiPackage);
pw.printPair("isStaged", isStaged);
pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
+ pw.printPair("dataLoaderPackageName", dataLoaderPackageName);
pw.println();
}
@@ -1885,6 +1936,7 @@
} else {
dest.writeParcelable(null, flags);
}
+ dest.writeString(dataLoaderPackageName);
}
public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index 515185e..35df474 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -229,6 +229,11 @@
String getOverlayTargetName();
+ /**
+ * Map of overlayable name to actor name.
+ */
+ Map<String, String> getOverlayables();
+
// TODO(b/135203078): Does this and getAppInfoPackageName have to be separate methods?
// The refactor makes them the same value with no known consequences, so should be redundant.
String getPackageName();
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index 0deb2ab..7732316 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -52,6 +52,7 @@
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
import android.content.pm.split.SplitAssetLoader;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -92,6 +93,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/** @hide */
@@ -287,8 +289,23 @@
+ result.getErrorMessage());
}
- return result.getResultAndNull()
- .setVolumeUuid(volumeUuid)
+ ParsingPackage pkg = result.getResultAndNull();
+ ApkAssets apkAssets = assets.getApkAssets()[0];
+ if (apkAssets.definesOverlayable()) {
+ SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String packageName = packageNames.get(index);
+ Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
+ if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
+ for (String overlayable : overlayableToActor.keySet()) {
+ pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable));
+ }
+ }
+ }
+ }
+
+ return pkg.setVolumeUuid(volumeUuid)
.setApplicationVolumeUuid(volumeUuid)
.setSigningDetails(SigningDetails.UNKNOWN);
} catch (PackageParserException e) {
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index 377279e..0e736d5 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -18,6 +18,8 @@
import static android.os.Build.VERSION_CODES.DONUT;
+import static java.util.Collections.emptyMap;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
@@ -55,11 +57,13 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.server.SystemConfig;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -126,6 +130,7 @@
private String overlayCategory;
private int overlayPriority;
private boolean overlayIsStatic;
+ private Map<String, String> overlayables = emptyMap();
private String staticSharedLibName;
private long staticSharedLibVersion;
@@ -475,7 +480,7 @@
@Override
public Map<String, ArraySet<PublicKey>> getKeySetMapping() {
- return keySetMapping == null ? Collections.emptyMap() : keySetMapping;
+ return keySetMapping == null ? emptyMap() : keySetMapping;
}
@Override
@@ -773,6 +778,13 @@
}
@Override
+ public ParsingPackage addOverlayable(String overlayableName, String actorName) {
+ this.overlayables = CollectionUtils.add(this.overlayables,
+ TextUtils.safeIntern(overlayableName), TextUtils.safeIntern(actorName));
+ return this;
+ }
+
+ @Override
public PackageImpl addAdoptPermission(String adoptPermission) {
this.adoptPermissions = ArrayUtils.add(this.adoptPermissions, adoptPermission);
return this;
@@ -2125,6 +2137,11 @@
}
@Override
+ public Map<String, String> getOverlayables() {
+ return overlayables;
+ }
+
+ @Override
public boolean isOverlayIsStatic() {
return overlayIsStatic;
}
@@ -2291,7 +2308,7 @@
appInfo.metaData = appMetaData;
appInfo.minAspectRatio = minAspectRatio;
appInfo.minSdkVersion = minSdkVersion;
- appInfo.name = name;
+ appInfo.name = className;
if (appInfo.name != null) {
appInfo.name = appInfo.name.trim();
}
@@ -2957,6 +2974,7 @@
dest.writeString(this.overlayCategory);
dest.writeInt(this.overlayPriority);
dest.writeBoolean(this.overlayIsStatic);
+ dest.writeMap(this.overlayables);
dest.writeString(this.staticSharedLibName);
dest.writeLong(this.staticSharedLibVersion);
dest.writeStringList(this.libraryNames);
@@ -3100,6 +3118,8 @@
this.overlayCategory = in.readString();
this.overlayPriority = in.readInt();
this.overlayIsStatic = in.readBoolean();
+ this.overlayables = new HashMap<>();
+ in.readMap(overlayables, boot);
this.staticSharedLibName = TextUtils.safeIntern(in.readString());
this.staticSharedLibVersion = in.readLong();
this.libraryNames = in.createStringArrayList();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 43c1f6e..aff1b2e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -62,6 +62,8 @@
ParsingPackage addOriginalPackage(String originalPackage);
+ ParsingPackage addOverlayable(String overlayableName, String actorName);
+
ParsingPackage addPermission(ParsedPermission permission);
ParsingPackage addPermissionGroup(ParsedPermissionGroup permissionGroup);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1f29d1a..c84c4a7 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -935,7 +935,7 @@
/**
* <p>List of the maximum number of regions that can be used for metering in
* auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF);
- * this corresponds to the the maximum number of elements in
+ * this corresponds to the maximum number of elements in
* {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}, {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions},
* and {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
* <p><b>Range of valid values:</b><br></p>
@@ -955,7 +955,7 @@
/**
* <p>The maximum number of metering regions that can be used by the auto-exposure (AE)
* routine.</p>
- * <p>This corresponds to the the maximum allowed number of elements in
+ * <p>This corresponds to the maximum allowed number of elements in
* {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}.</p>
* <p><b>Range of valid values:</b><br>
* Value will be >= 0. For FULL-capability devices, this
@@ -973,7 +973,7 @@
/**
* <p>The maximum number of metering regions that can be used by the auto-white balance (AWB)
* routine.</p>
- * <p>This corresponds to the the maximum allowed number of elements in
+ * <p>This corresponds to the maximum allowed number of elements in
* {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}.</p>
* <p><b>Range of valid values:</b><br>
* Value will be >= 0.</p>
@@ -989,7 +989,7 @@
/**
* <p>The maximum number of metering regions that can be used by the auto-focus (AF) routine.</p>
- * <p>This corresponds to the the maximum allowed number of elements in
+ * <p>This corresponds to the maximum allowed number of elements in
* {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
* <p><b>Range of valid values:</b><br>
* Value will be >= 0. For FULL-capability devices, this
@@ -1987,6 +1987,7 @@
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA SECURE_IMAGE_DATA}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA SYSTEM_CAMERA}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}</li>
* </ul></p>
* <p>This key is available on all devices.</p>
*
@@ -2006,6 +2007,7 @@
* @see #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME
* @see #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA
* @see #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 799c716..f2a7abd 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1004,6 +1004,51 @@
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14;
+ /**
+ * <p>The camera device supports the OFFLINE_PROCESSING use case.</p>
+ * <p>With OFFLINE_PROCESSING capability, the application can switch an ongoing
+ * capture session to offline mode by calling the
+ * CameraCaptureSession#switchToOffline method and specify streams to be kept in offline
+ * mode. The camera will then stop currently active repeating requests, prepare for
+ * some requests to go into offline mode, and return an offline session object. After
+ * the switchToOffline call returns, the original capture session is in closed state as
+ * if the CameraCaptureSession#close method has been called.
+ * In the offline mode, all inflight requests will continue to be processed in the
+ * background, and the application can immediately close the camera or create a new
+ * capture session without losing those requests' output images and capture results.</p>
+ * <p>While the camera device is processing offline requests, it
+ * might not be able to support all stream configurations it can support
+ * without offline requests. When that happens, the createCaptureSession
+ * method call will fail. The following stream configurations are guaranteed to work
+ * without hitting the resource busy exception:</p>
+ * <ul>
+ * <li>One ongoing offline session: target one output surface of YUV or
+ * JPEG format, any resolution.</li>
+ * <li>The active camera capture session:<ol>
+ * <li>One preview surface (SurfaceView or SurfaceTexture) up to 1920 width</li>
+ * <li>One YUV ImageReader surface up to 1920 width</li>
+ * <li>One Jpeg ImageReader, any resolution: the camera device is
+ * allowed to slow down JPEG output speed by 50% if there is any ongoing offline
+ * session.</li>
+ * <li>One ImageWriter surface of private format, any resolution if the device supports
+ * PRIVATE_REPROCESSING capability</li>
+ * </ol>
+ * </li>
+ * <li>Alternatively, the active camera session above can be replaced by an legacy
+ * {@link android.hardware.Camera Camera} with the following parameter settings:<ol>
+ * <li>Preview size up to 1920 width</li>
+ * <li>Preview callback size up to 1920 width</li>
+ * <li>Video size up to 1920 width</li>
+ * <li>Picture size, any resolution: the camera device is
+ * allowed to slow down JPEG output speed by 50% if there is any ongoing offline
+ * session.</li>
+ * </ol>
+ * </li>
+ * </ul>
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING = 15;
+
//
// Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
//
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index bcbc337..41435c9 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -29,6 +29,7 @@
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
@@ -866,6 +867,38 @@
}
}
+ public void switchToOffline(ICameraDeviceCallbacks cbs, Surface[] offlineOutputs)
+ throws CameraAccessException {
+ if ((offlineOutputs == null) || (offlineOutputs.length == 0)) {
+ throw new IllegalArgumentException("Invalid offline outputs!");
+ }
+ if (cbs == null) {
+ throw new IllegalArgumentException("Invalid device callbacks!");
+ }
+
+ ICameraOfflineSession offlineSession = null;
+ synchronized(mInterfaceLock) {
+ int streamId = -1;
+ for (Surface surface : offlineOutputs) {
+ for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+ if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
+ streamId = mConfiguredOutputs.keyAt(i);
+ break;
+ }
+ }
+ if (streamId == -1) {
+ throw new IllegalArgumentException("Offline surface is not part of this" +
+ " session");
+ }
+ }
+
+ offlineSession = mRemoteDevice.switchToOffline(cbs,
+ offlineOutputs);
+ // TODO: Initialize CameraOfflineSession wrapper, clear 'mConfiguredOutputs',
+ // and update request tracking
+ }
+ }
+
public void tearDown(Surface surface) throws CameraAccessException {
if (surface == null) throw new IllegalArgumentException("Surface is null");
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 3660f29..397417b 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -28,6 +28,8 @@
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
@@ -248,6 +250,17 @@
}
}
+ public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
+ Surface[] offlineOutputs)
+ throws CameraAccessException {
+ try {
+ return mRemoteDevice.switchToOffline(cbs, offlineOutputs);
+ } catch (Throwable t) {
+ CameraManager.throwAsPublicException(t);
+ throw new UnsupportedOperationException("Unexpected exception", t);
+ }
+ }
+
public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig)
throws CameraAccessException {
try {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 5d1435a..6ab0c29 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -24,6 +24,7 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
@@ -789,6 +790,12 @@
}
@Override
+ public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
+ Surface[] offlineOutputs) {
+ throw new UnsupportedOperationException("Legacy device does not support switchToOffline");
+ }
+
+ @Override
public IBinder asBinder() {
// This is solely intended to be used for in-process binding.
return null;
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 6eaf54b..23f18a8 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1163,7 +1163,7 @@
if (orderedPreviewSizes != null) {
for (Size size : orderedPreviewSizes) {
if ((mDisplaySize.getWidth() >= size.getWidth()) &&
- (mDisplaySize.getWidth() >= size.getHeight())) {
+ (mDisplaySize.getHeight() >= size.getHeight())) {
return size;
}
}
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
new file mode 100644
index 0000000..8231c58
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.soundtrigger;
+
+import android.hardware.soundtrigger.ModelParams;
+import android.media.AudioFormat;
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+import android.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+/** @hide */
+class ConversionUtil {
+ public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor(
+ SoundTriggerModuleDescriptor aidlDesc) {
+ SoundTriggerModuleProperties properties = aidlDesc.properties;
+ return new SoundTrigger.ModuleProperties(
+ aidlDesc.handle,
+ properties.implementor,
+ properties.description,
+ properties.uuid,
+ properties.version,
+ properties.maxSoundModels,
+ properties.maxKeyPhrases,
+ properties.maxUsers,
+ aidl2apiRecognitionModes(properties.recognitionModes),
+ properties.captureTransition,
+ properties.maxBufferMs,
+ properties.concurrentCapture,
+ properties.powerConsumptionMw,
+ properties.triggerInEvent
+ );
+ }
+
+ public static int aidl2apiRecognitionModes(int aidlModes) {
+ int result = 0;
+ if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+ }
+ if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+ }
+ if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
+ }
+ if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_GENERIC;
+ }
+ return result;
+ }
+
+ public static int api2aidlRecognitionModes(int apiModes) {
+ int result = 0;
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER) != 0) {
+ result |= RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION) != 0) {
+ result |= RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION) != 0) {
+ result |= RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_GENERIC) != 0) {
+ result |= RecognitionMode.GENERIC_TRIGGER;
+ }
+ return result;
+ }
+
+
+ public static SoundModel api2aidlGenericSoundModel(SoundTrigger.GenericSoundModel apiModel) {
+ return api2aidlSoundModel(apiModel);
+ }
+
+ public static SoundModel api2aidlSoundModel(SoundTrigger.SoundModel apiModel) {
+ SoundModel aidlModel = new SoundModel();
+ aidlModel.type = apiModel.type;
+ aidlModel.uuid = api2aidlUuid(apiModel.uuid);
+ aidlModel.vendorUuid = api2aidlUuid(apiModel.vendorUuid);
+ aidlModel.data = Arrays.copyOf(apiModel.data, apiModel.data.length);
+ return aidlModel;
+ }
+
+ public static String api2aidlUuid(UUID apiUuid) {
+ return apiUuid.toString();
+ }
+
+ public static PhraseSoundModel api2aidlPhraseSoundModel(
+ SoundTrigger.KeyphraseSoundModel apiModel) {
+ PhraseSoundModel aidlModel = new PhraseSoundModel();
+ aidlModel.common = api2aidlSoundModel(apiModel);
+ aidlModel.phrases = new Phrase[apiModel.keyphrases.length];
+ for (int i = 0; i < apiModel.keyphrases.length; ++i) {
+ aidlModel.phrases[i] = api2aidlPhrase(apiModel.keyphrases[i]);
+ }
+ return aidlModel;
+ }
+
+ public static Phrase api2aidlPhrase(SoundTrigger.Keyphrase apiPhrase) {
+ Phrase aidlPhrase = new Phrase();
+ aidlPhrase.id = apiPhrase.id;
+ aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.recognitionModes);
+ aidlPhrase.users = Arrays.copyOf(apiPhrase.users, apiPhrase.users.length);
+ aidlPhrase.locale = apiPhrase.locale;
+ aidlPhrase.text = apiPhrase.text;
+ return aidlPhrase;
+ }
+
+ public static RecognitionConfig api2aidlRecognitionConfig(
+ SoundTrigger.RecognitionConfig apiConfig) {
+ RecognitionConfig aidlConfig = new RecognitionConfig();
+ aidlConfig.captureRequested = apiConfig.captureRequested;
+ // apiConfig.allowMultipleTriggers is ignored by the lower layers.
+ aidlConfig.phraseRecognitionExtras =
+ new PhraseRecognitionExtra[apiConfig.keyphrases.length];
+ for (int i = 0; i < apiConfig.keyphrases.length; ++i) {
+ aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra(
+ apiConfig.keyphrases[i]);
+ }
+ aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+ return aidlConfig;
+ }
+
+ public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
+ SoundTrigger.KeyphraseRecognitionExtra apiExtra) {
+ PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+ aidlExtra.id = apiExtra.id;
+ aidlExtra.recognitionModes = api2aidlRecognitionModes(apiExtra.recognitionModes);
+ aidlExtra.confidenceLevel = apiExtra.coarseConfidenceLevel;
+ aidlExtra.levels = new ConfidenceLevel[apiExtra.confidenceLevels.length];
+ for (int i = 0; i < apiExtra.confidenceLevels.length; ++i) {
+ aidlExtra.levels[i] = api2aidlConfidenceLevel(apiExtra.confidenceLevels[i]);
+ }
+ return aidlExtra;
+ }
+
+ public static SoundTrigger.KeyphraseRecognitionExtra aidl2apiPhraseRecognitionExtra(
+ PhraseRecognitionExtra aidlExtra) {
+ SoundTrigger.ConfidenceLevel[] apiLevels =
+ new SoundTrigger.ConfidenceLevel[aidlExtra.levels.length];
+ for (int i = 0; i < aidlExtra.levels.length; ++i) {
+ apiLevels[i] = aidl2apiConfidenceLevel(aidlExtra.levels[i]);
+ }
+ return new SoundTrigger.KeyphraseRecognitionExtra(aidlExtra.id,
+ aidl2apiRecognitionModes(aidlExtra.recognitionModes),
+ aidlExtra.confidenceLevel, apiLevels);
+ }
+
+ public static ConfidenceLevel api2aidlConfidenceLevel(
+ SoundTrigger.ConfidenceLevel apiLevel) {
+ ConfidenceLevel aidlLevel = new ConfidenceLevel();
+ aidlLevel.levelPercent = apiLevel.confidenceLevel;
+ aidlLevel.userId = apiLevel.userId;
+ return aidlLevel;
+ }
+
+ public static SoundTrigger.ConfidenceLevel aidl2apiConfidenceLevel(
+ ConfidenceLevel apiLevel) {
+ return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
+ }
+
+ public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
+ int modelHandle, RecognitionEvent aidlEvent) {
+ return new SoundTrigger.GenericRecognitionEvent(
+ aidlEvent.status,
+ modelHandle, aidlEvent.captureAvailable, aidlEvent.captureSession,
+ aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
+ aidl2apiAudioFormat(aidlEvent.audioConfig), aidlEvent.data);
+ }
+
+ public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
+ int modelHandle,
+ PhraseRecognitionEvent aidlEvent) {
+ SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
+ new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
+ for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
+ apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
+ }
+ return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
+ aidlEvent.common.captureAvailable,
+ aidlEvent.common.captureSession, aidlEvent.common.captureDelayMs,
+ aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData,
+ aidl2apiAudioFormat(aidlEvent.common.audioConfig), aidlEvent.common.data,
+ apiExtras);
+ }
+
+ public static AudioFormat aidl2apiAudioFormat(AudioConfig audioConfig) {
+ AudioFormat.Builder apiBuilder = new AudioFormat.Builder();
+ apiBuilder.setSampleRate(audioConfig.sampleRateHz);
+ apiBuilder.setChannelMask(aidl2apiChannelInMask(audioConfig.channelMask));
+ apiBuilder.setEncoding(aidl2apiEncoding(audioConfig.format));
+ return apiBuilder.build();
+ }
+
+ public static int aidl2apiEncoding(int aidlFormat) {
+ switch (aidlFormat) {
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_16_BIT:
+ return AudioFormat.ENCODING_PCM_16BIT;
+
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_8_BIT:
+ return AudioFormat.ENCODING_PCM_8BIT;
+
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_FLOAT:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_8_24_BIT:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_24_BIT_PACKED:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_32_BIT:
+ return AudioFormat.ENCODING_PCM_FLOAT;
+
+ case android.media.audio.common.AudioFormat.AC3:
+ return AudioFormat.ENCODING_AC3;
+
+ case android.media.audio.common.AudioFormat.E_AC3:
+ return AudioFormat.ENCODING_E_AC3;
+
+ case android.media.audio.common.AudioFormat.DTS:
+ return AudioFormat.ENCODING_DTS;
+
+ case android.media.audio.common.AudioFormat.DTS_HD:
+ return AudioFormat.ENCODING_DTS_HD;
+
+ case android.media.audio.common.AudioFormat.MP3:
+ return AudioFormat.ENCODING_MP3;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_LC:
+ return AudioFormat.ENCODING_AAC_LC;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_HE_V1:
+ return AudioFormat.ENCODING_AAC_HE_V1;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_HE_V2:
+ return AudioFormat.ENCODING_AAC_HE_V2;
+
+ case android.media.audio.common.AudioFormat.IEC61937:
+ return AudioFormat.ENCODING_IEC61937;
+
+ case android.media.audio.common.AudioFormat.DOLBY_TRUEHD:
+ return AudioFormat.ENCODING_DOLBY_TRUEHD;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_ELD:
+ return AudioFormat.ENCODING_AAC_ELD;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_XHE:
+ return AudioFormat.ENCODING_AAC_XHE;
+
+ case android.media.audio.common.AudioFormat.AC4:
+ return AudioFormat.ENCODING_AC4;
+
+ case android.media.audio.common.AudioFormat.E_AC3
+ | android.media.audio.common.AudioFormat.E_AC3_SUB_JOC:
+ return AudioFormat.ENCODING_E_AC3_JOC;
+
+ case android.media.audio.common.AudioFormat.MAT:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_1_0:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_2_0:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_2_1:
+ return AudioFormat.ENCODING_DOLBY_MAT;
+
+ case android.media.audio.common.AudioFormat.DEFAULT:
+ return AudioFormat.ENCODING_DEFAULT;
+
+ default:
+ return AudioFormat.ENCODING_INVALID;
+ }
+ }
+
+ public static int api2aidlModelParameter(int apiParam) {
+ switch (apiParam) {
+ case ModelParams.THRESHOLD_FACTOR:
+ return android.media.soundtrigger_middleware.ModelParameter.THRESHOLD_FACTOR;
+ default:
+ return android.media.soundtrigger_middleware.ModelParameter.INVALID;
+ }
+ }
+
+ public static int aidl2apiChannelInMask(int aidlMask) {
+ // We're assuming AudioFormat.CHANNEL_IN_* constants are kept in sync with
+ // android.media.audio.common.AudioChannelMask.
+ return aidlMask;
+ }
+
+ public static SoundTrigger.ModelParamRange aidl2apiModelParameterRange(
+ @Nullable ModelParameterRange aidlRange) {
+ if (aidlRange == null) {
+ return null;
+ }
+ return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 86f3eec..5484df4 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -22,18 +22,29 @@
import static android.system.OsConstants.EPERM;
import static android.system.OsConstants.EPIPE;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.content.Context;
import android.media.AudioFormat;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.UUID;
/**
@@ -44,6 +55,7 @@
*/
@SystemApi
public class SoundTrigger {
+ private static final String TAG = "SoundTrigger";
private SoundTrigger() {
}
@@ -119,15 +131,15 @@
* recognition callback event */
public final boolean returnsTriggerInEvent;
- ModuleProperties(int id, String implementor, String description,
- String uuid, int version, int maxSoundModels, int maxKeyphrases,
+ ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
+ @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
int maxBufferMs, boolean supportsConcurrentCapture,
int powerConsumptionMw, boolean returnsTriggerInEvent) {
this.id = id;
- this.implementor = implementor;
- this.description = description;
- this.uuid = UUID.fromString(uuid);
+ this.implementor = requireNonNull(implementor);
+ this.description = requireNonNull(description);
+ this.uuid = UUID.fromString(requireNonNull(uuid));
this.version = version;
this.maxSoundModels = maxSoundModels;
this.maxKeyphrases = maxKeyphrases;
@@ -231,6 +243,7 @@
/** Unique sound model identifier */
@UnsupportedAppUsage
+ @NonNull
public final UUID uuid;
/** Sound model type (e.g. TYPE_KEYPHRASE); */
@@ -238,17 +251,20 @@
/** Unique sound model vendor identifier */
@UnsupportedAppUsage
+ @NonNull
public final UUID vendorUuid;
/** Opaque data. For use by vendor implementation and enrollment application */
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
- public SoundModel(UUID uuid, UUID vendorUuid, int type, byte[] data) {
- this.uuid = uuid;
- this.vendorUuid = vendorUuid;
+ public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
+ @Nullable byte[] data) {
+ this.uuid = requireNonNull(uuid);
+ this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
this.type = type;
- this.data = data;
+ this.data = data != null ? data : new byte[0];
}
@Override
@@ -271,8 +287,6 @@
if (!(obj instanceof SoundModel))
return false;
SoundModel other = (SoundModel) obj;
- if (!Arrays.equals(data, other.data))
- return false;
if (type != other.type)
return false;
if (uuid == null) {
@@ -285,6 +299,8 @@
return false;
} else if (!vendorUuid.equals(other.vendorUuid))
return false;
+ if (!Arrays.equals(data, other.data))
+ return false;
return true;
}
}
@@ -306,24 +322,28 @@
/** Locale of the keyphrase. JAVA Locale string e.g en_US */
@UnsupportedAppUsage
+ @NonNull
public final String locale;
/** Key phrase text */
@UnsupportedAppUsage
+ @NonNull
public final String text;
/** Users this key phrase has been trained for. countains sound trigger specific user IDs
* derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. */
@UnsupportedAppUsage
+ @NonNull
public final int[] users;
@UnsupportedAppUsage
- public Keyphrase(int id, int recognitionModes, String locale, String text, int[] users) {
+ public Keyphrase(int id, int recognitionModes, @NonNull String locale, @NonNull String text,
+ @Nullable int[] users) {
this.id = id;
this.recognitionModes = recognitionModes;
- this.locale = locale;
- this.text = text;
- this.users = users;
+ this.locale = requireNonNull(locale);
+ this.text = requireNonNull(text);
+ this.users = users != null ? users : new int[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<Keyphrase> CREATOR
@@ -427,13 +447,15 @@
public static class KeyphraseSoundModel extends SoundModel implements Parcelable {
/** Key phrases in this sound model */
@UnsupportedAppUsage
+ @NonNull
public final Keyphrase[] keyphrases; // keyword phrases in model
@UnsupportedAppUsage
public KeyphraseSoundModel(
- UUID uuid, UUID vendorUuid, byte[] data, Keyphrase[] keyphrases) {
+ @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
+ @Nullable Keyphrase[] keyphrases) {
super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
- this.keyphrases = keyphrases;
+ this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
@@ -528,7 +550,8 @@
};
@UnsupportedAppUsage
- public GenericSoundModel(UUID uuid, UUID vendorUuid, byte[] data) {
+ public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data) {
super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
}
@@ -648,6 +671,12 @@
* @hide
*/
public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4;
+ /**
+ * Generic (non-speech) recognition.
+ *
+ * @hide
+ */
+ public static final int RECOGNITION_MODE_GENERIC = 0x8;
/**
* Status codes for {@link RecognitionEvent}
@@ -739,6 +768,7 @@
*
* @hide
*/
+ @NonNull
public final AudioFormat captureFormat;
/**
* Opaque data for use by system applications who know about voice engine internals,
@@ -747,13 +777,14 @@
* @hide
*/
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
/** @hide */
@UnsupportedAppUsage
public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, AudioFormat captureFormat, byte[] data) {
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
this.captureAvailable = captureAvailable;
@@ -761,8 +792,8 @@
this.captureDelayMs = captureDelayMs;
this.capturePreambleMs = capturePreambleMs;
this.triggerInData = triggerInData;
- this.captureFormat = captureFormat;
- this.data = data;
+ this.captureFormat = requireNonNull(captureFormat);
+ this.data = data != null ? data : new byte[0];
}
/**
@@ -965,19 +996,21 @@
/** List of all keyphrases in the sound model for which recognition should be performed with
* options for each keyphrase. */
@UnsupportedAppUsage
+ @NonNull
public final KeyphraseRecognitionExtra keyphrases[];
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
@UnsupportedAppUsage
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- KeyphraseRecognitionExtra[] keyphrases, byte[] data) {
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
- this.keyphrases = keyphrases;
- this.data = data;
+ this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
+ this.data = data != null ? data : new byte[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1126,15 +1159,17 @@
/** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
* be recognized (RecognitionConfig) */
@UnsupportedAppUsage
+ @NonNull
public final ConfidenceLevel[] confidenceLevels;
@UnsupportedAppUsage
public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
- ConfidenceLevel[] confidenceLevels) {
+ @Nullable ConfidenceLevel[] confidenceLevels) {
this.id = id;
this.recognitionModes = recognitionModes;
this.coarseConfidenceLevel = coarseConfidenceLevel;
- this.confidenceLevels = confidenceLevels;
+ this.confidenceLevels =
+ confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
@@ -1217,16 +1252,18 @@
public static class KeyphraseRecognitionEvent extends RecognitionEvent implements Parcelable {
/** Indicates if the key phrase is present in the buffered audio available for capture */
@UnsupportedAppUsage
+ @NonNull
public final KeyphraseRecognitionExtra[] keyphraseExtras;
@UnsupportedAppUsage
public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, AudioFormat captureFormat, byte[] data,
- KeyphraseRecognitionExtra[] keyphraseExtras) {
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+ @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
capturePreambleMs, triggerInData, captureFormat, data);
- this.keyphraseExtras = keyphraseExtras;
+ this.keyphraseExtras =
+ keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
@@ -1343,8 +1380,8 @@
@UnsupportedAppUsage
public GenericRecognitionEvent(int status, int soundModelHandle,
boolean captureAvailable, int captureSession, int captureDelayMs,
- int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat,
- byte[] data) {
+ int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
+ @Nullable byte[] data) {
super(status, soundModelHandle, captureAvailable, captureSession,
captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
data);
@@ -1408,13 +1445,14 @@
/** The updated sound model handle */
public final int soundModelHandle;
/** New sound model data */
+ @NonNull
public final byte[] data;
@UnsupportedAppUsage
- SoundModelEvent(int status, int soundModelHandle, byte[] data) {
+ SoundModelEvent(int status, int soundModelHandle, @Nullable byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
- this.data = data;
+ this.data = data != null ? data : new byte[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<SoundModelEvent> CREATOR
@@ -1498,8 +1536,9 @@
* @hide
*/
public static final int SERVICE_STATE_DISABLED = 1;
-
- /**
+ private static Object mServiceLock = new Object();
+ private static ISoundTriggerMiddlewareService mService;
+ /**
* @return returns current package name.
*/
static String getCurrentOpPackageName() {
@@ -1523,25 +1562,22 @@
* @hide
*/
@UnsupportedAppUsage
- public static int listModules(ArrayList<ModuleProperties> modules) {
- return listModules(getCurrentOpPackageName(), modules);
+ public static int listModules(@NonNull ArrayList<ModuleProperties> modules) {
+ try {
+ SoundTriggerModuleDescriptor[] descs = getService().listModules();
+ modules.clear();
+ modules.ensureCapacity(descs.length);
+ for (SoundTriggerModuleDescriptor desc : descs) {
+ modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
+ }
+ return STATUS_OK;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception caught", e);
+ return STATUS_DEAD_OBJECT;
+ }
}
/**
- * Returns a list of descriptors for all hardware modules loaded.
- * @param opPackageName
- * @param modules A ModuleProperties array where the list will be returned.
- * @return - {@link #STATUS_OK} in case of success
- * - {@link #STATUS_ERROR} in case of unspecified error
- * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
- * - {@link #STATUS_NO_INIT} if the native service cannot be reached
- * - {@link #STATUS_BAD_VALUE} if modules is null
- * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
- */
- private static native int listModules(String opPackageName,
- ArrayList<ModuleProperties> modules);
-
- /**
* Get an interface on a hardware module to control sound models and recognition on
* this module.
* @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory.
@@ -1553,14 +1589,40 @@
* @hide
*/
@UnsupportedAppUsage
- public static SoundTriggerModule attachModule(int moduleId,
- StatusListener listener,
- Handler handler) {
- if (listener == null) {
+ public static @NonNull SoundTriggerModule attachModule(int moduleId,
+ @NonNull StatusListener listener,
+ @Nullable Handler handler) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
+ try {
+ return new SoundTriggerModule(getService(), moduleId, listener, looper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
return null;
}
- SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler);
- return module;
+ }
+
+ private static ISoundTriggerMiddlewareService getService() {
+ synchronized (mServiceLock) {
+ while (true) {
+ IBinder binder = null;
+ try {
+ binder =
+ ServiceManager.getServiceOrThrow(
+ Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
+ binder.linkToDeath(() -> {
+ synchronized (mServiceLock) {
+ mService = null;
+ }
+ }, 0);
+ mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
+ break;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to bind to soundtrigger service", e);
+ }
+ }
+ return mService;
+ }
+
}
/**
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index b16ef5c..7cf5600 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -16,14 +16,23 @@
package android.hardware.soundtrigger;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
-import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.SoundModel;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-
-import java.lang.ref.WeakReference;
+import android.os.RemoteException;
+import android.util.Log;
/**
* The SoundTriggerModule provides APIs to control sound models and sound detection
@@ -32,39 +41,47 @@
* @hide
*/
public class SoundTriggerModule {
- @UnsupportedAppUsage
- private long mNativeContext;
+ private static final String TAG = "SoundTriggerModule";
- @UnsupportedAppUsage
- private int mId;
- private NativeEventHandlerDelegate mEventHandlerDelegate;
-
- // to be kept in sync with core/jni/android_hardware_SoundTrigger.cpp
private static final int EVENT_RECOGNITION = 1;
private static final int EVENT_SERVICE_DIED = 2;
- private static final int EVENT_SOUNDMODEL = 3;
- private static final int EVENT_SERVICE_STATE_CHANGE = 4;
+ private static final int EVENT_SERVICE_STATE_CHANGE = 3;
+ @UnsupportedAppUsage
+ private int mId;
+ private EventHandlerDelegate mEventHandlerDelegate;
+ private ISoundTriggerModule mService;
- SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
+ SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
+ int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper)
+ throws RemoteException {
mId = moduleId;
- mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler);
- native_setup(SoundTrigger.getCurrentOpPackageName(),
- new WeakReference<SoundTriggerModule>(this));
+ mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
+ mService = service.attach(moduleId, mEventHandlerDelegate);
+ mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
}
- private native void native_setup(String opPackageName, Object moduleThis);
@Override
protected void finalize() {
- native_finalize();
+ detach();
}
- private native void native_finalize();
/**
* Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called
* anymore and associated resources will be released.
- * */
+ * All models must have been unloaded prior to detaching.
+ */
@UnsupportedAppUsage
- public native void detach();
+ public synchronized void detach() {
+ try {
+ if (mService != null) {
+ mService.asBinder().unlinkToDeath(mEventHandlerDelegate, 0);
+ mService.detach();
+ mService = null;
+ }
+ } catch (Exception e) {
+ handleException(e);
+ }
+ }
/**
* Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in
@@ -82,7 +99,26 @@
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int loadSoundModel(SoundTrigger.SoundModel model, int[] soundModelHandle);
+ public synchronized int loadSoundModel(@NonNull SoundTrigger.SoundModel model,
+ @NonNull int[] soundModelHandle) {
+ try {
+ if (model instanceof SoundTrigger.GenericSoundModel) {
+ SoundModel aidlModel = ConversionUtil.api2aidlGenericSoundModel(
+ (SoundTrigger.GenericSoundModel) model);
+ soundModelHandle[0] = mService.loadModel(aidlModel);
+ return SoundTrigger.STATUS_OK;
+ }
+ if (model instanceof SoundTrigger.KeyphraseSoundModel) {
+ PhraseSoundModel aidlModel = ConversionUtil.api2aidlPhraseSoundModel(
+ (SoundTrigger.KeyphraseSoundModel) model);
+ soundModelHandle[0] = mService.loadPhraseModel(aidlModel);
+ return SoundTrigger.STATUS_OK;
+ }
+ return SoundTrigger.STATUS_BAD_VALUE;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition
@@ -97,7 +133,14 @@
* service fails
*/
@UnsupportedAppUsage
- public native int unloadSoundModel(int soundModelHandle);
+ public synchronized int unloadSoundModel(int soundModelHandle) {
+ try {
+ mService.unloadModel(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Start listening to all key phrases in a {@link SoundTrigger.SoundModel}.
@@ -117,7 +160,16 @@
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int startRecognition(int soundModelHandle, SoundTrigger.RecognitionConfig config);
+ public synchronized int startRecognition(int soundModelHandle,
+ SoundTrigger.RecognitionConfig config) {
+ try {
+ mService.startRecognition(soundModelHandle,
+ ConversionUtil.api2aidlRecognitionConfig(config));
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Stop listening to all key phrases in a {@link SoundTrigger.SoundModel}
@@ -133,12 +185,20 @@
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int stopRecognition(int soundModelHandle);
+ public synchronized int stopRecognition(int soundModelHandle) {
+ try {
+ mService.stopRecognition(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Get the current state of a {@link SoundTrigger.SoundModel}.
- * The state will be returned asynchronously as a {@link SoundTrigger#RecognitionEvent}
- * in the callback registered in the {@link SoundTrigger.startRecognition} method.
+ * The state will be returned asynchronously as a {@link SoundTrigger.RecognitionEvent}
+ * in the callback registered in the
+ * {@link SoundTrigger#attachModule(int, SoundTrigger.StatusListener, Handler)} method.
* @param soundModelHandle The sound model handle indicating which model's state to return
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
@@ -150,46 +210,71 @@
* service fails
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
- public native int getModelState(int soundModelHandle);
+ public synchronized int getModelState(int soundModelHandle) {
+ try {
+ mService.forceRecognitionEvent(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Set a model specific {@link ModelParams} with the given value. This
- * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * parameter will keep its value for the duration the model is loaded regardless of starting
+ * and
* stopping recognition. Once the model is unloaded, the value will be lost.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first before calling this
- * method.
+ * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before calling
+ * this method.
*
* @param soundModelHandle handle of model to apply parameter
- * @param modelParam {@link ModelParams}
- * @param value Value to set
+ * @param modelParam {@link ModelParams}
+ * @param value Value to set
* @return - {@link SoundTrigger#STATUS_OK} in case of success
- * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
- * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
- * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
- * if API is not supported by HAL
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
*/
- public native int setParameter(int soundModelHandle,
- @ModelParams int modelParam, int value);
+ public synchronized int setParameter(int soundModelHandle, @ModelParams int modelParam,
+ int value) {
+ try {
+ mService.setModelParameter(soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam), value);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Get a model specific {@link ModelParams}. This parameter will keep its value
* for the duration the model is loaded regardless of starting and stopping recognition.
* Once the model is unloaded, the value will be lost. If the value is not set, a default
* value is returned. See {@link ModelParams} for parameter default values.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first before
+ * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before
* calling this method. Otherwise, an exception can be thrown.
*
* @param soundModelHandle handle of model to get parameter
- * @param modelParam {@link ModelParams}
+ * @param modelParam {@link ModelParams}
* @return value of parameter
* @throws UnsupportedOperationException if hal or model do not support this API.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first.
- * @throws IllegalArgumentException if invalid model handle or parameter is passed.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first.
+ * {@link SoundTriggerModule#queryParameter(int, int)}
+ * should
+ * be checked first.
+ * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+ * {@link SoundTriggerModule#queryParameter(int, int)}
+ * should be checked first.
*/
- public native int getParameter(int soundModelHandle,
- @ModelParams int modelParam)
- throws UnsupportedOperationException, IllegalArgumentException;
+ public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ try {
+ return mService.getModelParameter(soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Determine if parameter control is supported for the given model handle.
@@ -197,85 +282,98 @@
* {@link SoundTriggerModule#getParameter}.
*
* @param soundModelHandle handle of model to get parameter
- * @param modelParam {@link ModelParams}
+ * @param modelParam {@link ModelParams}
* @return supported range of parameter, null if not supported
*/
@Nullable
- public native ModelParamRange queryParameter(int soundModelHandle, @ModelParams int modelParam);
-
- private class NativeEventHandlerDelegate {
- private final Handler mHandler;
-
- NativeEventHandlerDelegate(final SoundTrigger.StatusListener listener,
- Handler handler) {
- // find the looper for our new event handler
- Looper looper;
- if (handler != null) {
- looper = handler.getLooper();
- } else {
- looper = Looper.getMainLooper();
- }
-
- // construct the event handler with this looper
- if (looper != null) {
- // implement the event handler delegate
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case EVENT_RECOGNITION:
- if (listener != null) {
- listener.onRecognition(
- (SoundTrigger.RecognitionEvent)msg.obj);
- }
- break;
- case EVENT_SOUNDMODEL:
- if (listener != null) {
- listener.onSoundModelUpdate(
- (SoundTrigger.SoundModelEvent)msg.obj);
- }
- break;
- case EVENT_SERVICE_STATE_CHANGE:
- if (listener != null) {
- listener.onServiceStateChange(msg.arg1);
- }
- break;
- case EVENT_SERVICE_DIED:
- if (listener != null) {
- listener.onServiceDied();
- }
- break;
- default:
- break;
- }
- }
- };
- } else {
- mHandler = null;
- }
- }
-
- Handler handler() {
- return mHandler;
+ public synchronized SoundTrigger.ModelParamRange queryParameter(int soundModelHandle,
+ @ModelParams int modelParam) {
+ try {
+ return ConversionUtil.aidl2apiModelParameterRange(mService.queryModelParameterSupport(
+ soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam)));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
- @SuppressWarnings("unused")
- @UnsupportedAppUsage
- private static void postEventFromNative(Object module_ref,
- int what, int arg1, int arg2, Object obj) {
- SoundTriggerModule module = (SoundTriggerModule)((WeakReference)module_ref).get();
- if (module == null) {
- return;
+ private int handleException(Exception e) {
+ Log.e(TAG, "", e);
+ if (e instanceof NullPointerException) {
+ return SoundTrigger.STATUS_NO_INIT;
+ }
+ if (e instanceof RemoteException) {
+ return SoundTrigger.STATUS_DEAD_OBJECT;
+ }
+ if (e instanceof IllegalArgumentException) {
+ return SoundTrigger.STATUS_BAD_VALUE;
+ }
+ if (e instanceof IllegalStateException) {
+ return SoundTrigger.STATUS_INVALID_OPERATION;
+ }
+ return SoundTrigger.STATUS_ERROR;
+ }
+
+ private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
+ IBinder.DeathRecipient {
+ private final Handler mHandler;
+
+ EventHandlerDelegate(@NonNull final SoundTrigger.StatusListener listener,
+ @NonNull Looper looper) {
+
+ // construct the event handler with this looper
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RECOGNITION:
+ listener.onRecognition(
+ (SoundTrigger.RecognitionEvent) msg.obj);
+ break;
+ case EVENT_SERVICE_STATE_CHANGE:
+ listener.onServiceStateChange(msg.arg1);
+ break;
+ case EVENT_SERVICE_DIED:
+ listener.onServiceDied();
+ break;
+ default:
+ Log.e(TAG, "Unknown message: " + msg.toString());
+ break;
+ }
+ }
+ };
}
- NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate;
- if (delegate != null) {
- Handler handler = delegate.handler();
- if (handler != null) {
- Message m = handler.obtainMessage(what, arg1, arg2, obj);
- handler.sendMessage(m);
- }
+ @Override
+ public synchronized void onRecognition(int handle, RecognitionEvent event)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+ ConversionUtil.aidl2apiRecognitionEvent(handle, event));
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+ ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, event));
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ public synchronized void onRecognitionAvailabilityChange(boolean available)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_STATE_CHANGE,
+ available ? SoundTrigger.SERVICE_STATE_ENABLED
+ : SoundTrigger.SERVICE_STATE_DISABLED);
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ public synchronized void binderDied() {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+ mHandler.sendMessage(m);
}
}
}
diff --git a/core/java/android/net/InvalidPacketException.java b/core/java/android/net/InvalidPacketException.java
new file mode 100644
index 0000000..909998d
--- /dev/null
+++ b/core/java/android/net/InvalidPacketException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Thrown when a packet is invalid.
+ * @hide
+ */
+@SystemApi
+public class InvalidPacketException extends Exception {
+ public final int error;
+
+ // Must match SocketKeepalive#ERROR_INVALID_IP_ADDRESS.
+ /** Invalid IP address. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+
+ // Must match SocketKeepalive#ERROR_INVALID_PORT.
+ /** Invalid port number. */
+ public static final int ERROR_INVALID_PORT = -22;
+
+ // Must match SocketKeepalive#ERROR_INVALID_LENGTH.
+ /** Invalid packet length. */
+ public static final int ERROR_INVALID_LENGTH = -23;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_IP_ADDRESS,
+ ERROR_INVALID_PORT,
+ ERROR_INVALID_LENGTH
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * This packet is invalid.
+ * See the error code for details.
+ */
+ public InvalidPacketException(@ErrorCode final int error) {
+ this.error = error;
+ }
+}
diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java
index 9b8b732..2b8b7e6 100644
--- a/core/java/android/net/KeepalivePacketData.java
+++ b/core/java/android/net/KeepalivePacketData.java
@@ -16,13 +16,13 @@
package android.net;
-import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
-import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS;
+import static android.net.InvalidPacketException.ERROR_INVALID_PORT;
-import android.net.SocketKeepalive.InvalidPacketException;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.net.util.IpUtils;
import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import java.net.InetAddress;
@@ -33,13 +33,16 @@
*
* @hide
*/
-public class KeepalivePacketData implements Parcelable {
+@SystemApi
+public class KeepalivePacketData {
private static final String TAG = "KeepalivePacketData";
/** Source IP address */
+ @NonNull
public final InetAddress srcAddress;
/** Destination IP address */
+ @NonNull
public final InetAddress dstAddress;
/** Source port */
@@ -51,13 +54,14 @@
/** Packet data. A raw byte string of packet data, not including the link-layer header. */
private final byte[] mPacket;
- protected static final int IPV4_HEADER_LENGTH = 20;
- protected static final int UDP_HEADER_LENGTH = 8;
-
// This should only be constructed via static factory methods, such as
- // nattKeepalivePacket
- protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
- InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
+ // nattKeepalivePacket.
+ /**
+ * A holding class for data necessary to build a keepalive packet.
+ */
+ protected KeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort,
+ @NonNull InetAddress dstAddress, int dstPort,
+ @NonNull byte[] data) throws InvalidPacketException {
this.srcAddress = srcAddress;
this.dstAddress = dstAddress;
this.srcPort = srcPort;
@@ -78,16 +82,12 @@
}
}
+ @NonNull
public byte[] getPacket() {
return mPacket.clone();
}
- /* Parcelable Implementation */
- public int describeContents() {
- return 0;
- }
-
- /** Write to parcel */
+ /** @hide */
public void writeToParcel(Parcel out, int flags) {
out.writeString(srcAddress.getHostAddress());
out.writeString(dstAddress.getHostAddress());
@@ -96,6 +96,7 @@
out.writeByteArray(mPacket);
}
+ /** @hide */
protected KeepalivePacketData(Parcel in) {
srcAddress = NetworkUtils.numericToInetAddress(in.readString());
dstAddress = NetworkUtils.numericToInetAddress(in.readString());
@@ -103,17 +104,4 @@
dstPort = in.readInt();
mPacket = in.createByteArray();
}
-
- /** Parcelable Creator */
- public static final @android.annotation.NonNull Parcelable.Creator<KeepalivePacketData> CREATOR =
- new Parcelable.Creator<KeepalivePacketData>() {
- public KeepalivePacketData createFromParcel(Parcel in) {
- return new KeepalivePacketData(in);
- }
-
- public KeepalivePacketData[] newArray(int size) {
- return new KeepalivePacketData[size];
- }
- };
-
}
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
index a77c244..3fb52f1 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -19,7 +19,6 @@
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
-import android.net.SocketKeepalive.InvalidPacketException;
import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +31,9 @@
/** @hide */
public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
+ private static final int IPV4_HEADER_LENGTH = 20;
+ private static final int UDP_HEADER_LENGTH = 8;
+
// This should only be constructed via static factory methods, such as
// nattKeepalivePacket
private NattKeepalivePacketData(InetAddress srcAddress, int srcPort,
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index ec73866..fb224fb 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -147,17 +147,6 @@
}
}
- /**
- * This packet is invalid.
- * See the error code for details.
- * @hide
- */
- public static class InvalidPacketException extends ErrorCodeException {
- public InvalidPacketException(final int error) {
- super(error);
- }
- }
-
@NonNull final IConnectivityManager mService;
@NonNull final Network mNetwork;
@NonNull final ParcelFileDescriptor mPfd;
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 12bce8a..ed980f3 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -182,6 +182,14 @@
public static final int MAX_IPC_SIZE = 64 * 1024;
/**
+ * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction
+ * buffer limit.
+ */
+ static int getSuggestedMaxIpcSizeBytes() {
+ return MAX_IPC_SIZE;
+ }
+
+ /**
* Get the canonical name of the interface supported by this binder.
*/
public @Nullable String getInterfaceDescriptor() throws RemoteException;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 9eb6445..339397b 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1815,8 +1815,12 @@
p.writeToParcel(this, parcelableFlags);
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * Flatten the name of the class of the Parcelable into this Parcel.
+ *
+ * @param p The Parcelable object to be written.
+ * @see #readParcelableCreator
+ */
public final void writeParcelableCreator(@NonNull Parcelable p) {
String name = p.getClass().getName();
writeString(name);
@@ -3011,8 +3015,19 @@
return (T) creator.createFromParcel(this);
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * Read and return a Parcelable.Creator from the parcel. The given class loader will be used to
+ * load the {@link Parcelable.Creator}. If it is null, the default class loader will be used.
+ *
+ * @param loader A ClassLoader from which to instantiate the {@link Parcelable.Creator}
+ * object, or null for the default class loader.
+ * @return the previously written {@link Parcelable.Creator}, or null if a null Creator was
+ * written.
+ * @throws BadParcelableException Throws BadParcelableException if there was an error trying to
+ * read the {@link Parcelable.Creator}.
+ *
+ * @see #writeParcelableCreator
+ */
@Nullable
public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
String name = readString();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 725e0fb..5e478b5 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -44,70 +44,10 @@
* <p>
* <b>Device battery life will be significantly affected by the use of this API.</b>
* Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
- * possible, and be sure to release them as soon as possible.
- * </p><p>
- * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
- * This will create a {@link PowerManager.WakeLock} object. You can then use methods
- * on the wake lock object to control the power state of the device.
- * </p><p>
- * In practice it's quite simple:
- * {@samplecode
- * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
- * wl.acquire();
- * ..screen will stay on during this section..
- * wl.release();
- * }
- * </p><p>
- * The following wake lock levels are defined, with varying effects on system power.
- * <i>These levels are mutually exclusive - you may only specify one of them.</i>
+ * possible, and be sure to release them as soon as possible. In most cases,
+ * you'll want to use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
*
- * <table>
- * <tr><th>Flag Value</th>
- * <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
- *
- * <tr><td>{@link #PARTIAL_WAKE_LOCK}</td>
- * <td>On*</td> <td>Off</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #SCREEN_DIM_WAKE_LOCK}</td>
- * <td>On</td> <td>Dim</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #SCREEN_BRIGHT_WAKE_LOCK}</td>
- * <td>On</td> <td>Bright</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #FULL_WAKE_LOCK}</td>
- * <td>On</td> <td>Bright</td> <td>Bright</td>
- * </tr>
- * </table>
- * </p><p>
- * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any
- * display timeouts or the state of the screen and even after the user presses the power button.
- * In all other wake locks, the CPU will run, but the user can still put the device to sleep
- * using the power button.</i>
- * </p><p>
- * In addition, you can add two more flags, which affect behavior of the screen only.
- * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i></p>
- *
- * <table>
- * <tr><th>Flag Value</th> <th>Description</th></tr>
- *
- * <tr><td>{@link #ACQUIRE_CAUSES_WAKEUP}</td>
- * <td>Normal wake locks don't actually turn on the illumination. Instead, they cause
- * the illumination to remain on once it turns on (e.g. from user activity). This flag
- * will force the screen and/or keyboard to turn on immediately, when the WakeLock is
- * acquired. A typical use would be for notifications which are important for the user to
- * see immediately.</td>
- * </tr>
- *
- * <tr><td>{@link #ON_AFTER_RELEASE}</td>
- * <td>If this flag is set, the user activity timer will be reset when the WakeLock is
- * released, causing the illumination to remain on a bit longer. This can be used to
- * reduce flicker if you are cycling between wake lock conditions.</td>
- * </tr>
- * </table>
* <p>
* Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
* permission in an {@code <uses-permission>} element of the application's manifest.
@@ -931,7 +871,8 @@
* {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK}
* and {@link #SCREEN_BRIGHT_WAKE_LOCK}. Exactly one wake lock level must be
* specified as part of the {@code levelAndFlags} parameter.
- * </p><p>
+ * </p>
+ * <p>
* The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP}
* and {@link #ON_AFTER_RELEASE}. Multiple flags can be combined as part of the
* {@code levelAndFlags} parameters.
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index eb1684f..c86c83c 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -2381,7 +2381,11 @@
* This id is provided by its own data source, and can be used to backup metadata
* to the server.
* This should be unique within each set of account_name/account_type/data_set
+ *
+ * @deprecated This column is no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
public static final String BACKUP_ID = "backup_id";
/**
@@ -2445,7 +2449,11 @@
* Flag indicating that a raw contact's metadata has changed, and its metadata
* needs to be synchronized by the server.
* <P>Type: INTEGER (boolean)</P>
+ *
+ * @deprecated This column is no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
public static final String METADATA_DIRTY = "metadata_dirty";
}
@@ -4188,7 +4196,10 @@
* Hash id on the data fields, used for backup and restore.
*
* @hide
+ * @deprecated This column is no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
public static final String HASH_ID = "hash_id";
/**
@@ -9495,7 +9506,10 @@
/**
* @hide
+ * @deprecated These columns are no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
@SystemApi
protected interface MetadataSyncColumns {
@@ -9602,7 +9616,10 @@
* from server before it is merged into other CP2 tables.
*
* @hide
+ * @deprecated These columns are no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
@SystemApi
public static final class MetadataSync implements BaseColumns, MetadataSyncColumns {
@@ -9638,7 +9655,10 @@
/**
* @hide
+ * @deprecated These columns are no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
@SystemApi
protected interface MetadataSyncStateColumns {
@@ -9672,7 +9692,10 @@
* sync state for a set of accounts.
*
* @hide
+ * @deprecated These columns are no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
@SystemApi
public static final class MetadataSyncState implements BaseColumns, MetadataSyncStateColumns {
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 2fa3386..701ba91 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -32,6 +32,7 @@
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.AppGlobals;
+import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
@@ -86,6 +87,7 @@
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -162,6 +164,15 @@
/** {@hide} */
public static final String SUICIDE_CALL = "suicide";
+ /** {@hide} */
+ public static final String CREATE_WRITE_REQUEST_CALL = "create_write_request";
+ /** {@hide} */
+ public static final String CREATE_TRASH_REQUEST_CALL = "create_trash_request";
+ /** {@hide} */
+ public static final String CREATE_FAVORITE_REQUEST_CALL = "create_favorite_request";
+ /** {@hide} */
+ public static final String CREATE_DELETE_REQUEST_CALL = "create_delete_request";
+
/**
* Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that
* the file path originated from shell.
@@ -199,6 +210,13 @@
/** {@hide} */
public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media";
+ /** {@hide} */
+ public static final String EXTRA_CLIP_DATA = "clip_data";
+ /** {@hide} */
+ public static final String EXTRA_CONTENT_VALUES = "content_values";
+ /** {@hide} */
+ public static final String EXTRA_RESULT = "result";
+
/**
* This is for internal use by the media scanner only.
* Name of the (optional) Uri parameter that determines whether to skip deleting
@@ -606,6 +624,10 @@
* {@link ContentResolver#delete}.
* <p>
* By default, trashed items are filtered away from operations.
+ *
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#QUERY_ARG_MATCH_TRASHED
+ * @see MediaStore#createTrashRequest
*/
@Match
public static final String QUERY_ARG_MATCH_TRASHED = "android:query-arg-match-trashed";
@@ -620,6 +642,10 @@
* <p>
* By default, favorite items are <em>not</em> filtered away from
* operations.
+ *
+ * @see MediaColumns#IS_FAVORITE
+ * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+ * @see MediaStore#createFavoriteRequest
*/
@Match
public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
@@ -952,7 +978,9 @@
* @see MediaStore#setIncludeTrashed(Uri)
* @see MediaStore#trash(Context, Uri)
* @see MediaStore#untrash(Context, Uri)
+ * @removed
*/
+ @Deprecated
public static void trash(@NonNull Context context, @NonNull Uri uri) {
trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS);
}
@@ -970,7 +998,9 @@
* @see MediaStore#setIncludeTrashed(Uri)
* @see MediaStore#trash(Context, Uri)
* @see MediaStore#untrash(Context, Uri)
+ * @removed
*/
+ @Deprecated
public static void trash(@NonNull Context context, @NonNull Uri uri,
@DurationMillisLong long timeoutMillis) {
if (timeoutMillis < 0) {
@@ -992,7 +1022,9 @@
* @see MediaStore#setIncludeTrashed(Uri)
* @see MediaStore#trash(Context, Uri)
* @see MediaStore#untrash(Context, Uri)
+ * @removed
*/
+ @Deprecated
public static void untrash(@NonNull Context context, @NonNull Uri uri) {
final ContentValues values = new ContentValues();
values.put(MediaColumns.IS_TRASHED, 0);
@@ -1010,6 +1042,180 @@
return uri.buildUpon().authority(MediaStore.AUTHORITY_LEGACY).build();
}
+ private static @NonNull PendingIntent createRequest(@NonNull ContentResolver resolver,
+ @NonNull String method, @NonNull Collection<Uri> uris, @Nullable ContentValues values) {
+ Objects.requireNonNull(resolver);
+ Objects.requireNonNull(uris);
+
+ final Iterator<Uri> it = uris.iterator();
+ final ClipData clipData = ClipData.newRawUri(null, it.next());
+ while (it.hasNext()) {
+ clipData.addItem(new ClipData.Item(it.next()));
+ }
+
+ final Bundle extras = new Bundle();
+ extras.putParcelable(EXTRA_CLIP_DATA, clipData);
+ extras.putParcelable(EXTRA_CONTENT_VALUES, values);
+ return resolver.call(AUTHORITY, method, null, extras).getParcelable(EXTRA_RESULT);
+ }
+
+ /**
+ * Create a {@link PendingIntent} that will prompt the user to grant your
+ * app write access for the requested media items.
+ * <p>
+ * This call only generates the request for a prompt; to display the prompt,
+ * call {@link Activity#startIntentSenderForResult} with
+ * {@link PendingIntent#getIntentSender()}. You can then determine if the
+ * user granted your request by testing for {@link Activity#RESULT_OK} in
+ * {@link Activity#onActivityResult}.
+ * <p>
+ * Permissions granted through this mechanism are tied to the lifecycle of
+ * the {@link Activity} that requests them. If you need to retain
+ * longer-term access for background actions, you can place items into a
+ * {@link ClipData} or {@link Intent} which can then be passed to
+ * {@link Context#startService} or
+ * {@link android.app.job.JobInfo.Builder#setClipData}. Be sure to include
+ * any relevant access modes you want to retain, such as
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ * <p>
+ * The displayed prompt will reflect all the media items you're requesting,
+ * including those for which you already hold write access. If you want to
+ * determine if you already hold write access before requesting access, use
+ * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ * <p>
+ * For security and performance reasons this method does not support
+ * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} or
+ * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
+ *
+ * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+ * Typically this value is {@link Context#getContentResolver()},
+ * but if you need more explicit lifecycle controls, you can
+ * obtain a {@link ContentProviderClient} and wrap it using
+ * {@link ContentResolver#wrap(ContentProviderClient)}.
+ * @param uris The set of media items to include in this request. Each item
+ * must be hosted by {@link MediaStore#AUTHORITY} and must
+ * reference a specific media item by {@link BaseColumns#_ID}.
+ */
+ public static @NonNull PendingIntent createWriteRequest(@NonNull ContentResolver resolver,
+ @NonNull Collection<Uri> uris) {
+ return createRequest(resolver, CREATE_WRITE_REQUEST_CALL, uris, null);
+ }
+
+ /**
+ * Create a {@link PendingIntent} that will prompt the user to trash the
+ * requested media items. When the user approves this request,
+ * {@link MediaColumns#IS_TRASHED} is set on these items.
+ * <p>
+ * This call only generates the request for a prompt; to display the prompt,
+ * call {@link Activity#startIntentSenderForResult} with
+ * {@link PendingIntent#getIntentSender()}. You can then determine if the
+ * user granted your request by testing for {@link Activity#RESULT_OK} in
+ * {@link Activity#onActivityResult}.
+ * <p>
+ * The displayed prompt will reflect all the media items you're requesting,
+ * including those for which you already hold write access. If you want to
+ * determine if you already hold write access before requesting access, use
+ * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ *
+ * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+ * Typically this value is {@link Context#getContentResolver()},
+ * but if you need more explicit lifecycle controls, you can
+ * obtain a {@link ContentProviderClient} and wrap it using
+ * {@link ContentResolver#wrap(ContentProviderClient)}.
+ * @param uris The set of media items to include in this request. Each item
+ * must be hosted by {@link MediaStore#AUTHORITY} and must
+ * reference a specific media item by {@link BaseColumns#_ID}.
+ * @param value The {@link MediaColumns#IS_TRASHED} value to apply.
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#QUERY_ARG_MATCH_TRASHED
+ */
+ public static @NonNull PendingIntent createTrashRequest(@NonNull ContentResolver resolver,
+ @NonNull Collection<Uri> uris, boolean value) {
+ final ContentValues values = new ContentValues();
+ if (value) {
+ values.put(MediaColumns.IS_TRASHED, 1);
+ values.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + DateUtils.WEEK_IN_MILLIS) / 1000);
+ } else {
+ values.put(MediaColumns.IS_TRASHED, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ }
+ return createRequest(resolver, CREATE_TRASH_REQUEST_CALL, uris, values);
+ }
+
+ /**
+ * Create a {@link PendingIntent} that will prompt the user to favorite the
+ * requested media items. When the user approves this request,
+ * {@link MediaColumns#IS_FAVORITE} is set on these items.
+ * <p>
+ * This call only generates the request for a prompt; to display the prompt,
+ * call {@link Activity#startIntentSenderForResult} with
+ * {@link PendingIntent#getIntentSender()}. You can then determine if the
+ * user granted your request by testing for {@link Activity#RESULT_OK} in
+ * {@link Activity#onActivityResult}.
+ * <p>
+ * The displayed prompt will reflect all the media items you're requesting,
+ * including those for which you already hold write access. If you want to
+ * determine if you already hold write access before requesting access, use
+ * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ *
+ * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+ * Typically this value is {@link Context#getContentResolver()},
+ * but if you need more explicit lifecycle controls, you can
+ * obtain a {@link ContentProviderClient} and wrap it using
+ * {@link ContentResolver#wrap(ContentProviderClient)}.
+ * @param uris The set of media items to include in this request. Each item
+ * must be hosted by {@link MediaStore#AUTHORITY} and must
+ * reference a specific media item by {@link BaseColumns#_ID}.
+ * @param value The {@link MediaColumns#IS_FAVORITE} value to apply.
+ * @see MediaColumns#IS_FAVORITE
+ * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+ */
+ public static @NonNull PendingIntent createFavoriteRequest(@NonNull ContentResolver resolver,
+ @NonNull Collection<Uri> uris, boolean value) {
+ final ContentValues values = new ContentValues();
+ if (value) {
+ values.put(MediaColumns.IS_FAVORITE, 1);
+ } else {
+ values.put(MediaColumns.IS_FAVORITE, 0);
+ }
+ return createRequest(resolver, CREATE_FAVORITE_REQUEST_CALL, uris, values);
+ }
+
+ /**
+ * Create a {@link PendingIntent} that will prompt the user to permanently
+ * delete the requested media items. When the user approves this request,
+ * {@link ContentResolver#delete} will be called on these items.
+ * <p>
+ * This call only generates the request for a prompt; to display the prompt,
+ * call {@link Activity#startIntentSenderForResult} with
+ * {@link PendingIntent#getIntentSender()}. You can then determine if the
+ * user granted your request by testing for {@link Activity#RESULT_OK} in
+ * {@link Activity#onActivityResult}.
+ * <p>
+ * The displayed prompt will reflect all the media items you're requesting,
+ * including those for which you already hold write access. If you want to
+ * determine if you already hold write access before requesting access, use
+ * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ *
+ * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+ * Typically this value is {@link Context#getContentResolver()},
+ * but if you need more explicit lifecycle controls, you can
+ * obtain a {@link ContentProviderClient} and wrap it using
+ * {@link ContentResolver#wrap(ContentProviderClient)}.
+ * @param uris The set of media items to include in this request. Each item
+ * must be hosted by {@link MediaStore#AUTHORITY} and must
+ * reference a specific media item by {@link BaseColumns#_ID}.
+ */
+ public static @NonNull PendingIntent createDeleteRequest(@NonNull ContentResolver resolver,
+ @NonNull Collection<Uri> uris) {
+ return createRequest(resolver, CREATE_DELETE_REQUEST_CALL, uris, null);
+ }
+
/**
* Common media metadata columns.
*/
@@ -1127,9 +1333,9 @@
* Trashed items are retained until they expire as defined by
* {@link #DATE_EXPIRES}.
*
+ * @see MediaColumns#IS_TRASHED
* @see MediaStore#QUERY_ARG_MATCH_TRASHED
- * @see MediaStore#trash(Context, Uri)
- * @see MediaStore#untrash(Context, Uri)
+ * @see MediaStore#createTrashRequest
*/
@Column(Cursor.FIELD_TYPE_INTEGER)
public static final String IS_TRASHED = "is_trashed";
@@ -1302,7 +1508,9 @@
* Flag indicating if the media item has been marked as being a
* "favorite" by the user.
*
+ * @see MediaColumns#IS_FAVORITE
* @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+ * @see MediaStore#createFavoriteRequest
*/
@Column(Cursor.FIELD_TYPE_INTEGER)
public static final String IS_FAVORITE = "is_favorite";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 197d814..e73a74f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8392,6 +8392,20 @@
"navigation_mode";
/**
+ * Scale factor for the back gesture inset size on the left side of the screen.
+ * @hide
+ */
+ public static final String BACK_GESTURE_INSET_SCALE_LEFT =
+ "back_gesture_inset_scale_left";
+
+ /**
+ * Scale factor for the back gesture inset size on the right side of the screen.
+ * @hide
+ */
+ public static final String BACK_GESTURE_INSET_SCALE_RIGHT =
+ "back_gesture_inset_scale_right";
+
+ /**
* Controls whether aware is enabled.
* @hide
*/
@@ -8416,6 +8430,12 @@
public static final String PEOPLE_STRIP = "people_strip";
/**
+ * Controls if window magnification is enabled.
+ * @hide
+ */
+ public static final String WINDOW_MAGNIFICATION = "window_magnification";
+
+ /**
* Keys we no longer back up under the current schema, but want to continue to
* process when restoring historical backup datasets.
*
@@ -14229,6 +14249,20 @@
*/
public static final int ADD_WIFI_RESULT_ALREADY_EXISTS = 2;
+ /**
+ * Activity Action: Allows user to select current bug report handler.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_BUGREPORT_HANDLER_SETTINGS =
+ "android.settings.BUGREPORT_HANDLER_SETTINGS";
+
private static final String[] PM_WRITE_SETTINGS = {
android.Manifest.permission.WRITE_SETTINGS
};
diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java
index efb8923..f2cedbc 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -36,12 +36,6 @@
public final class Sandman {
private static final String TAG = "Sandman";
- // The component name of a special dock app that merely launches a dream.
- // We don't want to launch this app when docked because it causes an unnecessary
- // activity transition. We just want to start the dream.
- private static final ComponentName SOMNAMBULATOR_COMPONENT =
- new ComponentName("com.android.systemui", "com.android.systemui.Somnambulator");
-
// The sandman is eternal. No one instantiates him.
private Sandman() {
@@ -52,8 +46,11 @@
* False if we should dream instead, if appropriate.
*/
public static boolean shouldStartDockApp(Context context, Intent intent) {
+ final ComponentName somnambulatorComponent = ComponentName.unflattenFromString(
+ context.getResources().getString(
+ com.android.internal.R.string.config_somnambulatorComponent));
ComponentName name = intent.resolveActivity(context.getPackageManager());
- return name != null && !name.equals(SOMNAMBULATOR_COMPONENT);
+ return name != null && !name.equals(somnambulatorComponent);
}
/**
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 8ab687f..c84fbc7 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -124,6 +124,13 @@
public static final String KEY_IMPORTANCE = "key_importance";
/**
+ * Data type: float, a ranking score from 0 (lowest) to 1 (highest).
+ * Used to rank notifications inside that fall under the same classification (i.e. alerting,
+ * silenced).
+ */
+ public static final String KEY_RANKING_SCORE = "key_ranking_score";
+
+ /**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 85f13d5..c04ac59 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1895,7 +1895,8 @@
&& ((mSmartActions == null ? 0 : mSmartActions.size())
== (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
&& Objects.equals(mSmartReplies, other.mSmartReplies)
- && Objects.equals(mCanBubble, other.mCanBubble);
+ && Objects.equals(mCanBubble, other.mCanBubble)
+ && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive);
}
}
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index dd2586c..d0675ed 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -481,9 +481,12 @@
* as true on their TileService Manifest declaration, and will do nothing otherwise.
*/
public static final void requestListeningState(Context context, ComponentName component) {
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ context.getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
Intent intent = new Intent(ACTION_REQUEST_LISTENING);
intent.putExtra(Intent.EXTRA_COMPONENT_NAME, component);
- intent.setPackage("com.android.systemui");
+ intent.setPackage(sysuiComponent.getPackageName());
context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
}
}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 716a522..51a9c86 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -35,8 +35,8 @@
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IPhoneStateListener;
import dalvik.system.VMRuntime;
@@ -169,14 +169,6 @@
public static final int LISTEN_SIGNAL_STRENGTHS = 0x00000100;
/**
- * Listen for changes to OTASP mode.
- *
- * @see #onOtaspChanged
- * @hide
- */
- public static final int LISTEN_OTASP_CHANGED = 0x00000200;
-
- /**
* Listen for changes to observed cell info.
*
* @see #onCellInfoChanged
@@ -196,12 +188,13 @@
/**
* Listen for {@link PreciseDataConnectionState} on the data connection (cellular).
*
- * @see #onPreciseDataConnectionStateChanged
+ * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+ * or the calling app has carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * @hide
+ * @see #onPreciseDataConnectionStateChanged
*/
- @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
+ @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 0x00001000;
/**
@@ -624,29 +617,6 @@
// default implementation empty
}
-
- /**
- * The Over The Air Service Provisioning (OTASP) has changed on the registered subscription.
- * Note, the registration subId comes from {@link TelephonyManager} object which registers
- * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subId. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * Requires the READ_PHONE_STATE permission.
- * @param otaspMode is integer <code>OTASP_UNKNOWN=1<code>
- * means the value is currently unknown and the system should wait until
- * <code>OTASP_NEEDED=2<code> or <code>OTASP_NOT_NEEDED=3<code> is received before
- * making the decision to perform OTASP or not.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public void onOtaspChanged(int otaspMode) {
- // default implementation empty
- }
-
/**
* Callback invoked when a observed cell info has changed or new cells have been added
* or removed on the registered subscription.
@@ -719,8 +689,9 @@
}
/**
- * Callback invoked when data connection state changes with precise information
- * on the registered subscription.
+ * Callback providing update about the default/internet data connection on the registered
+ * subscription.
+ *
* Note, the registration subId comes from {@link TelephonyManager} object which registers
* PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
* If this TelephonyManager object was created with
@@ -728,12 +699,13 @@
* subId. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * @param dataConnectionState {@link PreciseDataConnectionState}
+ * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+ * or the calling app has carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * @hide
+ * @param dataConnectionState {@link PreciseDataConnectionState}
*/
- @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
+ @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
public void onPreciseDataConnectionStateChanged(
@NonNull PreciseDataConnectionState dataConnectionState) {
// default implementation empty
@@ -1042,11 +1014,21 @@
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> {
- psl.onDataConnectionStateChanged(state, networkType);
- psl.onDataConnectionStateChanged(state);
- }));
+ if (state == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> {
+ psl.onDataConnectionStateChanged(
+ TelephonyManager.DATA_CONNECTED, networkType);
+ psl.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED);
+ }));
+ } else {
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> {
+ psl.onDataConnectionStateChanged(state, networkType);
+ psl.onDataConnectionStateChanged(state);
+ }));
+ }
}
public void onDataActivity(int direction) {
@@ -1065,14 +1047,6 @@
() -> mExecutor.execute(() -> psl.onSignalStrengthsChanged(signalStrength)));
}
- public void onOtaspChanged(int otaspMode) {
- PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
- if (psl == null) return;
-
- Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> psl.onOtaspChanged(otaspMode)));
- }
-
public void onCellInfoChanged(List<CellInfo> cellInfo) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9d7b57b..f574160 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -21,17 +21,13 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DataFailureCause;
-import android.telephony.Annotation.DataState;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.PreciseCallStates;
import android.telephony.Annotation.RadioPowerState;
@@ -357,27 +353,18 @@
* @param subId for which data connection state changed.
* @param slotIndex for which data connections state changed. Can be derived from subId except
* when subId is invalid.
- * @param state latest data connection state, e.g,
- * @param isDataConnectivityPossible indicates if data is allowed
- * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
- * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
- * @param linkProperties {@link LinkProperties} associated with this data connection.
- * @param networkCapabilities {@link NetworkCapabilities} associated with this data connection.
- * @param networkType associated with this data connection.
- * @param roaming {@code true} indicates in roaming, {@false} otherwise.
- * @see TelephonyManager#DATA_DISCONNECTED
- * @see TelephonyManager#isDataConnectivityPossible()
+ * @param apnType the APN type that triggered this update
+ * @param preciseState the PreciseDataConnectionState
*
+ * @see android.telephony.PreciseDataConnection
+ * @see TelephonyManager#DATA_DISCONNECTED
* @hide
*/
- public void notifyDataConnectionForSubscriber(int slotIndex, int subId, @DataState int state,
- boolean isDataConnectivityPossible,
- @ApnType String apn, String apnType, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
+ public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
+ String apnType, PreciseDataConnectionState preciseState) {
try {
- sRegistry.notifyDataConnectionForSubscriber(slotIndex, subId, state,
- isDataConnectivityPossible,
- apn, apnType, linkProperties, networkCapabilities, networkType, roaming);
+ sRegistry.notifyDataConnectionForSubscriber(
+ slotIndex, subId, apnType, preciseState);
} catch (RemoteException ex) {
// system process is dead
}
@@ -600,22 +587,6 @@
}
/**
- * Notify over the air sim provisioning(OTASP) mode changed on certain subscription.
- *
- * @param subId for which otasp mode changed.
- * @param otaspMode latest mode for OTASP e.g, OTASP needed.
- *
- * @hide
- */
- public void notifyOtaspChanged(int subId, int otaspMode) {
- try {
- sRegistry.notifyOtaspChanged(subId, otaspMode);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- /**
* Notify precise call state changed on certain subscription, including foreground, background
* and ringcall states.
*
@@ -662,25 +633,6 @@
}
/**
- * Notify data connection failed on certain subscription.
- *
- * @param subId for which data connection failed.
- * @param slotIndex for which data conenction faled. Can be derived from subId except when subId
- * is invalid.
- * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN. Note each data
- * connection can support multiple anyTypes.
- *
- * @hide
- */
- public void notifyDataConnectionFailed(int subId, int slotIndex, String apnType) {
- try {
- sRegistry.notifyDataConnectionFailedForSubscriber(slotIndex, subId, apnType);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- /**
* TODO change from bundle to CellLocation?
* @hide
*/
diff --git a/core/java/android/util/LongArrayQueue.java b/core/java/android/util/LongArrayQueue.java
index d5f0484..5c701db 100644
--- a/core/java/android/util/LongArrayQueue.java
+++ b/core/java/android/util/LongArrayQueue.java
@@ -162,4 +162,24 @@
final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
return mValues[index];
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ if (mSize <= 0) {
+ return "{}";
+ }
+
+ final StringBuilder buffer = new StringBuilder(mSize * 64);
+ buffer.append('{');
+ buffer.append(get(0));
+ for (int i = 1; i < mSize; i++) {
+ buffer.append(", ");
+ buffer.append(get(i));
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 28eb79a..71ac578 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,13 +16,13 @@
package android.view;
-import static android.view.DisplayEventReceiver.CONFIG_CHANGED_EVENT_SUPPRESS;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.graphics.FrameInfo;
+import android.graphics.Insets;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Build;
import android.os.Handler;
@@ -219,9 +219,10 @@
/**
* Callback type: Animation callback to handle inset updates. This is separate from
* {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via
- * {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then
- * update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress}
- * that contains all the combined updated insets.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} for multiple
+ * ongoing animations but then update the whole view system with a single callback to
+ * {@link View#dispatchWindowInsetsAnimationProgress} that contains all the combined updated
+ * insets.
* <p>
* Both input and animation may change insets, so we need to run this after these callbacks, but
* before traversals.
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 411508f..615dab0 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -66,6 +66,7 @@
private static final String BOTTOM_MARKER = "@bottom";
private static final String DP_MARKER = "@dp";
private static final String RIGHT_MARKER = "@right";
+ private static final String LEFT_MARKER = "@left";
/**
* Category for overlays that allow emulating a display cutout on devices that don't have
@@ -647,6 +648,9 @@
if (spec.endsWith(RIGHT_MARKER)) {
offsetX = displayWidth;
spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
+ } else if (spec.endsWith(LEFT_MARKER)) {
+ offsetX = 0;
+ spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
} else {
offsetX = displayWidth / 2f;
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 3d139cd..7ea4f30 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -21,7 +21,6 @@
import static android.view.InsetsState.ISIDE_LEFT;
import static android.view.InsetsState.ISIDE_RIGHT;
import static android.view.InsetsState.ISIDE_TOP;
-import static android.view.InsetsState.toPublicType;
import android.annotation.Nullable;
import android.graphics.Insets;
@@ -34,7 +33,8 @@
import android.view.InsetsState.InternalInsetsSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.WindowManager.LayoutParams;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,20 +66,21 @@
private final @InsetsType int mTypes;
private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier;
private final InsetsController mController;
- private final WindowInsetsAnimationListener.InsetsAnimation mAnimation;
+ private final WindowInsetsAnimationCallback.InsetsAnimation mAnimation;
private final Rect mFrame;
private Insets mCurrentInsets;
private Insets mPendingInsets;
+ private float mPendingFraction;
private boolean mFinished;
private boolean mCancelled;
- private int mFinishedShownTypes;
+ private boolean mShownOnFinish;
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types,
Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
- InsetsController controller) {
+ InsetsController controller, long durationMs) {
mConsumers = consumers;
mListener = listener;
mTypes = types;
@@ -97,9 +98,10 @@
// TODO: Check for controllability first and wait for IME if needed.
listener.onReady(this, types);
- mAnimation = new WindowInsetsAnimationListener.InsetsAnimation(mTypes, mHiddenInsets,
- mShownInsets);
- mController.dispatchAnimationStarted(mAnimation);
+ mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
+ InsetsController.INTERPOLATOR, durationMs);
+ mController.dispatchAnimationStarted(mAnimation,
+ new AnimationBounds(mHiddenInsets, mShownInsets));
}
@Override
@@ -123,7 +125,7 @@
}
@Override
- public void changeInsets(Insets insets) {
+ public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
if (mFinished) {
throw new IllegalStateException(
"Can't change insets on an animation that is finished.");
@@ -132,6 +134,7 @@
throw new IllegalStateException(
"Can't change insets on an animation that is cancelled.");
}
+ mPendingFraction = sanitize(fraction);
mPendingInsets = sanitize(insets);
mController.scheduleApplyChangeInsets();
}
@@ -155,30 +158,35 @@
SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
+ mAnimation.setFraction(mPendingFraction);
if (mFinished) {
- mController.notifyFinished(this, mFinishedShownTypes);
+ mController.notifyFinished(this, mShownOnFinish);
}
return mFinished;
}
@Override
- public void finish(int shownTypes) {
+ public void finish(boolean shown) {
if (mCancelled) {
return;
}
InsetsState state = new InsetsState(mController.getState());
for (int i = mConsumers.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = mConsumers.valueAt(i);
- boolean visible = (shownTypes & toPublicType(consumer.getType())) != 0;
- state.getSource(consumer.getType()).setVisible(visible);
+ state.getSource(consumer.getType()).setVisible(shown);
}
Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */);
- changeInsets(insets);
+ setInsetsAndAlpha(insets, 1f /* alpha */, shown ? 1f : 0f /* fraction */);
mFinished = true;
- mFinishedShownTypes = shownTypes;
+ mShownOnFinish = shown;
}
+ @Override
@VisibleForTesting
+ public float getCurrentFraction() {
+ return mAnimation.getFraction();
+ }
+
public void onCancelled() {
if (mFinished) {
return;
@@ -191,6 +199,10 @@
return mAnimation;
}
+ WindowInsetsAnimationControlListener getListener() {
+ return mListener;
+ }
+
private Insets calculateInsets(InsetsState state, Rect frame,
SparseArray<InsetsSourceConsumer> consumers, boolean shown,
@Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
@@ -210,9 +222,16 @@
}
private Insets sanitize(Insets insets) {
+ if (insets == null) {
+ insets = getCurrentInsets();
+ }
return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets);
}
+ private static float sanitize(float alpha) {
+ return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha);
+ }
+
private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 43fec82..8870311 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -39,6 +39,8 @@
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -55,11 +57,12 @@
private static final int ANIMATION_DURATION_SHOW_MS = 275;
private static final int ANIMATION_DURATION_HIDE_MS = 340;
- private static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
private static final int DIRECTION_NONE = 0;
private static final int DIRECTION_SHOW = 1;
private static final int DIRECTION_HIDE = 2;
+ static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
@IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
private @interface AnimationDirection{}
@@ -85,8 +88,75 @@
return object.getCurrentInsets();
}
@Override
- public void set(WindowInsetsAnimationController object, Insets value) {
- object.changeInsets(value);
+ public void set(WindowInsetsAnimationController controller, Insets value) {
+ controller.setInsetsAndAlpha(
+ value, 1f /* alpha */, (((DefaultAnimationControlListener)
+ ((InsetsAnimationControlImpl) controller).getListener())
+ .getRawProgress()));
+ }
+ }
+
+ private class DefaultAnimationControlListener implements WindowInsetsAnimationControlListener {
+
+ private WindowInsetsAnimationController mController;
+ private ObjectAnimator mAnimator;
+ private boolean mShow;
+
+ DefaultAnimationControlListener(boolean show) {
+ mShow = show;
+ }
+
+ @Override
+ public void onReady(WindowInsetsAnimationController controller, int types) {
+ mController = controller;
+ if (mShow) {
+ showDirectly(types);
+ } else {
+ hideDirectly(types);
+ }
+ mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
+ mAnimator = ObjectAnimator.ofObject(
+ controller,
+ new InsetsProperty(),
+ sEvaluator,
+ mShow ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
+ mShow ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
+ );
+ mAnimator.setDuration(getDurationMs());
+ mAnimator.setInterpolator(INTERPOLATOR);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onAnimationFinish();
+ }
+ });
+ mAnimator.start();
+ }
+
+ @Override
+ public void onCancelled() {
+ // Animator can be null when it is cancelled before onReady() completes.
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
+
+ private void onAnimationFinish() {
+ mAnimationDirection = DIRECTION_NONE;
+ mController.finish(mShow);
+ }
+
+ private float getRawProgress() {
+ float fraction = (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration();
+ return mShow ? fraction : 1 - fraction;
+ }
+
+ private long getDurationMs() {
+ if (mAnimator != null) {
+ return mAnimator.getDuration();
+ }
+ return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS;
}
}
@@ -278,24 +348,25 @@
}
@Override
- public void controlWindowInsetsAnimation(@InsetsType int types,
+ public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
WindowInsetsAnimationControlListener listener) {
- controlWindowInsetsAnimation(types, listener, false /* fromIme */);
+ controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs);
}
private void controlWindowInsetsAnimation(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, boolean fromIme) {
+ WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) {
// If the frame of our window doesn't span the entire display, the control API makes very
// little sense, as we don't deal with negative insets. So just cancel immediately.
if (!mState.getDisplayFrame().equals(mFrame)) {
listener.onCancelled();
return;
}
- controlAnimationUnchecked(types, listener, mFrame, fromIme);
+ controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs);
}
private void controlAnimationUnchecked(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme) {
+ WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
+ long durationMs) {
if (types == 0) {
// nothing to animate.
return;
@@ -326,7 +397,7 @@
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
frame, mState, listener, typesReady,
- () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
+ () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this, durationMs);
mAnimationControls.add(controller);
}
@@ -397,10 +468,13 @@
}
@VisibleForTesting
- public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) {
+ public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
mAnimationControls.remove(controller);
- hideDirectly(controller.getTypes() & ~shownTypes);
- showDirectly(controller.getTypes() & shownTypes);
+ if (shown) {
+ showDirectly(controller.getTypes());
+ } else {
+ hideDirectly(controller.getTypes());
+ }
}
void notifyControlRevoked(InsetsSourceConsumer consumer) {
@@ -510,58 +584,11 @@
return;
}
- WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
-
- private WindowInsetsAnimationController mController;
- private ObjectAnimator mAnimator;
-
- @Override
- public void onReady(WindowInsetsAnimationController controller, int types) {
- mController = controller;
- if (show) {
- showDirectly(types);
- } else {
- hideDirectly(types);
- }
- mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
- mAnimator = ObjectAnimator.ofObject(
- controller,
- new InsetsProperty(),
- sEvaluator,
- show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
- show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
- );
- mAnimator.setDuration(show
- ? ANIMATION_DURATION_SHOW_MS
- : ANIMATION_DURATION_HIDE_MS);
- mAnimator.setInterpolator(INTERPOLATOR);
- mAnimator.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onAnimationFinish();
- }
- });
- mAnimator.start();
- }
-
- @Override
- public void onCancelled() {
- // Animator can be null when it is cancelled before onReady() completes.
- if (mAnimator != null) {
- mAnimator.cancel();
- }
- }
-
- private void onAnimationFinish() {
- mAnimationDirection = DIRECTION_NONE;
- mController.finish(show ? types : 0);
- }
- };
-
+ final DefaultAnimationControlListener listener = new DefaultAnimationControlListener(show);
// Show/hide animations always need to be relative to the display frame, in order that shown
// and hidden state insets are correct.
- controlAnimationUnchecked(types, listener, mState.getDisplayFrame(), fromIme);
+ controlAnimationUnchecked(
+ types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs());
}
private void hideDirectly(@InsetsType int types) {
@@ -592,12 +619,12 @@
}
@VisibleForTesting
- public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
- mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
+ public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
+ mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
}
@VisibleForTesting
- public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ public void dispatchAnimationFinished(InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9d4f3878..2d0b954 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -109,7 +109,8 @@
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
@@ -4626,7 +4627,7 @@
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
- private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
+ private WindowInsetsAnimationCallback mWindowInsetsAnimationCallback;
/**
* This lives here since it's only valid for interactive views.
@@ -11091,33 +11092,55 @@
}
/**
- * Sets a {@link WindowInsetsAnimationListener} to be notified about animations of windows that
+ * Sets a {@link WindowInsetsAnimationCallback} to be notified about animations of windows that
* cause insets.
*
* @param listener The listener to set.
- * @hide pending unhide
*/
- public void setWindowInsetsAnimationListener(WindowInsetsAnimationListener listener) {
- getListenerInfo().mWindowInsetsAnimationListener = listener;
+ public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) {
+ getListenerInfo().mWindowInsetsAnimationCallback = listener;
}
- void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- mListenerInfo.mWindowInsetsAnimationListener.onStarted(animation);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+ * when Window Insets animation is started.
+ * @param animation current animation
+ * @param bounds the upper and lower {@link AnimationBounds} that provides range of
+ * {@link InsetsAnimation}.
+ * @return the upper and lower {@link AnimationBounds}.
+ */
+ @NonNull
+ public AnimationBounds dispatchWindowInsetsAnimationStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
}
+ return bounds;
}
- WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- return mListenerInfo.mWindowInsetsAnimationListener.onProgress(insets);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onProgress(WindowInsets)}
+ * when Window Insets animation makes progress.
+ * @param insets The current {@link WindowInsets}.
+ * @return current {@link WindowInsets}.
+ */
+ @NonNull
+ public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ return mListenerInfo.mWindowInsetsAnimationCallback.onProgress(insets);
} else {
return insets;
}
}
- void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+ * when Window Insets animation finishes.
+ * @param animation The current ongoing {@link InsetsAnimation}.
+ */
+ public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
}
}
@@ -11253,7 +11276,6 @@
* @return The {@link WindowInsetsController} or {@code null} if the view isn't attached to a
* a window.
* @see Window#getInsetsController()
- * @hide pending unhide
*/
public @Nullable WindowInsetsController getWindowInsetsController() {
if (mAttachInfo != null) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 853a302..4334bb5 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -51,7 +51,8 @@
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -7198,16 +7199,20 @@
}
@Override
- void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
- super.dispatchWindowInsetsAnimationStarted(animation);
+ @NonNull
+ public AnimationBounds dispatchWindowInsetsAnimationStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ super.dispatchWindowInsetsAnimationStarted(animation, bounds);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
- getChildAt(i).dispatchWindowInsetsAnimationStarted(animation);
+ getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
}
+ return bounds;
}
@Override
- WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
+ @NonNull
+ public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
insets = super.dispatchWindowInsetsAnimationProgress(insets);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
@@ -7217,7 +7222,7 @@
}
@Override
- void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
+ public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
super.dispatchWindowInsetsAnimationFinished(animation);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index af1882b..ff31115 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2573,7 +2573,8 @@
/**
* @return The {@link WindowInsetsController} associated with this window
* @see View#getWindowInsetsController()
- * @hide pending unhide
*/
- public abstract @NonNull WindowInsetsController getInsetsController();
+ public @Nullable WindowInsetsController getInsetsController() {
+ return null;
+ }
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index b16a4ca..2404c84 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -177,7 +177,7 @@
* @return The insets that include system bars indicated by {@code typeMask}, taken from
* {@code typeInsetsMap}.
*/
- private static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
+ static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
Insets result = null;
for (int i = FIRST; i <= LAST; i = i << 1) {
if ((typeMask & i) == 0) {
@@ -289,9 +289,8 @@
*
* @param typeMask Bit mask of {@link InsetsType}s to query the insets for.
* @return The insets.
- *
- * @hide pending unhide
*/
+ @NonNull
public Insets getInsets(@InsetsType int typeMask) {
return getInsets(mTypeInsetsMap, typeMask);
}
@@ -313,8 +312,8 @@
* insets are not available for this type as the height of the
* IME is dynamic depending on the {@link EditorInfo} of the
* currently focused view, as well as the UI state of the IME.
- * @hide pending unhide
*/
+ @NonNull
public Insets getMaxInsets(@InsetsType int typeMask) throws IllegalArgumentException {
if ((typeMask & IME) != 0) {
throw new IllegalArgumentException("Unable to query the maximum insets for IME");
@@ -329,7 +328,6 @@
* @param typeMask Bit mask of {@link Type.InsetsType}s to query visibility status.
* @return {@code true} if and only if all windows included in {@code typeMask} are currently
* visible on screen.
- * @hide pending unhide
*/
public boolean isVisible(@InsetsType int typeMask) {
for (int i = FIRST; i <= LAST; i = i << 1) {
@@ -874,7 +872,7 @@
return typeInsetsMap;
}
- private static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
+ static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
int newLeft = Math.max(0, insets.left - left);
int newTop = Math.max(0, insets.top - top);
int newRight = Math.max(0, insets.right - right);
@@ -1015,7 +1013,6 @@
* @param insets The insets to set.
*
* @return itself
- * @hide pending unhide
*/
@NonNull
public Builder setInsets(@InsetsType int typeMask, @NonNull Insets insets) {
@@ -1046,7 +1043,6 @@
* the IME is dynamic depending on the {@link EditorInfo}
* of the currently focused view, as well as the UI
* state of the IME.
- * @hide pending unhide
*/
@NonNull
public Builder setMaxInsets(@InsetsType int typeMask, @NonNull Insets insets)
@@ -1070,7 +1066,6 @@
* @param visible Whether to mark the windows as visible or not.
*
* @return itself
- * @hide pending unhide
*/
@NonNull
public Builder setVisible(@InsetsType int typeMask, boolean visible) {
@@ -1145,7 +1140,6 @@
/**
* Class that defines different types of sources causing window insets.
- * @hide pending unhide
*/
public static final class Type {
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
new file mode 100644
index 0000000..8ae8520
--- /dev/null
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.animation.Interpolator;
+
+/**
+ * Interface that allows the application to listen to animation events for windows that cause
+ * insets.
+ */
+public interface WindowInsetsAnimationCallback {
+
+ /**
+ * Called when an inset animation gets started.
+ * <p>
+ * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical:
+ * It will starts at the root of the view hierarchy and then traverse it and invoke the callback
+ * of the specific {@link View} that is being traversed. The method my return a modified
+ * instance of the bounds by calling {@link AnimationBounds#inset} to indicate that a part of
+ * the insets have been used to offset or clip its children, and the children shouldn't worry
+ * about that part anymore.
+ *
+ * @param animation The animation that is about to start.
+ * @param bounds The bounds in which animation happens.
+ * @return The animation representing the part of the insets that should be dispatched to the
+ * subtree of the hierarchy.
+ */
+ @NonNull
+ default AnimationBounds onStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ return bounds;
+ }
+
+ /**
+ * Called when the insets change as part of running an animation. Note that even if multiple
+ * animations for different types are running, there will only be one progress callback per
+ * frame. The {@code insets} passed as an argument represents the overall state and will include
+ * all types, regardless of whether they are animating or not.
+ * <p>
+ * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
+ * and then traverse it and invoke the callback of the specific {@link View} being traversed.
+ * The method may return a modified instance by calling
+ * {@link WindowInsets#inset(int, int, int, int)} to indicate that a part of the insets have
+ * been used to offset or clip its children, and the children shouldn't worry about that part
+ * anymore.
+ * TODO: Introduce a way to map (type -> InsetAnimation) so app developer can query animation
+ * for a given type e.g. callback.getAnimation(type) OR controller.getAnimation(type).
+ * Or on the controller directly?
+ * @param insets The current insets.
+ * @return The insets to dispatch to the subtree of the hierarchy.
+ */
+ @NonNull
+ WindowInsets onProgress(@NonNull WindowInsets insets);
+
+ /**
+ * Called when an inset animation has finished.
+ *
+ * @param animation The animation that has finished running. This will be the same instance as
+ * passed into {@link #onStarted}
+ */
+ default void onFinished(@NonNull InsetsAnimation animation) {
+ }
+
+ /**
+ * Class representing an animation of a set of windows that cause insets.
+ */
+ final class InsetsAnimation {
+
+ private final @InsetsType int mTypeMask;
+ private float mFraction;
+ @Nullable private final Interpolator mInterpolator;
+ private long mDurationMs;
+
+ public InsetsAnimation(
+ @InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMs) {
+ mTypeMask = typeMask;
+ mInterpolator = interpolator;
+ mDurationMs = durationMs;
+ }
+
+ /**
+ * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
+ */
+ public @InsetsType int getTypeMask() {
+ return mTypeMask;
+ }
+
+ /**
+ * Returns the raw fractional progress of this animation between
+ * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+ * that this progress is the global progress of the animation, whereas
+ * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
+ * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+ * Progress per insets animation is global for the entire animation. One animation animates
+ * all things together (in, out, ...). If they don't animate together, we'd have
+ * multiple animations.
+ *
+ * @return The current progress of this animation.
+ */
+ @FloatRange(from = 0f, to = 1f)
+ public float getFraction() {
+ return mFraction;
+ }
+
+ /**
+ * Returns the interpolated fractional progress of this animation between
+ * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+ * that this progress is the global progress of the animation, whereas
+ * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
+ * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+ * Progress per insets animation is global for the entire animation. One animation animates
+ * all things together (in, out, ...). If they don't animate together, we'd have
+ * multiple animations.
+ * @see #getFraction() for raw fraction.
+ * @return The current interpolated progress of this animation. -1 if interpolator isn't
+ * specified.
+ */
+ public float getInterpolatedFraction() {
+ if (mInterpolator != null) {
+ return mInterpolator.getInterpolation(mFraction);
+ }
+ return -1;
+ }
+
+ @Nullable
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+ */
+ public long getDurationMillis() {
+ return mDurationMs;
+ }
+
+ /**
+ * Set fraction of the progress if {@link WindowInsets.Type.InsetsType} animation is
+ * controlled by the app {@see #getCurrentFraction}.
+ * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set progress either.
+ * Progress would be set by system with the system-default animation.
+ * </p>
+ * @param fraction fractional progress between 0 and 1 where 0 represents hidden and
+ * zero progress and 1 represent fully shown final state.
+ */
+ public void setFraction(@FloatRange(from = 0f, to = 1f) float fraction) {
+ mFraction = fraction;
+ }
+
+ /**
+ * Set duration of the animation if {@link WindowInsets.Type.InsetsType} animation is
+ * controlled by the app.
+ * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set duration either.
+ * Duration would be set by system with the system-default animation.
+ * </p>
+ * @param durationMs in {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+ */
+ public void setDuration(long durationMs) {
+ mDurationMs = durationMs;
+ }
+ }
+
+ /**
+ * Class representing the range of an {@link InsetsAnimation}
+ */
+ final class AnimationBounds {
+ private final Insets mLowerBound;
+ private final Insets mUpperBound;
+
+ public AnimationBounds(@NonNull Insets lowerBound, @NonNull Insets upperBound) {
+ mLowerBound = lowerBound;
+ mUpperBound = upperBound;
+ }
+
+ /**
+ * Queries the lower inset bound of the animation. If the animation is about showing or
+ * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
+ * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
+ * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+ * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+ * invoked because of an animation that originates from
+ * {@link WindowInsetsAnimationController}.
+ * <p>
+ * However, if the size of a window that causes insets is changing, these are the
+ * lower/upper bounds of that size animation.
+ * </p>
+ * There are no overlapping animations for a specific type, but there may be multiple
+ * animations running at the same time for different inset types.
+ *
+ * @see #getUpperBound()
+ * @see WindowInsetsAnimationController#getHiddenStateInsets
+ */
+ @NonNull
+ public Insets getLowerBound() {
+ return mLowerBound;
+ }
+
+ /**
+ * Queries the upper inset bound of the animation. If the animation is about showing or
+ * hiding a window that cause insets, the lower bound is {@link Insets#NONE}
+ * nd the upper bound is the same as {@link WindowInsets#getInsets(int)} for the fully
+ * shown state. This is the same as
+ * {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+ * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+ * invoked because of an animation that originates from
+ * {@link WindowInsetsAnimationController}.
+ * <p>
+ * However, if the size of a window that causes insets is changing, these are the
+ * lower/upper bounds of that size animation.
+ * <p>
+ * There are no overlapping animations for a specific type, but there may be multiple
+ * animations running at the same time for different inset types.
+ *
+ * @see #getLowerBound()
+ * @see WindowInsetsAnimationController#getShownStateInsets
+ */
+ @NonNull
+ public Insets getUpperBound() {
+ return mUpperBound;
+ }
+
+ /**
+ * Insets both the lower and upper bound by the specified insets. This is to be used in
+ * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has
+ * been used to offset or clip its children, and the children shouldn't worry about that
+ * part anymore.
+ *
+ * @param insets The amount to inset.
+ * @return A copy of this instance inset in the given directions.
+ * @see WindowInsets#inset
+ * @see WindowInsetsAnimationCallback#onStarted
+ */
+ @NonNull
+ public AnimationBounds inset(@NonNull Insets insets) {
+ return new AnimationBounds(
+ // TODO: refactor so that WindowInsets.insetInsets() is in a more appropriate
+ // place eventually.
+ WindowInsets.insetInsets(
+ mLowerBound, insets.left, insets.top, insets.right, insets.bottom),
+ WindowInsets.insetInsets(
+ mUpperBound, insets.left, insets.top, insets.right, insets.bottom));
+ }
+ }
+}
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 33fb327..8a226c1 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -22,19 +22,17 @@
/**
* Interface that informs the client about {@link WindowInsetsAnimationController} state changes.
- * @hide pending unhide
*/
public interface WindowInsetsAnimationControlListener {
/**
- * Gets called as soon as the animation is ready to be controlled. This may be
- * delayed when the IME needs to redraw because of an {@link EditorInfo} change, or when the
- * window is starting up.
+ * Called when the animation is ready to be controlled. This may be delayed when the IME needs
+ * to redraw because of an {@link EditorInfo} change, or when the window is starting up.
*
* @param controller The controller to control the inset animation.
* @param types The {@link InsetsType}s it was able to gain control over. Note that this may be
* different than the types passed into
- * {@link WindowInsetsController#controlWindowInsetsAnimation} in case the window
+ * {@link WindowInsetsController#controlInputMethodAnimation} in case the window
* wasn't able to gain the controls because it wasn't the IME target or not
* currently the window that's controlling the system bars.
*/
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 5cbf3b8..5149103 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -16,83 +16,127 @@
package android.view;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.graphics.Insets;
import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
/**
- * Interface to control a window inset animation frame-by-frame.
- * @hide pending unhide
+ * Controller for app-driven animation of system windows.
+ * <p>
+ * {@code WindowInsetsAnimationController} lets apps animate system windows such as
+ * the {@link android.inputmethodservice.InputMethodService IME}. The animation is
+ * synchronized, such that changes the system windows and the app's current frame
+ * are rendered at the same time.
+ * <p>
+ * Control is obtained through {@link WindowInsetsController#controlInputMethodAnimation}.
*/
+@SuppressLint("NotClosable")
public interface WindowInsetsAnimationController {
/**
* Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden.
* <p>
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
+ * <p>
* If there are any animation listeners registered, this value is the same as
- * {@link InsetsAnimation#getLowerBound()} that will be passed into the callbacks.
+ * {@link AnimationBounds#getLowerBound()} that is being be passed into the root view of the
+ * hierarchy.
*
* @return Insets when the windows this animation is controlling are fully hidden.
*
- * @see InsetsAnimation#getLowerBound()
+ * @see AnimationBounds#getLowerBound()
*/
@NonNull Insets getHiddenStateInsets();
/**
* Retrieves the {@link Insets} when the windows this animation is controlling are fully shown.
* <p>
- * In case the size of a window causing insets is changing in the middle of the animation, we
- * execute that height change after this animation has finished.
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
* <p>
* If there are any animation listeners registered, this value is the same as
- * {@link InsetsAnimation#getUpperBound()} that will be passed into the callbacks.
+ * {@link AnimationBounds#getUpperBound()} that is being passed into the root view of hierarchy.
*
* @return Insets when the windows this animation is controlling are fully shown.
*
- * @see InsetsAnimation#getUpperBound()
+ * @see AnimationBounds#getUpperBound()
*/
@NonNull Insets getShownStateInsets();
/**
- * @return The current insets on the window. These will follow any animation changes.
+ * Retrieves the current insets.
+ * <p>
+ * Note that these insets are always relative to the window, which is the same as
+ * being relative
+ * to {@link View#getRootView}
+ * @return The current insets on the currently showing frame. These insets will change as the
+ * animation progresses to reflect the current insets provided by the controlled window.
*/
@NonNull Insets getCurrentInsets();
/**
+ * Returns the progress as previously set by {@code fraction} in {@link #setInsetsAndAlpha}
+ *
+ * @return the progress of the animation, where {@code 0} is fully hidden and {@code 1} is
+ * fully shown.
+ * <p>
+ * Note: this value represents raw overall progress of the animation
+ * i.e. the combined progress of insets and alpha.
+ * <p>
+ */
+ @FloatRange(from = 0f, to = 1f)
+ float getCurrentFraction();
+
+ /**
* @return The {@link InsetsType}s this object is currently controlling.
*/
@InsetsType int getTypes();
/**
- * Modifies the insets by indirectly moving the windows around in the system that are causing
- * window insets.
+ * Modifies the insets for the frame being drawn by indirectly moving the windows around in the
+ * system that are causing window insets.
* <p>
- * Note that this will <b>not</b> inform the view system of a full inset change via
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
+ * <p>
+ * Also note that this will <b>not</b> inform the view system of a full inset change via
* {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the
* animation. If you'd like to animate views during a window inset animation, register a
- * {@link WindowInsetsAnimationListener} by calling
- * {@link View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)} that will be
- * notified about any insets change via {@link WindowInsetsAnimationListener#onProgress} during
+ * {@link WindowInsetsAnimationCallback} by calling
+ * {@link View#setWindowInsetsAnimationCallback(WindowInsetsAnimationCallback)} that will be
+ * notified about any insets change via {@link WindowInsetsAnimationCallback#onProgress} during
* the animation.
* <p>
* {@link View#dispatchApplyWindowInsets} will instead be called once the animation has
* finished, i.e. once {@link #finish} has been called.
+ * Note: If there are no insets, alpha animation is still applied.
*
* @param insets The new insets to apply. Based on the requested insets, the system will
* calculate the positions of the windows in the system causing insets such that
* the resulting insets of that configuration will match the passed in parameter.
* Note that these insets are being clamped to the range from
- * {@link #getHiddenStateInsets} to {@link #getShownStateInsets}
+ * {@link #getHiddenStateInsets} to {@link #getShownStateInsets}.
+ * If you intend on changing alpha only, pass null or {@link #getCurrentInsets()}.
+ * @param alpha The new alpha to apply to the inset side.
+ * @param fraction instantaneous animation progress. This value is dispatched to
+ * {@link WindowInsetsAnimationCallback}.
*
- * @see WindowInsetsAnimationListener
- * @see View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)
+ * @see WindowInsetsAnimationCallback
+ * @see View#setWindowInsetsAnimationCallback(WindowInsetsAnimationCallback)
*/
- void changeInsets(@NonNull Insets insets);
+ void setInsetsAndAlpha(@Nullable Insets insets, @FloatRange(from = 0f, to = 1f) float alpha,
+ @FloatRange(from = 0f, to = 1f) float fraction);
/**
- * @param shownTypes The list of windows causing insets that should remain shown after finishing
- * the animation.
+ * Finishes the animation, and leaves the windows shown or hidden. After invoking
+ * {@link #finish(boolean)}, this instance is no longer valid.
+ * @param shown if {@code true}, the windows will be shown after finishing the
+ * animation. Otherwise they will be hidden.
*/
- void finish(@InsetsType int shownTypes);
+ void finish(boolean shown);
}
diff --git a/core/java/android/view/WindowInsetsAnimationListener.java b/core/java/android/view/WindowInsetsAnimationListener.java
deleted file mode 100644
index f734b4b..0000000
--- a/core/java/android/view/WindowInsetsAnimationListener.java
+++ /dev/null
@@ -1,122 +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.view;
-
-import android.graphics.Insets;
-
-/**
- * Interface that allows the application to listen to animation events for windows that cause
- * insets.
- * @hide pending unhide
- */
-public interface WindowInsetsAnimationListener {
-
- /**
- * Called when an inset animation gets started.
- *
- * @param animation The animation that is about to start.
- */
- void onStarted(InsetsAnimation animation);
-
- /**
- * Called when the insets change as part of running an animation. Note that even if multiple
- * animations for different types are running, there will only be one progress callback per
- * frame. The {@code insets} passed as an argument represents the overall state and will include
- * all types, regardless of whether they are animating or not.
- * <p>
- * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
- * and then traverse it and invoke the callback of the specific {@link View} being traversed.
- * The callback may return a modified instance by calling {@link WindowInsets#inset(int, int, int, int)}
- * to indicate that a part of the insets have been used to offset or clip its children, and the
- * children shouldn't worry about that part anymore.
- *
- * @param insets The current insets.
- * @return The insets to dispatch to the subtree of the hierarchy.
- */
- WindowInsets onProgress(WindowInsets insets);
-
- /**
- * Called when an inset animation has finished.
- *
- * @param animation The animation that has finished running.
- */
- void onFinished(InsetsAnimation animation);
-
- /**
- * Class representing an animation of a set of windows that cause insets.
- */
- class InsetsAnimation {
-
- private final @WindowInsets.Type.InsetsType int mTypeMask;
- private final Insets mLowerBound;
- private final Insets mUpperBound;
-
- /**
- * @hide
- */
- InsetsAnimation(int typeMask, Insets lowerBound, Insets upperBound) {
- mTypeMask = typeMask;
- mLowerBound = lowerBound;
- mUpperBound = upperBound;
- }
-
- /**
- * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
- */
- public @WindowInsets.Type.InsetsType int getTypeMask() {
- return mTypeMask;
- }
-
- /**
- * Queries the lower inset bound of the animation. If the animation is about showing or
- * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
- * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
- * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
- * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
- * invoked because of an animation that originates from
- * {@link WindowInsetsAnimationController}.
- * <p>
- * However, if the size of a window that causes insets is changing, these are the
- * lower/upper bounds of that size animation.
- * <p>
- * There are no overlapping animations for a specific type, but there may be two animations
- * running at the same time for different inset types.
- *
- * @see #getUpperBound()
- * @see WindowInsetsAnimationController#getHiddenStateInsets
- * TODO: It's a bit weird that these are global per window but onProgress is hierarchical.
- * TODO: If multiple types are animating, querying the bound per type isn't possible. Should
- * we:
- * 1. Offer bounds by type here?
- * 2. Restrict one animation to one single type only?
- * Returning WindowInsets here isn't feasible in case of overlapping animations: We can't
- * fill in the insets for the types from the other animation into the WindowInsets object
- * as it's changing as well.
- */
- public Insets getLowerBound() {
- return mLowerBound;
- }
-
- /**
- * @see #getLowerBound()
- * @see WindowInsetsAnimationController#getShownStateInsets
- */
- public Insets getUpperBound() {
- return mUpperBound;
- }
- }
-}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index a045a6a..6de56be 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -30,7 +30,6 @@
* Interface to control windows that generate insets.
*
* TODO Needs more information and examples once the API is more baked.
- * @hide pending unhide
*/
public interface WindowInsetsController {
@@ -64,7 +63,10 @@
*/
int APPEARANCE_LIGHT_NAVIGATION_BARS = 1 << 4;
- /** Determines the appearance of system bars. */
+ /**
+ * Determines the appearance of system bars.
+ * @hide
+ */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
@@ -75,33 +77,40 @@
/**
* The default option for {@link #setSystemBarsBehavior(int)}. System bars will be forcibly
* shown on any user interaction on the corresponding display if navigation bars are hidden by
- * {@link #hide(int)} or {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link #hide(int)} or
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+ * @hide
*/
int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0;
/**
* Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
* hiding navigation bars by calling {@link #hide(int)} or
- * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
*
* <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
* as swiping from the edge of the screen where the bar is hidden from.</p>
+ * @hide
*/
int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1;
/**
* Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
* hiding navigation bars by calling {@link #hide(int)} or
- * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
*
* <p>When system bars are hidden in this mode, they can be revealed temporarily with system
* gestures, such as swiping from the edge of the screen where the bar is hidden from. These
* transient system bars will overlay app’s content, may have some degree of transparency, and
* will automatically hide after a short timeout.</p>
+ * @hide
*/
int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2;
- /** Determines the behavior of system bars when hiding them by calling {@link #hide}. */
+ /**
+ * Determines the behavior of system bars when hiding them by calling {@link #hide}.
+ * @hide
+ */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {BEHAVIOR_SHOW_BARS_BY_TOUCH, BEHAVIOR_SHOW_BARS_BY_SWIPE,
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE})
@@ -139,23 +148,27 @@
* the position of the windows in the system causing insets directly.
*
* @param types The {@link InsetsType}s the application has requested to control.
+ * @param durationMillis duration of animation in
+ * {@link java.util.concurrent.TimeUnit#MILLISECONDS}
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* windows are ready to be controlled, among other callbacks.
* @hide
*/
- void controlWindowInsetsAnimation(@InsetsType int types,
+ void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
@NonNull WindowInsetsAnimationControlListener listener);
/**
* Lets the application control the animation for showing the IME in a frame-by-frame manner by
* modifying the position of the IME when it's causing insets.
*
+ * @param durationMillis duration of the animation in
+ * {@link java.util.concurrent.TimeUnit#MILLISECONDS}
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* IME are ready to be controlled, among other callbacks.
*/
- default void controlInputMethodAnimation(
+ default void controlInputMethodAnimation(long durationMillis,
@NonNull WindowInsetsAnimationControlListener listener) {
- controlWindowInsetsAnimation(ime(), listener);
+ controlWindowInsetsAnimation(ime(), durationMillis, listener);
}
/**
@@ -166,7 +179,7 @@
* the event by observing {@link View#onApplyWindowInsets} and checking visibility with
* {@link WindowInsets#isVisible}.
*
- * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
* @see #hideInputMethod()
*/
default void showInputMethod() {
@@ -181,7 +194,7 @@
* the event by observing {@link View#onApplyWindowInsets} and checking visibility with
* {@link WindowInsets#isVisible}.
*
- * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
* @see #showInputMethod()
*/
default void hideInputMethod() {
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index d44fbd7..3e26f63 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityNodeInfo;
/**
@@ -38,19 +39,19 @@
* guide.</p>
*
* <p><strong>XML attributes</strong></p>
- * <p>
- * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
- * {@link android.R.styleable#Button Button Attributes},
- * {@link android.R.styleable#TextView TextView Attributes},
+ * <p>
+ * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
+ * {@link android.R.styleable#Button Button Attributes},
+ * {@link android.R.styleable#TextView TextView Attributes},
* {@link android.R.styleable#View View Attributes}
* </p>
*/
public class RadioButton extends CompoundButton {
-
+
public RadioButton(Context context) {
this(context, null);
}
-
+
public RadioButton(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
}
@@ -81,4 +82,20 @@
public CharSequence getAccessibilityClassName() {
return RadioButton.class.getName();
}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (getParent() instanceof RadioGroup) {
+ RadioGroup radioGroup = (RadioGroup) getParent();
+ if (radioGroup.getOrientation() == LinearLayout.HORIZONTAL) {
+ info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(0, 1,
+ radioGroup.getIndexWithinVisibleButtons(this), 1, false, isChecked()));
+ } else {
+ info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(
+ radioGroup.getIndexWithinVisibleButtons(this), 1, 0, 1,
+ false, isChecked()));
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index c62c16c..90bc0a3 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -18,6 +18,7 @@
import android.annotation.IdRes;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
@@ -26,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStructure;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -93,6 +95,7 @@
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
}
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
// retrieve selected radio button as requested by the user in the
// XML layout file
@@ -475,4 +478,50 @@
}
return null;
}
-}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (this.getOrientation() == HORIZONTAL) {
+ info.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(1,
+ getVisibleChildCount(), false,
+ AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+ } else {
+ info.setCollectionInfo(
+ AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildCount(),
+ 1, false,
+ AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+ }
+ }
+
+ private int getVisibleChildCount() {
+ int count = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (this.getChildAt(i) instanceof RadioButton) {
+ if (((RadioButton) this.getChildAt(i)).getVisibility() == VISIBLE) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ int getIndexWithinVisibleButtons(@Nullable View child) {
+ if (!(child instanceof RadioButton)) {
+ return -1;
+ }
+ int index = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (this.getChildAt(i) instanceof RadioButton) {
+ RadioButton radioButton = (RadioButton) this.getChildAt(i);
+ if (radioButton == child) {
+ return index;
+ }
+ if (radioButton.getVisibility() == VISIBLE) {
+ index++;
+ }
+ }
+ }
+ return -1;
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c571737..a6304b1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3473,18 +3473,10 @@
return applyAsync(context, parent, executor, listener, null);
}
- private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) {
- CancellationSignal cancelSignal = new CancellationSignal();
- cancelSignal.setOnCancelListener(task);
-
- task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
- return cancelSignal;
- }
-
/** @hide */
public CancellationSignal applyAsync(Context context, ViewGroup parent,
Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
- return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor);
+ return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor);
}
private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
@@ -3495,6 +3487,7 @@
private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
implements CancellationSignal.OnCancelListener {
+ final CancellationSignal mCancelSignal = new CancellationSignal();
final RemoteViews mRV;
final ViewGroup mParent;
final Context mContext;
@@ -3545,6 +3538,7 @@
@Override
protected void onPostExecute(ViewTree viewTree) {
+ mCancelSignal.setOnCancelListener(null);
if (mError == null) {
if (mListener != null) {
mListener.onViewInflated(viewTree.mRoot);
@@ -3582,6 +3576,12 @@
public void onCancel() {
cancel(true);
}
+
+ private CancellationSignal startTaskOnExecutor(Executor executor) {
+ mCancelSignal.setOnCancelListener(this);
+ executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
+ return mCancelSignal;
+ }
}
/**
@@ -3646,8 +3646,8 @@
}
}
- return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
- context, listener, handler, v), executor);
+ return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
+ context, listener, handler, v).startTaskOnExecutor(executor);
}
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 8856f99..f361784 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -111,6 +111,7 @@
import com.android.internal.app.ResolverListAdapter.ViewHolder;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.MultiDisplayResolveInfo;
import com.android.internal.app.chooser.NotSelectableTargetInfo;
import com.android.internal.app.chooser.SelectableTargetInfo;
import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
@@ -1352,17 +1353,31 @@
return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
}
- @Override
- public void showTargetDetails(ResolveInfo ri) {
- if (ri == null) {
+ void showTargetDetails(TargetInfo ti) {
+ if (ti == null) {
return;
}
-
- ComponentName name = ri.activityInfo.getComponentName();
+ ComponentName name = ti.getResolveInfo().activityInfo.getComponentName();
boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
- ResolverTargetActionsDialogFragment f =
- new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
- name, pinned);
+
+ ResolverTargetActionsDialogFragment f;
+
+ // For multiple targets, include info on all targets
+ if (ti instanceof MultiDisplayResolveInfo) {
+ MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti;
+ List<CharSequence> labels = new ArrayList<>();
+
+ for (TargetInfo innerInfo : mti.getTargets()) {
+ labels.add(innerInfo.getResolveInfo().loadLabel(getPackageManager()));
+ }
+ f = new ResolverTargetActionsDialogFragment(
+ mti.getResolveInfo().loadLabel(getPackageManager()), name, mti.getTargets(),
+ labels);
+ } else {
+ f = new ResolverTargetActionsDialogFragment(
+ ti.getResolveInfo().loadLabel(getPackageManager()), name, pinned);
+ }
+
f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
}
@@ -1416,8 +1431,26 @@
}
final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
+
+ // Stacked apps get a disambiguation first
+ if (targetInfo instanceof MultiDisplayResolveInfo) {
+ MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
+ CharSequence[] labels = new CharSequence[mti.getTargets().size()];
+ int i = 0;
+ for (TargetInfo ti : mti.getTargets()) {
+ labels[i++] = ti.getResolveInfo().loadLabel(getPackageManager());
+ }
+ ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
+ targetInfo.getDisplayLabel(),
+ ((MultiDisplayResolveInfo) targetInfo).getTargets(), labels);
+
+ f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+ return;
+ }
+
super.startSelected(which, always, filtered);
+
if (currentListAdapter.getCount() > 0) {
// Log the index of which type of target the user picked.
// Lower values mean the ranking was better.
@@ -2363,7 +2396,7 @@
itemView.setOnLongClickListener(v -> {
showTargetDetails(
mChooserMultiProfilePagerAdapter.getActiveListAdapter()
- .resolveInfoForPosition(mListPosition, /* filtered */ true));
+ .targetInfoForPosition(mListPosition, /* filtered */ true));
return true;
});
}
@@ -2615,7 +2648,7 @@
@Override
public boolean onLongClick(View v) {
showTargetDetails(
- mChooserListAdapter.resolveInfoForPosition(
+ mChooserListAdapter.targetInfoForPosition(
holder.getItemIndex(column), true));
return true;
}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 4eccf21..a8a676d 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -38,17 +38,22 @@
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.MultiDisplayResolveInfo;
import com.android.internal.app.chooser.SelectableTargetInfo;
import com.android.internal.app.chooser.TargetInfo;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class ChooserListAdapter extends ResolverListAdapter {
private static final String TAG = "ChooserListAdapter";
private static final boolean DEBUG = false;
+ private boolean mEnableStackedApps = true;
+
public static final int NO_POSITION = -1;
public static final int TARGET_BAD = -1;
public static final int TARGET_CALLER = 0;
@@ -218,7 +223,25 @@
void updateAlphabeticalList() {
mSortedList.clear();
- mSortedList.addAll(mDisplayList);
+ if (mEnableStackedApps) {
+ // Consolidate multiple targets from same app.
+ Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
+ for (DisplayResolveInfo info : mDisplayList) {
+ String packageName = info.getResolvedComponentName().getPackageName();
+ if (consolidated.get(packageName) != null) {
+ // create consolidated target
+ MultiDisplayResolveInfo multiDisplayResolveInfo =
+ new MultiDisplayResolveInfo(packageName, info);
+ multiDisplayResolveInfo.addTarget(consolidated.get(packageName));
+ consolidated.put(packageName, multiDisplayResolveInfo);
+ } else {
+ consolidated.put(packageName, info);
+ }
+ }
+ mSortedList.addAll(consolidated.values());
+ } else {
+ mSortedList.addAll(mDisplayList);
+ }
Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext));
}
@@ -270,7 +293,7 @@
}
int getAlphaTargetCount() {
- int standardCount = super.getCount();
+ int standardCount = mSortedList.size();
return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
}
diff --git a/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
new file mode 100644
index 0000000..ff6582d
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.internal.app;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import com.android.internal.app.chooser.DisplayResolveInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows individual actions for a "stacked" app target - such as an app with multiple posting
+ * streams represented in the Sharesheet.
+ */
+public class ChooserStackedAppDialogFragment extends DialogFragment
+ implements DialogInterface.OnClickListener {
+ private static final String TITLE_KEY = "title";
+ private static final String PINNED_KEY = "pinned";
+
+ private List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+ private CharSequence[] mLabels;
+
+ public ChooserStackedAppDialogFragment() {
+ }
+
+ public ChooserStackedAppDialogFragment(CharSequence title) {
+ Bundle args = new Bundle();
+ args.putCharSequence(TITLE_KEY, title);
+ setArguments(args);
+ }
+
+ public ChooserStackedAppDialogFragment(CharSequence title,
+ List<DisplayResolveInfo> targets, CharSequence[] labels) {
+ Bundle args = new Bundle();
+ args.putCharSequence(TITLE_KEY, title);
+ mTargetInfos = targets;
+ mLabels = labels;
+ setArguments(args);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle args = getArguments();
+ return new Builder(getContext())
+ .setCancelable(true)
+ .setItems(mLabels, this)
+ .setTitle(args.getCharSequence(TITLE_KEY))
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Bundle args = getArguments();
+ mTargetInfos.get(which).start(getActivity(), null);
+ dismiss();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Dismiss on config changed (eg: rotation)
+ // TODO: Maintain state on config change
+ super.onConfigurationChanged(newConfig);
+ dismiss();
+ }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index cb7f2e4..8dc3a07 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1160,7 +1160,7 @@
return !target.isSuspended();
}
- public void showTargetDetails(ResolveInfo ri) {
+ void showTargetDetails(ResolveInfo ri) {
Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
index df91c4a..bdbe210 100644
--- a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -24,14 +24,19 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import com.android.internal.R;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+
+import java.util.ArrayList;
+import java.util.List;
/**
- * Shows a dialog with actions to take on a chooser target
+ * Shows a dialog with actions to take on a chooser target.
*/
public class ResolverTargetActionsDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
@@ -43,6 +48,10 @@
private static final int TOGGLE_PIN_INDEX = 0;
private static final int APP_INFO_INDEX = 1;
+ private List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+ private List<CharSequence> mLabels = new ArrayList<>();
+ private boolean[] mPinned;
+
public ResolverTargetActionsDialogFragment() {
}
@@ -55,15 +64,43 @@
setArguments(args);
}
+ public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
+ List<DisplayResolveInfo> targets, List<CharSequence> labels) {
+ Bundle args = new Bundle();
+ args.putCharSequence(TITLE_KEY, title);
+ args.putParcelable(NAME_KEY, name);
+ mTargetInfos = targets;
+ mLabels = labels;
+ setArguments(args);
+ }
+
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments();
final int itemRes = args.getBoolean(PINNED_KEY, false)
? R.array.resolver_target_actions_unpin
: R.array.resolver_target_actions_pin;
+ String[] defaultActions = getResources().getStringArray(itemRes);
+ CharSequence[] items;
+
+ if (mTargetInfos == null || mTargetInfos.size() < 2) {
+ items = defaultActions;
+ } else {
+ // Pin item for each sub-item
+ items = new CharSequence[mTargetInfos.size() + 1];
+ for (int i = 0; i < mTargetInfos.size(); i++) {
+ items[i] = mTargetInfos.get(i).isPinned()
+ ? getResources().getString(R.string.unpin_specific_target, mLabels.get(i))
+ : getResources().getString(R.string.pin_specific_target, mLabels.get(i));
+ }
+ // "App info"
+ items[mTargetInfos.size()] = defaultActions[1];
+ }
+
+
return new Builder(getContext())
.setCancelable(true)
- .setItems(itemRes, this)
+ .setItems(items, this)
.setTitle(args.getCharSequence(TITLE_KEY))
.create();
}
@@ -72,27 +109,41 @@
public void onClick(DialogInterface dialog, int which) {
final Bundle args = getArguments();
ComponentName name = args.getParcelable(NAME_KEY);
- switch (which) {
- case TOGGLE_PIN_INDEX:
- SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
- final String key = name.flattenToString();
- boolean currentVal = sp.getBoolean(name.flattenToString(), false);
- if (currentVal) {
- sp.edit().remove(key).apply();
- } else {
- sp.edit().putBoolean(key, true).apply();
- }
-
- // Force the chooser to requery and resort things
- getActivity().recreate();
- break;
- case APP_INFO_INDEX:
- Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
- .setData(Uri.fromParts("package", name.getPackageName(), null))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
- startActivity(in);
- break;
+ if (which == 0 || (mTargetInfos.size() > 0 && which < mTargetInfos.size())) {
+ if (mTargetInfos == null || mTargetInfos.size() == 0) {
+ pinComponent(name);
+ } else {
+ pinComponent(mTargetInfos.get(which).getResolvedComponentName());
+ }
+ // Force the chooser to requery and resort things
+ getActivity().recreate();
+ } else {
+ // Last item in dialog is App Info
+ Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(Uri.fromParts("package", name.getPackageName(), null))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ startActivity(in);
}
dismiss();
}
+
+ private void pinComponent(ComponentName name) {
+ SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
+ final String key = name.flattenToString();
+ boolean currentVal = sp.getBoolean(name.flattenToString(), false);
+ if (currentVal) {
+ sp.edit().remove(key).apply();
+ } else {
+ sp.edit().putBoolean(key, true).apply();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Dismiss on config changed (eg: rotation)
+ // TODO: Maintain state on config change
+ super.onConfigurationChanged(newConfig);
+ dismiss();
+ }
+
}
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index f92637c..86a9af3 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -91,6 +91,16 @@
mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
}
+ DisplayResolveInfo(DisplayResolveInfo other) {
+ mSourceIntents.addAll(other.getAllSourceIntents());
+ mResolveInfo = other.mResolveInfo;
+ mDisplayLabel = other.mDisplayLabel;
+ mDisplayIcon = other.mDisplayIcon;
+ mExtendedInfo = other.mExtendedInfo;
+ mResolvedIntent = other.mResolvedIntent;
+ mResolveInfoPresentationGetter = other.mResolveInfoPresentationGetter;
+ }
+
public ResolveInfo getResolveInfo() {
return mResolveInfo;
}
@@ -189,4 +199,5 @@
public void setPinned(boolean pinned) {
mPinned = pinned;
}
+
}
diff --git a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
new file mode 100644
index 0000000..4c52411
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app.chooser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a "stack" of chooser targets for various activities within the same component.
+ */
+public class MultiDisplayResolveInfo extends DisplayResolveInfo {
+
+ List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+ String mPackageName;
+ // We'll use this DRI for basic presentation info - eg icon, name.
+ final DisplayResolveInfo mBaseInfo;
+
+ /**
+ * @param firstInfo A representative DRI to use for the main icon, title, etc for this Info.
+ */
+ public MultiDisplayResolveInfo(String packageName, DisplayResolveInfo firstInfo) {
+ super(firstInfo);
+ mBaseInfo = firstInfo;
+ mTargetInfos.add(firstInfo);
+ }
+
+ /**
+ * Add another DisplayResolveInfo to the list included for this target.
+ */
+ public void addTarget(DisplayResolveInfo target) {
+ mTargetInfos.add(target);
+ }
+
+ /**
+ * List of all DisplayResolveInfos included in this target.
+ */
+ public List<DisplayResolveInfo> getTargets() {
+ return mTargetInfos;
+ }
+
+}
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index d78bd73..b250578 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -531,6 +531,12 @@
try {
AndroidFuture.this.complete((T) resultContainer.get());
} catch (Throwable t) {
+ // If resultContainer was completed exceptionally, get() wraps the
+ // underlying exception in an ExecutionException. Unwrap it now to avoid
+ // double-layering ExecutionExceptions.
+ if (t instanceof ExecutionException && t.getCause() != null) {
+ t = t.getCause();
+ }
completeExceptionally(t);
}
}
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 01f5743..cb67309 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -43,7 +43,6 @@
void onDataConnectionStateChanged(int state, int networkType);
void onDataActivity(int direction);
void onSignalStrengthsChanged(in SignalStrength signalStrength);
- void onOtaspChanged(in int otaspMode);
void onCellInfoChanged(in List<CellInfo> cellInfo);
void onPreciseCallStateChanged(in PreciseCallState callState);
void onPreciseDataConnectionStateChanged(in PreciseDataConnectionState dataConnectionState);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index d8e8d67..f954679 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -24,6 +24,8 @@
import android.telephony.CellInfo;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.PhoneCapability;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.PreciseDataConnectionState;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
@@ -60,16 +62,13 @@
@UnsupportedAppUsage(maxTargetSdk = 28)
void notifyDataActivity(int state);
void notifyDataActivityForSubscriber(in int subId, int state);
- void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
- boolean isDataConnectivityPossible,
- String apn, String apnType, in LinkProperties linkProperties,
- in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
- void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType);
+ void notifyDataConnectionForSubscriber(
+ int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState);
+ @UnsupportedAppUsage
+ void notifyDataConnectionFailed(String apnType);
@UnsupportedAppUsage(maxTargetSdk = 28)
void notifyCellLocation(in Bundle cellLocation);
void notifyCellLocationForSubscriber(in int subId, in Bundle cellLocation);
- @UnsupportedAppUsage(maxTargetSdk = 28)
- void notifyOtaspChanged(in int subId, in int otaspMode);
@UnsupportedAppUsage
void notifyCellInfo(in List<CellInfo> cellInfo);
void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 4165f20..4dac542 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -308,6 +308,17 @@
}
/**
+ * @see #add(List, Object)
+ */
+ public static @NonNull <K, V> Map<K, V> add(@Nullable Map<K, V> map, K key, V value) {
+ if (map == null || map == Collections.emptyMap()) {
+ map = new ArrayMap<>();
+ }
+ map.put(key, value);
+ return map;
+ }
+
+ /**
* Similar to {@link List#remove}, but with support for list values of {@code null} and
* {@link Collections#emptyList}
*/
diff --git a/core/java/com/android/internal/util/ScreenRecordHelper.java b/core/java/com/android/internal/util/ScreenRecordHelper.java
index 64d0898..ec7ed4e 100644
--- a/core/java/com/android/internal/util/ScreenRecordHelper.java
+++ b/core/java/com/android/internal/util/ScreenRecordHelper.java
@@ -24,10 +24,6 @@
* Helper class to initiate a screen recording
*/
public class ScreenRecordHelper {
- private static final String SYSUI_PACKAGE = "com.android.systemui";
- private static final String SYSUI_SCREENRECORD_LAUNCHER =
- "com.android.systemui.screenrecord.ScreenRecordDialog";
-
private final Context mContext;
/**
@@ -42,8 +38,9 @@
* Show dialog of screen recording options to user.
*/
public void launchRecordPrompt() {
- final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
- SYSUI_SCREENRECORD_LAUNCHER);
+ final ComponentName launcherComponent = ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_screenRecorderComponent));
final Intent intent = new Intent();
intent.setComponent(launcherComponent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index f6f187f..8cad5a0 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -20,12 +20,6 @@
public class ScreenshotHelper {
private static final String TAG = "ScreenshotHelper";
- private static final String SYSUI_PACKAGE = "com.android.systemui";
- private static final String SYSUI_SCREENSHOT_SERVICE =
- "com.android.systemui.screenshot.TakeScreenshotService";
- private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER =
- "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver";
-
// Time until we give up on the screenshot & show an error instead.
private final int SCREENSHOT_TIMEOUT_MS = 10000;
@@ -94,8 +88,9 @@
if (mScreenshotConnection != null) {
return;
}
- final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
- SYSUI_SCREENSHOT_SERVICE);
+ final ComponentName serviceComponent = ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_screenshotServiceComponent));
final Intent serviceIntent = new Intent();
final Runnable mScreenshotTimeout = new Runnable() {
@@ -181,8 +176,9 @@
*/
private void notifyScreenshotError() {
// If the service process is killed, then ask it to clean up after itself
- final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE,
- SYSUI_SCREENSHOT_ERROR_RECEIVER);
+ final ComponentName errorComponent = ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_screenshotErrorReceiverComponent));
// Broadcast needs to have a valid action. We'll just pick
// a generic one, since the receiver here doesn't care.
Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 7562bad..8a59c99 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
@@ -228,7 +229,7 @@
* Map of system pre-defined, uniquely named actors; keys are namespace,
* value maps actor name to package name.
*/
- private ArrayMap<String, ArrayMap<String, String>> mNamedActors = null;
+ private Map<String, Map<String, String>> mNamedActors = null;
public static SystemConfig getInstance() {
if (!isSystemProcess()) {
@@ -412,7 +413,7 @@
}
@NonNull
- public Map<String, ? extends Map<String, String>> getNamedActors() {
+ public Map<String, Map<String, String>> getNamedActors() {
return mNamedActors != null ? mNamedActors : Collections.emptyMap();
}
@@ -498,6 +499,19 @@
Environment.getSystemExtDirectory(), "etc", "sysconfig"), ALLOW_ALL);
readPermissions(Environment.buildPath(
Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);
+
+ // Skip loading configuration from apex if it is not a system process.
+ if (!isSystemProcess()) {
+ return;
+ }
+ // Read configuration of libs from apex module.
+ // TODO: Use a solid way to filter apex module folders?
+ for (File f: FileUtils.listFilesOrEmpty(Environment.getApexDirectory())) {
+ if (f.isFile() || f.getPath().contains("@")) {
+ continue;
+ }
+ readPermissions(Environment.buildPath(f, "etc", "permissions"), ALLOW_LIBS);
+ }
}
@VisibleForTesting
@@ -1069,7 +1083,7 @@
mNamedActors = new ArrayMap<>();
}
- ArrayMap<String, String> nameToPkgMap = mNamedActors.get(namespace);
+ Map<String, String> nameToPkgMap = mNamedActors.get(namespace);
if (nameToPkgMap == null) {
nameToPkgMap = new ArrayMap<>();
mNamedActors.put(namespace, nameToPkgMap);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b91d359..a2f6a62 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -177,7 +177,6 @@
"android_hardware_HardwareBuffer.cpp",
"android_hardware_SensorManager.cpp",
"android_hardware_SerialPort.cpp",
- "android_hardware_SoundTrigger.cpp",
"android_hardware_UsbDevice.cpp",
"android_hardware_UsbDeviceConnection.cpp",
"android_hardware_UsbRequest.cpp",
@@ -259,7 +258,6 @@
"libpdfium",
"libimg_utils",
"libnetd_client",
- "libsoundtrigger",
"libprocessgroup",
"libnativebridge_lazy",
"libnativeloader_lazy",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b8fd3ad..f7a994f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -80,7 +80,6 @@
extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
extern int register_android_hardware_SerialPort(JNIEnv *env);
-extern int register_android_hardware_SoundTrigger(JNIEnv *env);
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
@@ -1508,7 +1507,6 @@
REG_JNI(register_android_hardware_HardwareBuffer),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_hardware_SerialPort),
- REG_JNI(register_android_hardware_SoundTrigger),
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
deleted file mode 100644
index 4376b0b..0000000
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ /dev/null
@@ -1,1071 +0,0 @@
-/*
-**
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SoundTrigger-JNI"
-#include <utils/Log.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include "core_jni_helpers.h"
-#include <system/sound_trigger.h>
-#include <soundtrigger/SoundTriggerCallback.h>
-#include <soundtrigger/SoundTrigger.h>
-#include <utils/RefBase.h>
-#include <utils/Vector.h>
-#include <binder/IMemory.h>
-#include <binder/MemoryDealer.h>
-#include "android_media_AudioFormat.h"
-
-using namespace android;
-
-static jclass gArrayListClass;
-static struct {
- jmethodID add;
-} gArrayListMethods;
-
-static jclass gUUIDClass;
-static struct {
- jmethodID toString;
-} gUUIDMethods;
-
-static const char* const kUnsupportedOperationExceptionClassPathName =
- "java/lang/UnsupportedOperationException";
-static jclass gUnsupportedOperationExceptionClass;
-static const char* const kIllegalArgumentExceptionClassPathName =
- "java/lang/IllegalArgumentException";
-static jclass gIllegalArgumentExceptionClass;
-
-static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger";
-static jclass gSoundTriggerClass;
-
-static const char* const kModuleClassPathName = "android/hardware/soundtrigger/SoundTriggerModule";
-static jclass gModuleClass;
-static struct {
- jfieldID mNativeContext;
- jfieldID mId;
-} gModuleFields;
-static jmethodID gPostEventFromNative;
-
-static const char* const kModulePropertiesClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ModuleProperties";
-static jclass gModulePropertiesClass;
-static jmethodID gModulePropertiesCstor;
-
-static const char* const kSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$SoundModel";
-static jclass gSoundModelClass;
-static struct {
- jfieldID uuid;
- jfieldID vendorUuid;
- jfieldID data;
-} gSoundModelFields;
-
-static const char* const kGenericSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$GenericSoundModel";
-static jclass gGenericSoundModelClass;
-
-static const char* const kKeyphraseClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$Keyphrase";
-static jclass gKeyphraseClass;
-static struct {
- jfieldID id;
- jfieldID recognitionModes;
- jfieldID locale;
- jfieldID text;
- jfieldID users;
-} gKeyphraseFields;
-
-static const char* const kKeyphraseSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseSoundModel";
-static jclass gKeyphraseSoundModelClass;
-static struct {
- jfieldID keyphrases;
-} gKeyphraseSoundModelFields;
-
-static const char* const kModelParamRangeClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ModelParamRange";
-static jclass gModelParamRangeClass;
-static jmethodID gModelParamRangeCstor;
-
-static const char* const kRecognitionConfigClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$RecognitionConfig";
-static jclass gRecognitionConfigClass;
-static struct {
- jfieldID captureRequested;
- jfieldID keyphrases;
- jfieldID data;
-} gRecognitionConfigFields;
-
-static const char* const kRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$RecognitionEvent";
-static jclass gRecognitionEventClass;
-static jmethodID gRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionEvent";
-static jclass gKeyphraseRecognitionEventClass;
-static jmethodID gKeyphraseRecognitionEventCstor;
-
-static const char* const kGenericRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$GenericRecognitionEvent";
-static jclass gGenericRecognitionEventClass;
-static jmethodID gGenericRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionExtraClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra";
-static jclass gKeyphraseRecognitionExtraClass;
-static jmethodID gKeyphraseRecognitionExtraCstor;
-static struct {
- jfieldID id;
- jfieldID recognitionModes;
- jfieldID coarseConfidenceLevel;
- jfieldID confidenceLevels;
-} gKeyphraseRecognitionExtraFields;
-
-static const char* const kConfidenceLevelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ConfidenceLevel";
-static jclass gConfidenceLevelClass;
-static jmethodID gConfidenceLevelCstor;
-static struct {
- jfieldID userId;
- jfieldID confidenceLevel;
-} gConfidenceLevelFields;
-
-static const char* const kAudioFormatClassPathName =
- "android/media/AudioFormat";
-static jclass gAudioFormatClass;
-static jmethodID gAudioFormatCstor;
-
-static const char* const kSoundModelEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$SoundModelEvent";
-static jclass gSoundModelEventClass;
-static jmethodID gSoundModelEventCstor;
-
-static Mutex gLock;
-
-enum {
- SOUNDTRIGGER_STATUS_OK = 0,
- SOUNDTRIGGER_STATUS_ERROR = INT_MIN,
- SOUNDTRIGGER_PERMISSION_DENIED = -1,
- SOUNDTRIGGER_STATUS_NO_INIT = -19,
- SOUNDTRIGGER_STATUS_BAD_VALUE = -22,
- SOUNDTRIGGER_STATUS_DEAD_OBJECT = -32,
- SOUNDTRIGGER_INVALID_OPERATION = -38,
-};
-
-enum {
- SOUNDTRIGGER_EVENT_RECOGNITION = 1,
- SOUNDTRIGGER_EVENT_SERVICE_DIED = 2,
- SOUNDTRIGGER_EVENT_SOUNDMODEL = 3,
- SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE = 4,
-};
-
-static jint throwUnsupportedOperationException(JNIEnv *env)
-{
- return env->ThrowNew(gUnsupportedOperationExceptionClass, nullptr);
-}
-
-static jint throwIllegalArgumentException(JNIEnv *env)
-{
- return env->ThrowNew(gIllegalArgumentExceptionClass, nullptr);
-}
-
-// ----------------------------------------------------------------------------
-// ref-counted object for callbacks
-class JNISoundTriggerCallback: public SoundTriggerCallback
-{
-public:
- JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
- ~JNISoundTriggerCallback();
-
- virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event);
- virtual void onSoundModelEvent(struct sound_trigger_model_event *event);
- virtual void onServiceStateChange(sound_trigger_service_state_t state);
- virtual void onServiceDied();
-
-private:
- jclass mClass; // Reference to SoundTrigger class
- jobject mObject; // Weak ref to SoundTrigger Java object to call on
-};
-
-JNISoundTriggerCallback::JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
-{
-
- // Hold onto the SoundTriggerModule class for use in calling the static method
- // that posts events to the application thread.
- jclass clazz = env->GetObjectClass(thiz);
- if (clazz == NULL) {
- ALOGE("Can't find class %s", kModuleClassPathName);
- return;
- }
- mClass = (jclass)env->NewGlobalRef(clazz);
-
- // We use a weak reference so the SoundTriggerModule object can be garbage collected.
- // The reference is only used as a proxy for callbacks.
- mObject = env->NewGlobalRef(weak_thiz);
-}
-
-JNISoundTriggerCallback::~JNISoundTriggerCallback()
-{
- // remove global references
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->DeleteGlobalRef(mObject);
- env->DeleteGlobalRef(mClass);
-}
-
-void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject jEvent = NULL;
- jbyteArray jData = NULL;
-
- if (event->data_size) {
- jData = env->NewByteArray(event->data_size);
- jbyte *nData = env->GetByteArrayElements(jData, NULL);
- memcpy(nData, (char *)event + event->data_offset, event->data_size);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
-
- jobject jAudioFormat = NULL;
- if (event->trigger_in_data || event->capture_available) {
- jint channelMask = (jint)audio_channel_mask_get_bits(event->audio_config.channel_mask);
- jint channelIndexMask = (jint)AUDIO_CHANNEL_NONE;
-
- switch (audio_channel_mask_get_representation(event->audio_config.channel_mask)) {
- case AUDIO_CHANNEL_REPRESENTATION_INDEX:
- channelIndexMask = channelMask;
- channelMask = (jint)AUDIO_CHANNEL_NONE;
- break;
- default:
- break;
- }
- jAudioFormat = env->NewObject(gAudioFormatClass,
- gAudioFormatCstor,
- audioFormatFromNative(event->audio_config.format),
- event->audio_config.sample_rate,
- channelMask,
- channelIndexMask);
-
- }
- if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_recognition_event *phraseEvent =
- (struct sound_trigger_phrase_recognition_event *)event;
-
- jobjectArray jExtras = env->NewObjectArray(phraseEvent->num_phrases,
- gKeyphraseRecognitionExtraClass, NULL);
- if (jExtras == NULL) {
- return;
- }
-
- for (size_t i = 0; i < phraseEvent->num_phrases; i++) {
- jobjectArray jConfidenceLevels = env->NewObjectArray(
- phraseEvent->phrase_extras[i].num_levels,
- gConfidenceLevelClass, NULL);
-
- if (jConfidenceLevels == NULL) {
- return;
- }
- for (size_t j = 0; j < phraseEvent->phrase_extras[i].num_levels; j++) {
- jobject jConfidenceLevel = env->NewObject(gConfidenceLevelClass,
- gConfidenceLevelCstor,
- phraseEvent->phrase_extras[i].levels[j].user_id,
- phraseEvent->phrase_extras[i].levels[j].level);
- env->SetObjectArrayElement(jConfidenceLevels, j, jConfidenceLevel);
- env->DeleteLocalRef(jConfidenceLevel);
- }
-
- jobject jNewExtra = env->NewObject(gKeyphraseRecognitionExtraClass,
- gKeyphraseRecognitionExtraCstor,
- phraseEvent->phrase_extras[i].id,
- phraseEvent->phrase_extras[i].recognition_modes,
- phraseEvent->phrase_extras[i].confidence_level,
- jConfidenceLevels);
-
- if (jNewExtra == NULL) {
- return;
- }
- env->SetObjectArrayElement(jExtras, i, jNewExtra);
- env->DeleteLocalRef(jNewExtra);
- env->DeleteLocalRef(jConfidenceLevels);
- }
- jEvent = env->NewObject(gKeyphraseRecognitionEventClass, gKeyphraseRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData, jExtras);
- env->DeleteLocalRef(jExtras);
- } else if (event->type == SOUND_MODEL_TYPE_GENERIC) {
- jEvent = env->NewObject(gGenericRecognitionEventClass, gGenericRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData);
- } else {
- jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData);
- }
-
- if (jAudioFormat != NULL) {
- env->DeleteLocalRef(jAudioFormat);
- }
- if (jData != NULL) {
- env->DeleteLocalRef(jData);
- }
-
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent);
-
- env->DeleteLocalRef(jEvent);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onSoundModelEvent(struct sound_trigger_model_event *event)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject jEvent = NULL;
- jbyteArray jData = NULL;
-
- if (event->data_size) {
- jData = env->NewByteArray(event->data_size);
- jbyte *nData = env->GetByteArrayElements(jData, NULL);
- memcpy(nData, (char *)event + event->data_offset, event->data_size);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
-
- jEvent = env->NewObject(gSoundModelEventClass, gSoundModelEventCstor,
- event->status, event->model, jData);
-
- env->DeleteLocalRef(jData);
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SOUNDMODEL, 0, 0, jEvent);
- env->DeleteLocalRef(jEvent);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onServiceStateChange(sound_trigger_service_state_t state)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE, state, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onServiceDied()
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
-
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SERVICE_DIED, 0, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-// ----------------------------------------------------------------------------
-
-static sp<SoundTrigger> getSoundTrigger(JNIEnv* env, jobject thiz)
-{
- Mutex::Autolock l(gLock);
- SoundTrigger* const st = (SoundTrigger*)env->GetLongField(thiz,
- gModuleFields.mNativeContext);
- return sp<SoundTrigger>(st);
-}
-
-static sp<SoundTrigger> setSoundTrigger(JNIEnv* env, jobject thiz, const sp<SoundTrigger>& module)
-{
- Mutex::Autolock l(gLock);
- sp<SoundTrigger> old = (SoundTrigger*)env->GetLongField(thiz,
- gModuleFields.mNativeContext);
- if (module.get()) {
- module->incStrong((void*)setSoundTrigger);
- }
- if (old != 0) {
- old->decStrong((void*)setSoundTrigger);
- }
- env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get());
- return old;
-}
-
-
-static jint
-android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
- jstring opPackageName, jobject jModules)
-{
- ALOGV("listModules");
-
- if (jModules == NULL) {
- ALOGE("listModules NULL AudioPatch ArrayList");
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- if (!env->IsInstanceOf(jModules, gArrayListClass)) {
- ALOGE("listModules not an arraylist");
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
-
- unsigned int numModules = 0;
- struct sound_trigger_module_descriptor *nModules = NULL;
-
- ScopedUtfChars opPackageNameStr(env, opPackageName);
- const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
- status_t status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
- if (status != NO_ERROR || numModules == 0) {
- return (jint)status;
- }
-
- nModules = (struct sound_trigger_module_descriptor *)
- calloc(numModules, sizeof(struct sound_trigger_module_descriptor));
-
- status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
- ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules);
-
- if (status != NO_ERROR) {
- numModules = 0;
- }
-
- for (size_t i = 0; i < numModules; i++) {
- char str[SOUND_TRIGGER_MAX_STRING_LEN];
-
- jstring implementor = env->NewStringUTF(nModules[i].properties.implementor);
- jstring description = env->NewStringUTF(nModules[i].properties.description);
- SoundTrigger::guidToString(&nModules[i].properties.uuid,
- str,
- SOUND_TRIGGER_MAX_STRING_LEN);
- jstring uuid = env->NewStringUTF(str);
-
- ALOGV("listModules module %zu id %d description %s maxSoundModels %d",
- i, nModules[i].handle, nModules[i].properties.description,
- nModules[i].properties.max_sound_models);
-
- jobject newModuleDesc = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor,
- nModules[i].handle,
- implementor, description, uuid,
- nModules[i].properties.version,
- nModules[i].properties.max_sound_models,
- nModules[i].properties.max_key_phrases,
- nModules[i].properties.max_users,
- nModules[i].properties.recognition_modes,
- nModules[i].properties.capture_transition,
- nModules[i].properties.max_buffer_ms,
- nModules[i].properties.concurrent_capture,
- nModules[i].properties.power_consumption_mw,
- nModules[i].properties.trigger_in_event);
-
- env->DeleteLocalRef(implementor);
- env->DeleteLocalRef(description);
- env->DeleteLocalRef(uuid);
- if (newModuleDesc == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
- env->CallBooleanMethod(jModules, gArrayListMethods.add, newModuleDesc);
- }
-
-exit:
- free(nModules);
- return (jint) status;
-}
-
-static void
-android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz,
- jstring opPackageName, jobject weak_this)
-{
- ALOGV("setup");
-
- ScopedUtfChars opPackageNameStr(env, opPackageName);
- const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
- sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this);
-
- sound_trigger_module_handle_t handle =
- (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId);
-
- sp<SoundTrigger> module = SoundTrigger::attach(opPackageNameString16, handle, callback);
- if (module == 0) {
- return;
- }
-
- setSoundTrigger(env, thiz, module);
-}
-
-static void
-android_hardware_SoundTrigger_detach(JNIEnv *env, jobject thiz)
-{
- ALOGV("detach");
- sp<SoundTrigger> module = setSoundTrigger(env, thiz, 0);
- ALOGV("detach module %p", module.get());
- if (module != 0) {
- ALOGV("detach module->detach()");
- module->detach();
- }
-}
-
-static void
-android_hardware_SoundTrigger_finalize(JNIEnv *env, jobject thiz)
-{
- ALOGV("finalize");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module != 0) {
- ALOGW("SoundTrigger finalized without being detached");
- }
- android_hardware_SoundTrigger_detach(env, thiz);
-}
-
-static jint
-android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
- jobject jSoundModel, jintArray jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- jbyte *nData = NULL;
- struct sound_trigger_sound_model *nSoundModel;
- jbyteArray jData;
- sp<MemoryDealer> memoryDealer;
- sp<IMemory> memory;
- size_t size;
- sound_model_handle_t handle = 0;
- jobject jUuid;
- jstring jUuidString;
- const char *nUuidString;
-
- ALOGV("loadSoundModel");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (jHandle == NULL) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- jsize jHandleLen = env->GetArrayLength(jHandle);
- if (jHandleLen == 0) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- jint *nHandle = env->GetIntArrayElements(jHandle, NULL);
- if (nHandle == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (!env->IsInstanceOf(jSoundModel, gSoundModelClass)) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
- size_t offset;
- sound_trigger_sound_model_type_t type;
- if (env->IsInstanceOf(jSoundModel, gKeyphraseSoundModelClass)) {
- offset = sizeof(struct sound_trigger_phrase_sound_model);
- type = SOUND_MODEL_TYPE_KEYPHRASE;
- } else if (env->IsInstanceOf(jSoundModel, gGenericSoundModelClass)) {
- offset = sizeof(struct sound_trigger_generic_sound_model);
- type = SOUND_MODEL_TYPE_GENERIC;
- } else {
- offset = sizeof(struct sound_trigger_sound_model);
- type = SOUND_MODEL_TYPE_UNKNOWN;
- }
-
- jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.uuid);
- jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
- nUuidString = env->GetStringUTFChars(jUuidString, NULL);
- sound_trigger_uuid_t nUuid;
- SoundTrigger::stringToGuid(nUuidString, &nUuid);
- env->ReleaseStringUTFChars(jUuidString, nUuidString);
- env->DeleteLocalRef(jUuidString);
-
- sound_trigger_uuid_t nVendorUuid;
- jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.vendorUuid);
- if (jUuid != NULL) {
- jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
- nUuidString = env->GetStringUTFChars(jUuidString, NULL);
- SoundTrigger::stringToGuid(nUuidString, &nVendorUuid);
- env->ReleaseStringUTFChars(jUuidString, nUuidString);
- env->DeleteLocalRef(jUuidString);
- } else {
- SoundTrigger::stringToGuid("00000000-0000-0000-0000-000000000000", &nVendorUuid);
- }
-
- jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data);
- if (jData == NULL) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
- size = env->GetArrayLength(jData);
-
- nData = env->GetByteArrayElements(jData, NULL);
- if (jData == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
-
- memoryDealer = new MemoryDealer(offset + size, "SoundTrigge-JNI::LoadModel");
- if (memoryDealer == 0) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
- memory = memoryDealer->allocate(offset + size);
- if (memory == 0 || memory->unsecurePointer() == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
-
- nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer();
-
- nSoundModel->type = type;
- nSoundModel->uuid = nUuid;
- nSoundModel->vendor_uuid = nVendorUuid;
- nSoundModel->data_size = size;
- nSoundModel->data_offset = offset;
- memcpy((char *)nSoundModel + offset, nData, size);
- if (type == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_sound_model *phraseModel =
- (struct sound_trigger_phrase_sound_model *)nSoundModel;
-
- jobjectArray jPhrases =
- (jobjectArray)env->GetObjectField(jSoundModel, gKeyphraseSoundModelFields.keyphrases);
- if (jPhrases == NULL) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
-
- size_t numPhrases = env->GetArrayLength(jPhrases);
- phraseModel->num_phrases = numPhrases;
- ALOGV("loadSoundModel numPhrases %zu", numPhrases);
- for (size_t i = 0; i < numPhrases; i++) {
- jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
- phraseModel->phrases[i].id =
- env->GetIntField(jPhrase,gKeyphraseFields.id);
- phraseModel->phrases[i].recognition_mode =
- env->GetIntField(jPhrase,gKeyphraseFields.recognitionModes);
-
- jintArray jUsers = (jintArray)env->GetObjectField(jPhrase, gKeyphraseFields.users);
- phraseModel->phrases[i].num_users = env->GetArrayLength(jUsers);
- jint *nUsers = env->GetIntArrayElements(jUsers, NULL);
- memcpy(phraseModel->phrases[i].users,
- nUsers,
- phraseModel->phrases[i].num_users * sizeof(int));
- env->ReleaseIntArrayElements(jUsers, nUsers, 0);
- env->DeleteLocalRef(jUsers);
-
- jstring jLocale = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.locale);
- const char *nLocale = env->GetStringUTFChars(jLocale, NULL);
- strncpy(phraseModel->phrases[i].locale,
- nLocale,
- SOUND_TRIGGER_MAX_LOCALE_LEN);
- jstring jText = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.text);
- const char *nText = env->GetStringUTFChars(jText, NULL);
- strncpy(phraseModel->phrases[i].text,
- nText,
- SOUND_TRIGGER_MAX_STRING_LEN);
-
- env->ReleaseStringUTFChars(jLocale, nLocale);
- env->DeleteLocalRef(jLocale);
- env->ReleaseStringUTFChars(jText, nText);
- env->DeleteLocalRef(jText);
- ALOGV("loadSoundModel phrases %zu text %s locale %s",
- i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale);
- env->DeleteLocalRef(jPhrase);
- }
- env->DeleteLocalRef(jPhrases);
- } else if (type == SOUND_MODEL_TYPE_GENERIC) {
- /* No initialization needed */
- }
- status = module->loadSoundModel(memory, &handle);
- ALOGV("loadSoundModel status %d handle %d", status, handle);
-
-exit:
- if (nHandle != NULL) {
- nHandle[0] = (jint)handle;
- env->ReleaseIntArrayElements(jHandle, nHandle, NULL);
- }
- if (nData != NULL) {
- env->ReleaseByteArrayElements(jData, nData, NULL);
- }
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_unloadSoundModel(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("unloadSoundModel");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->unloadSoundModel((sound_model_handle_t)jHandle);
-
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz,
- jint jHandle, jobject jConfig)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("startRecognition");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
-
- if (!env->IsInstanceOf(jConfig, gRecognitionConfigClass)) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
-
- jbyteArray jData = (jbyteArray)env->GetObjectField(jConfig, gRecognitionConfigFields.data);
- jsize dataSize = 0;
- jbyte *nData = NULL;
- if (jData != NULL) {
- dataSize = env->GetArrayLength(jData);
- if (dataSize == 0) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- nData = env->GetByteArrayElements(jData, NULL);
- if (nData == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- }
-
- size_t totalSize = sizeof(struct sound_trigger_recognition_config) + dataSize;
- sp<MemoryDealer> memoryDealer =
- new MemoryDealer(totalSize, "SoundTrigge-JNI::StartRecognition");
- if (memoryDealer == 0) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- sp<IMemory> memory = memoryDealer->allocate(totalSize);
- if (memory == 0 || memory->unsecurePointer() == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (dataSize != 0) {
- memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config),
- nData,
- dataSize);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
- env->DeleteLocalRef(jData);
- struct sound_trigger_recognition_config *config =
- (struct sound_trigger_recognition_config *)memory->unsecurePointer();
- config->data_size = dataSize;
- config->data_offset = sizeof(struct sound_trigger_recognition_config);
- config->capture_requested = env->GetBooleanField(jConfig,
- gRecognitionConfigFields.captureRequested);
-
- config->num_phrases = 0;
- jobjectArray jPhrases =
- (jobjectArray)env->GetObjectField(jConfig, gRecognitionConfigFields.keyphrases);
- if (jPhrases != NULL) {
- config->num_phrases = env->GetArrayLength(jPhrases);
- }
- ALOGV("startRecognition num phrases %d", config->num_phrases);
- for (size_t i = 0; i < config->num_phrases; i++) {
- jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
- config->phrases[i].id = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.id);
- config->phrases[i].recognition_modes = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.recognitionModes);
- config->phrases[i].confidence_level = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.coarseConfidenceLevel);
- config->phrases[i].num_levels = 0;
- jobjectArray jConfidenceLevels = (jobjectArray)env->GetObjectField(jPhrase,
- gKeyphraseRecognitionExtraFields.confidenceLevels);
- if (jConfidenceLevels != NULL) {
- config->phrases[i].num_levels = env->GetArrayLength(jConfidenceLevels);
- }
- ALOGV("startRecognition phrase %zu num_levels %d", i, config->phrases[i].num_levels);
- for (size_t j = 0; j < config->phrases[i].num_levels; j++) {
- jobject jConfidenceLevel = env->GetObjectArrayElement(jConfidenceLevels, j);
- config->phrases[i].levels[j].user_id = env->GetIntField(jConfidenceLevel,
- gConfidenceLevelFields.userId);
- config->phrases[i].levels[j].level = env->GetIntField(jConfidenceLevel,
- gConfidenceLevelFields.confidenceLevel);
- env->DeleteLocalRef(jConfidenceLevel);
- }
- ALOGV("startRecognition phrases %zu", i);
- env->DeleteLocalRef(jConfidenceLevels);
- env->DeleteLocalRef(jPhrase);
- }
- env->DeleteLocalRef(jPhrases);
-
- status = module->startRecognition(jHandle, memory);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_stopRecognition(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("stopRecognition");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->stopRecognition(jHandle);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_getModelState(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("getModelState");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->getModelState(jHandle);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_setParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam, jint jValue)
-{
- ALOGV("setParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_NO_INIT;
- }
- return module->setParameter(jHandle, (sound_trigger_model_parameter_t) jModelParam, jValue);
-}
-
-static jint
-android_hardware_SoundTrigger_getParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam)
-{
- ALOGV("getParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- throwUnsupportedOperationException(env);
- return -1;
- }
-
- jint nValue;
- jint status = module->getParameter(jHandle,
- (sound_trigger_model_parameter_t) jModelParam, &nValue);
-
- switch (status) {
- case 0:
- return nValue;
- case -EINVAL:
- throwIllegalArgumentException(env);
- break;
- default:
- throwUnsupportedOperationException(env);
- break;
- }
-
- return -1;
-}
-
-static jobject
-android_hardware_SoundTrigger_queryParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam)
-{
- ALOGV("queryParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == nullptr) {
- return nullptr;
- }
-
- sound_trigger_model_parameter_range_t nRange;
- jint nValue = module->queryParameter(jHandle,
- (sound_trigger_model_parameter_t) jModelParam, &nRange);
-
- if (nValue != 0) {
- ALOGE("failed to query parameter error code: %d", nValue);
- return nullptr;
- }
-
- return env->NewObject(gModelParamRangeClass, gModelParamRangeCstor, nRange.start, nRange.end);
-}
-
-static const JNINativeMethod gMethods[] = {
- {"listModules",
- "(Ljava/lang/String;Ljava/util/ArrayList;)I",
- (void *)android_hardware_SoundTrigger_listModules},
-};
-
-
-static const JNINativeMethod gModuleMethods[] = {
- {"native_setup",
- "(Ljava/lang/String;Ljava/lang/Object;)V",
- (void *)android_hardware_SoundTrigger_setup},
- {"native_finalize",
- "()V",
- (void *)android_hardware_SoundTrigger_finalize},
- {"detach",
- "()V",
- (void *)android_hardware_SoundTrigger_detach},
- {"loadSoundModel",
- "(Landroid/hardware/soundtrigger/SoundTrigger$SoundModel;[I)I",
- (void *)android_hardware_SoundTrigger_loadSoundModel},
- {"unloadSoundModel",
- "(I)I",
- (void *)android_hardware_SoundTrigger_unloadSoundModel},
- {"startRecognition",
- "(ILandroid/hardware/soundtrigger/SoundTrigger$RecognitionConfig;)I",
- (void *)android_hardware_SoundTrigger_startRecognition},
- {"stopRecognition",
- "(I)I",
- (void *)android_hardware_SoundTrigger_stopRecognition},
- {"getModelState",
- "(I)I",
- (void *)android_hardware_SoundTrigger_getModelState},
- {"setParameter",
- "(III)I",
- (void *)android_hardware_SoundTrigger_setParameter},
- {"getParameter",
- "(II)I",
- (void *)android_hardware_SoundTrigger_getParameter},
- {"queryParameter",
- "(II)Landroid/hardware/soundtrigger/SoundTrigger$ModelParamRange;",
- (void *)android_hardware_SoundTrigger_queryParameter}
-};
-
-int register_android_hardware_SoundTrigger(JNIEnv *env)
-{
- jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
- gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
- gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
-
- jclass uuidClass = FindClassOrDie(env, "java/util/UUID");
- gUUIDClass = MakeGlobalRefOrDie(env, uuidClass);
- gUUIDMethods.toString = GetMethodIDOrDie(env, uuidClass, "toString", "()Ljava/lang/String;");
-
- jclass exUClass = FindClassOrDie(env, kUnsupportedOperationExceptionClassPathName);
- gUnsupportedOperationExceptionClass = MakeGlobalRefOrDie(env, exUClass);
-
- jclass exIClass = FindClassOrDie(env, kIllegalArgumentExceptionClassPathName);
- gIllegalArgumentExceptionClass = MakeGlobalRefOrDie(env, exIClass);
-
- jclass lClass = FindClassOrDie(env, kSoundTriggerClassPathName);
- gSoundTriggerClass = MakeGlobalRefOrDie(env, lClass);
-
- jclass moduleClass = FindClassOrDie(env, kModuleClassPathName);
- gModuleClass = MakeGlobalRefOrDie(env, moduleClass);
- gPostEventFromNative = GetStaticMethodIDOrDie(env, moduleClass, "postEventFromNative",
- "(Ljava/lang/Object;IIILjava/lang/Object;)V");
- gModuleFields.mNativeContext = GetFieldIDOrDie(env, moduleClass, "mNativeContext", "J");
- gModuleFields.mId = GetFieldIDOrDie(env, moduleClass, "mId", "I");
-
- jclass modulePropertiesClass = FindClassOrDie(env, kModulePropertiesClassPathName);
- gModulePropertiesClass = MakeGlobalRefOrDie(env, modulePropertiesClass);
- gModulePropertiesCstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>",
- "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZIZ)V");
-
- jclass soundModelClass = FindClassOrDie(env, kSoundModelClassPathName);
- gSoundModelClass = MakeGlobalRefOrDie(env, soundModelClass);
- gSoundModelFields.uuid = GetFieldIDOrDie(env, soundModelClass, "uuid", "Ljava/util/UUID;");
- gSoundModelFields.vendorUuid = GetFieldIDOrDie(env, soundModelClass, "vendorUuid",
- "Ljava/util/UUID;");
- gSoundModelFields.data = GetFieldIDOrDie(env, soundModelClass, "data", "[B");
-
- jclass genericSoundModelClass = FindClassOrDie(env, kGenericSoundModelClassPathName);
- gGenericSoundModelClass = MakeGlobalRefOrDie(env, genericSoundModelClass);
-
- jclass keyphraseClass = FindClassOrDie(env, kKeyphraseClassPathName);
- gKeyphraseClass = MakeGlobalRefOrDie(env, keyphraseClass);
- gKeyphraseFields.id = GetFieldIDOrDie(env, keyphraseClass, "id", "I");
- gKeyphraseFields.recognitionModes = GetFieldIDOrDie(env, keyphraseClass, "recognitionModes",
- "I");
- gKeyphraseFields.locale = GetFieldIDOrDie(env, keyphraseClass, "locale", "Ljava/lang/String;");
- gKeyphraseFields.text = GetFieldIDOrDie(env, keyphraseClass, "text", "Ljava/lang/String;");
- gKeyphraseFields.users = GetFieldIDOrDie(env, keyphraseClass, "users", "[I");
-
- jclass keyphraseSoundModelClass = FindClassOrDie(env, kKeyphraseSoundModelClassPathName);
- gKeyphraseSoundModelClass = MakeGlobalRefOrDie(env, keyphraseSoundModelClass);
- gKeyphraseSoundModelFields.keyphrases = GetFieldIDOrDie(env, keyphraseSoundModelClass,
- "keyphrases",
- "[Landroid/hardware/soundtrigger/SoundTrigger$Keyphrase;");
-
- jclass modelParamRangeClass = FindClassOrDie(env, kModelParamRangeClassPathName);
- gModelParamRangeClass = MakeGlobalRefOrDie(env, modelParamRangeClass);
- gModelParamRangeCstor = GetMethodIDOrDie(env, modelParamRangeClass, "<init>", "(II)V");
-
- jclass recognitionEventClass = FindClassOrDie(env, kRecognitionEventClassPathName);
- gRecognitionEventClass = MakeGlobalRefOrDie(env, recognitionEventClass);
- gRecognitionEventCstor = GetMethodIDOrDie(env, recognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
- jclass keyphraseRecognitionEventClass = FindClassOrDie(env,
- kKeyphraseRecognitionEventClassPathName);
- gKeyphraseRecognitionEventClass = MakeGlobalRefOrDie(env, keyphraseRecognitionEventClass);
- gKeyphraseRecognitionEventCstor = GetMethodIDOrDie(env, keyphraseRecognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
-
- jclass genericRecognitionEventClass = FindClassOrDie(env,
- kGenericRecognitionEventClassPathName);
- gGenericRecognitionEventClass = MakeGlobalRefOrDie(env, genericRecognitionEventClass);
- gGenericRecognitionEventCstor = GetMethodIDOrDie(env, genericRecognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
- jclass keyRecognitionConfigClass = FindClassOrDie(env, kRecognitionConfigClassPathName);
- gRecognitionConfigClass = MakeGlobalRefOrDie(env, keyRecognitionConfigClass);
- gRecognitionConfigFields.captureRequested = GetFieldIDOrDie(env, keyRecognitionConfigClass,
- "captureRequested", "Z");
- gRecognitionConfigFields.keyphrases = GetFieldIDOrDie(env, keyRecognitionConfigClass,
- "keyphrases", "[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;");
- gRecognitionConfigFields.data = GetFieldIDOrDie(env, keyRecognitionConfigClass, "data", "[B");
-
- jclass keyphraseRecognitionExtraClass = FindClassOrDie(env,
- kKeyphraseRecognitionExtraClassPathName);
- gKeyphraseRecognitionExtraClass = MakeGlobalRefOrDie(env, keyphraseRecognitionExtraClass);
- gKeyphraseRecognitionExtraCstor = GetMethodIDOrDie(env, keyphraseRecognitionExtraClass,
- "<init>", "(III[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
- gKeyphraseRecognitionExtraFields.id = GetFieldIDOrDie(env, gKeyphraseRecognitionExtraClass,
- "id", "I");
- gKeyphraseRecognitionExtraFields.recognitionModes = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "recognitionModes", "I");
- gKeyphraseRecognitionExtraFields.coarseConfidenceLevel = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "coarseConfidenceLevel", "I");
- gKeyphraseRecognitionExtraFields.confidenceLevels = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "confidenceLevels",
- "[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;");
-
- jclass confidenceLevelClass = FindClassOrDie(env, kConfidenceLevelClassPathName);
- gConfidenceLevelClass = MakeGlobalRefOrDie(env, confidenceLevelClass);
- gConfidenceLevelCstor = GetMethodIDOrDie(env, confidenceLevelClass, "<init>", "(II)V");
- gConfidenceLevelFields.userId = GetFieldIDOrDie(env, confidenceLevelClass, "userId", "I");
- gConfidenceLevelFields.confidenceLevel = GetFieldIDOrDie(env, confidenceLevelClass,
- "confidenceLevel", "I");
-
- jclass audioFormatClass = FindClassOrDie(env, kAudioFormatClassPathName);
- gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
- gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(IIII)V");
-
- jclass soundModelEventClass = FindClassOrDie(env, kSoundModelEventClassPathName);
- gSoundModelEventClass = MakeGlobalRefOrDie(env, soundModelEventClass);
- gSoundModelEventCstor = GetMethodIDOrDie(env, soundModelEventClass, "<init>", "(II[B)V");
-
-
- RegisterMethodsOrDie(env, kSoundTriggerClassPathName, gMethods, NELEM(gMethods));
- return RegisterMethodsOrDie(env, kModuleClassPathName, gModuleMethods, NELEM(gModuleMethods));
-}
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 342aba0..6cbc587 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -763,7 +763,7 @@
}
// get what we have for the metrics from the record session
- MediaAnalyticsItem *item = NULL;
+ mediametrics::Item *item = NULL;
status_t err = lpRecord->getMetrics(item);
if (err != OK) {
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index c5049ec..c979133 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1032,7 +1032,7 @@
}
// get what we have for the metrics from the track
- MediaAnalyticsItem *item = NULL;
+ mediametrics::Item *item = NULL;
status_t err = lpTrack->getMetrics(item);
if (err != OK) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 4733048..8c1ecae 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -33,6 +33,7 @@
// Static whitelist of open paths that the zygote is allowed to keep open.
static const char* kPathWhitelist[] = {
+ "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
"/apex/com.android.conscrypt/javalib/conscrypt.jar",
"/apex/com.android.ipsec/javalib/ike.jar",
"/apex/com.android.media/javalib/updatable-media.jar",
@@ -40,6 +41,7 @@
"/apex/com.android.sdkext/javalib/framework-sdkext.jar",
"/apex/com.android.telephony/javalib/telephony-common.jar",
"/apex/com.android.telephony/javalib/ims-common.jar",
+ "/apex/com.android.wifi/javalib/framework-wifi.jar",
"/dev/null",
"/dev/socket/zygote",
"/dev/socket/zygote_secondary",
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index ab97fdd..0c2129f 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2467,4 +2467,9 @@
// CATEGORY: SETTINGS
// OS: R
ACCOUNT_WORK = 1807;
+
+ // OPEN: Settings > Developer Options > Bug report handler
+ // CATEGORY: SETTINGS
+ // OS: R
+ SETTINGS_BUGREPORT_HANDLER = 1808;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4ea574d..6a1ec6c 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -219,6 +219,12 @@
}
optional Gesture gesture = 74;
+ message GestureNavigation {
+ optional SettingProto back_gesture_inset_scale_left = 1 [(android.privacy).dest = DEST_AUTOMATIC];
+ optional SettingProto back_gesture_inset_scale_right = 2 [(android.privacy).dest = DEST_AUTOMATIC];
+ }
+ optional GestureNavigation gesture_navigation = 77;
+
optional SettingProto immersive_mode_confirmations = 24 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Incall {
@@ -316,6 +322,7 @@
optional SettingProto multi_press_timeout = 38 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto navigation_mode = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
message NfcPayment {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -564,5 +571,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 77;
+ // Next tag = 78;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c925744..cb5b4a5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4410,6 +4410,12 @@
<permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
android:protectionLevel="signature|privileged" />
+ <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on
+ implementations which do not support running both concurrently.
+ @hide -->
+ <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER"
+ android:protectionLevel="signature|privileged" />
+
<!-- Must be required by system/priv apps implementing sound trigger detection services
@hide
@SystemApi -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0046d82..03f8ebd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2544,6 +2544,28 @@
<string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
>com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
+ <!-- Component name of the activity that shows the usb containment status. -->
+ <string name="config_usbContaminantActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbContaminantActivity</string>
+
+ <!-- Component name of the activity that shows the request for access to a usb device. -->
+ <string name="config_usbPermissionActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbPermissionActivity</string>
+
+ <!-- Component name of the activity that shows more information about a usb accessory. -->
+ <string name="config_usbAccessoryUriActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbAccessoryUriActivity</string>
+
+ <!-- Component name of the activity that confirms the activity to start when a usb device is
+ plugged in. -->
+ <string name="config_usbConfirmActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbConfirmActivity</string>
+
+ <!-- Component name of the activity to select the activity to start when a usb device is plugged
+ in. -->
+ <string name="config_usbResolverActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
+
<!-- Name of the dialog that is used to request the user's consent to VPN connection -->
<string name="config_customVpnConfirmDialogComponent" translatable="false"
>com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog</string>
@@ -2674,11 +2696,45 @@
property. If this is false, then the following recents config flags are ignored. -->
<bool name="config_hasRecents">true</bool>
- <!-- Component name for the activity that will be presenting the Recents UI, which will receive special permissions for API related
- to fetching and presenting recent tasks. The default configuration uses Launcehr3QuickStep as default launcher and points to
- the corresponding recents component. When using a different default launcher, change this appropriately or use the default
- systemui implementation: com.android.systemui/.recents.RecentsActivity -->
- <string name="config_recentsComponentName" translatable="false">com.android.launcher3/com.android.quickstep.RecentsActivity</string>
+ <!-- Component name for the activity that will be presenting the Recents UI, which will receive
+ special permissions for API related to fetching and presenting recent tasks. The default
+ configuration uses Launcehr3QuickStep as default launcher and points to the corresponding
+ recents component. When using a different default launcher, change this appropriately or
+ use the default systemui implementation: com.android.systemui/.recents.RecentsActivity -->
+ <string name="config_recentsComponentName" translatable="false"
+ >com.android.launcher3/com.android.quickstep.RecentsActivity</string>
+
+ <!-- SystemUi service component -->
+ <string name="config_systemUIServiceComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.SystemUIService</string>
+
+ <!-- Keyguard component -->
+ <string name="config_keyguardComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
+
+ <!-- Screen record dialog component -->
+ <string name="config_screenRecorderComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.screenrecord.ScreenRecordDialog</string>
+
+ <!-- The component name of a special dock app that merely launches a dream.
+ We don't want to launch this app when docked because it causes an unnecessary
+ activity transition. We just want to start the dream. -->
+ <string name="config_somnambulatorComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.Somnambulator</string>
+
+ <!-- The component name of a special dock app that merely launches a dream.
+ We don't want to launch this app when docked because it causes an unnecessary
+ activity transition. We just want to start the dream.. -->
+ <string name="config_screenshotServiceComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.screenshot.TakeScreenshotService</string>
+
+ <!-- The component notified when there is an error while taking a screenshot. -->
+ <string name="config_screenshotErrorReceiverComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.screenshot.ScreenshotServiceErrorReceiver</string>
+
+ <!-- The component for the activity shown to grant permissions for a slice. -->
+ <string name="config_slicePermissionComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.SlicePermissionActivity</string>
<!-- The minimum number of visible recent tasks to be presented to the user through the
SystemUI. Can be -1 if there is no minimum limit. -->
@@ -2851,10 +2907,6 @@
<!-- Whether to use voip audio mode for ims call -->
<bool name="config_use_voip_mode_for_ims">false</bool>
- <!-- ImsService package name to bind to by default. If none is specified in an overlay, an
- empty string is passed in -->
- <string name="config_ims_package"/>
-
<!-- String array containing numbers that shouldn't be logged. Country-specific. -->
<string-array name="unloggable_phone_numbers" />
@@ -3032,9 +3084,6 @@
<!-- Specifies the maximum burn-in offset vertically. -->
<integer name="config_burnInProtectionMaxVerticalOffset">0</integer>
- <!-- Keyguard component -->
- <string name="config_keyguardComponent" translatable="false">com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
-
<!-- Limit for the number of face templates per user -->
<integer name="config_faceMaxTemplatesPerUser">1</integer>
@@ -3073,7 +3122,9 @@
The path is assumed to be specified in display coordinates with pixel units and in
the display's native orientation, with the origin of the coordinate system at the
- center top of the display.
+ center top of the display. Optionally, you can append either `@left` or `@right` to the
+ end of the path string, in order to change the path origin to either the top left,
+ or top right of the display.
To facilitate writing device-independent emulation overlays, the marker `@dp` can be
appended after the path string to interpret coordinates in dp instead of px units.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index de1b5ba..ab10738 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4946,8 +4946,12 @@
<!-- Resolver target actions strings -->
<!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
<string name="pin_target">Pin</string>
+ <!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
+ <string name="pin_specific_target">Pin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string>
<!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
- <string name="unpin_target">Unpin</string>
+ <string name="unpin_target">Unpin </string>
+ <!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
+ <string name="unpin_specific_target">Unpin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string>
<!-- View application info for a target. -->
<string name="app_info">App info</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1b6583d..a8d30c1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -295,7 +295,6 @@
<java-symbol type="bool" name="config_enableBurnInProtection" />
<java-symbol type="bool" name="config_hotswapCapable" />
<java-symbol type="bool" name="config_mms_content_disposition_support" />
- <java-symbol type="string" name="config_ims_package" />
<java-symbol type="string" name="config_wwan_network_service_package" />
<java-symbol type="string" name="config_wlan_network_service_package" />
<java-symbol type="string" name="config_wwan_network_service_class" />
@@ -360,6 +359,17 @@
<java-symbol type="bool" name="config_use16BitTaskSnapshotPixelFormat" />
<java-symbol type="bool" name="config_hasRecents" />
<java-symbol type="string" name="config_recentsComponentName" />
+ <java-symbol type="string" name="config_systemUIServiceComponent" />
+ <java-symbol type="string" name="config_screenRecorderComponent" />
+ <java-symbol type="string" name="config_somnambulatorComponent" />
+ <java-symbol type="string" name="config_screenshotServiceComponent" />
+ <java-symbol type="string" name="config_screenshotErrorReceiverComponent" />
+ <java-symbol type="string" name="config_slicePermissionComponent" />
+ <java-symbol type="string" name="config_usbContaminantActivity" />
+ <java-symbol type="string" name="config_usbPermissionActivity" />
+ <java-symbol type="string" name="config_usbAccessoryUriActivity" />
+ <java-symbol type="string" name="config_usbConfirmActivity" />
+ <java-symbol type="string" name="config_usbResolverActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
@@ -2947,6 +2957,8 @@
<!-- Resolver target actions -->
<java-symbol type="array" name="resolver_target_actions_pin" />
<java-symbol type="array" name="resolver_target_actions_unpin" />
+ <java-symbol type="string" name="pin_specific_target" />
+ <java-symbol type="string" name="unpin_specific_target" />
<java-symbol type="array" name="non_removable_euicc_slots" />
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 1a48260..cce38f6 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -19,11 +19,9 @@
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -118,7 +116,7 @@
consumers.put(ITYPE_NAVIGATION_BAR, navConsumer);
mController = new InsetsAnimationControlImpl(consumers,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- () -> mMockTransactionApplier, mMockController);
+ () -> mMockTransactionApplier, mMockController, 10 /* durationMs */);
}
@Test
@@ -131,7 +129,8 @@
@Test
public void testChangeInsets() {
- mController.changeInsets(Insets.of(0, 30, 40, 0));
+ mController.setInsetsAndAlpha(Insets.of(0, 30, 40, 0), 1f /* alpha */,
+ 0f /* fraction */);
mController.applyChangeInsets(new InsetsState());
assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
@@ -150,24 +149,24 @@
@Test
public void testFinishing() {
when(mMockController.getState()).thenReturn(mInsetsState);
- mController.finish(navigationBars());
+ mController.finish(true /* shown */);
mController.applyChangeInsets(mInsetsState);
- assertFalse(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible());
+ assertTrue(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible());
assertTrue(mInsetsState.getSource(ITYPE_NAVIGATION_BAR).isVisible());
- assertEquals(Insets.of(0, 0, 100, 0), mController.getCurrentInsets());
- verify(mMockController).notifyFinished(eq(mController), eq(navigationBars()));
+ assertEquals(Insets.of(0, 100, 100, 0), mController.getCurrentInsets());
+ verify(mMockController).notifyFinished(eq(mController), eq(true /* shown */));
}
@Test
public void testCancelled() {
mController.onCancelled();
try {
- mController.changeInsets(Insets.NONE);
+ mController.setInsetsAndAlpha(Insets.NONE, 1f /*alpha */, 0f /* fraction */);
fail("Expected exception to be thrown");
} catch (IllegalStateException ignored) {
}
verify(mMockListener).onCancelled();
- mController.finish(navigationBars());
+ mController.finish(true /* shown */);
}
private void assertPosition(Matrix m, Rect original, Rect transformed) {
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index e4d8279..a89fc1e 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -123,7 +123,7 @@
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(statusBars(), mockListener);
+ mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
verify(mockListener).onReady(any(), anyInt());
mController.onControlsChanged(new InsetsSourceControl[0]);
verify(mockListener).onCancelled();
@@ -135,7 +135,7 @@
mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200));
WindowInsetsAnimationControlListener controlListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(0, controlListener);
+ mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, controlListener);
verify(controlListener).onCancelled();
verify(controlListener, never()).onReady(any(), anyInt());
}
@@ -331,12 +331,13 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(statusBars(), mockListener);
+ mController.controlWindowInsetsAnimation(statusBars(), 0 /* durationMs */,
+ mockListener);
ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
- controllerCaptor.getValue().finish(0 /* shownTypes */);
+ controllerCaptor.getValue().finish(false /* shown */);
});
waitUntilNextFrame();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index 9002c2c..ffc925f 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -111,7 +111,7 @@
}
@Test
- public void testWriteToParcel_Exceptionally() throws Exception {
+ public void testWriteToParcel_Exception() throws Exception {
Parcel parcel = Parcel.obtain();
AndroidFuture<Integer> future1 = new AndroidFuture<>();
future1.completeExceptionally(new UnsupportedOperationException());
@@ -123,4 +123,30 @@
expectThrows(ExecutionException.class, future2::get);
assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
}
+
+ @Test
+ public void testWriteToParcel_Incomplete() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ AndroidFuture<Integer> future1 = new AndroidFuture<>();
+ future1.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
+ future2.complete(5);
+ assertThat(future1.get()).isEqualTo(5);
+ }
+
+ @Test
+ public void testWriteToParcel_Incomplete_Exception() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ AndroidFuture<Integer> future1 = new AndroidFuture<>();
+ future1.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
+ future2.completeExceptionally(new UnsupportedOperationException());
+ ExecutionException executionException =
+ expectThrows(ExecutionException.class, future1::get);
+ assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
+ }
}
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index e16d1ca..1472b90 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -24,8 +24,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import android.content.Context;
+import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
@@ -54,9 +59,15 @@
// This raises a `SecurityException` if the device is locked. Calling either `Context`
// method results in a broadcast of `android.intent.action. USER_PRESENT`. Only the system
// process is allowed to broadcast that `Intent`.
+ Resources res = mock(Resources.class);
mContext = Mockito.spy(Context.class);
- Mockito.doNothing().when(mContext).sendBroadcastAsUser(any(), any());
- Mockito.doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+ doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
+ doReturn(res).when(mContext).getResources();
+ doReturn("com.android.systemui/.Service").when(res).getString(
+ eq(com.android.internal.R.string.config_screenshotServiceComponent));
+ doReturn("com.android.systemui/.ErrorReceiver").when(res).getString(
+ eq(com.android.internal.R.string.config_screenshotErrorReceiverComponent));
mHandler = new Handler(Looper.getMainLooper());
mScreenshotHelper = new ScreenshotHelper(mContext);
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 80098c5..0574775 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -157,6 +157,7 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
+ <assign-permission name="android.permission.PREEMPT_SOUND_TRIGGER" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 322cbd7..e07edd4 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -145,7 +145,7 @@
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_CONFIGURATION"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
- <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+ <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
<permission name="android.permission.DUMP"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
new file mode 100644
index 0000000..d8af726
--- /dev/null
+++ b/framework-jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule android.hidl.** android.internal.hidl.@1
+rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 2648008..5ad93f4 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.FontVariationAxis;
import android.text.TextUtils;
@@ -57,6 +58,7 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public long mNativePtr;
// Points native font family builder. Must be zero after freezing this family.
@@ -65,6 +67,7 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public FontFamily() {
mBuilderPtr = nInitBuilder(null, 0);
mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -73,6 +76,7 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public FontFamily(@Nullable String[] langs, int variant) {
final String langsString;
if (langs == null || langs.length == 0) {
@@ -94,6 +98,7 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public boolean freeze() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen");
@@ -110,6 +115,7 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public void abortCreation() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -121,6 +127,7 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
int italic) {
if (mBuilderPtr == 0) {
@@ -144,6 +151,7 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
int weight, int italic) {
if (mBuilderPtr == 0) {
@@ -171,6 +179,7 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
boolean isAsset, int ttcIndex, int weight, int isItalic,
FontVariationAxis[] axes) {
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 45b2de5..c6586ec 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -784,9 +784,7 @@
mFillPaint.setDither(st.mDither);
mFillPaint.setColorFilter(colorFilter);
if (colorFilter != null && st.mSolidColors == null) {
- // If we don't have a solid color and we don't have a gradient,
- // the app is stroking the shape, set the color to transparent
- mFillPaint.setColor(st.mGradientColors != null ? mAlpha << 24 : 0);
+ mFillPaint.setColor(mAlpha << 24);
}
if (haveStroke) {
mStrokePaint.setAlpha(currStrokeAlpha);
diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt
deleted file mode 100644
index 4b2331d..0000000
--- a/jarjar_rules_hidl.txt
+++ /dev/null
@@ -1 +0,0 @@
-rule android.hidl.** android.internal.hidl.@1
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 572fa8c..048dee6 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -65,6 +65,9 @@
/** Key prefix for VPN. */
public static final String VPN = "VPN_";
+ /** Key prefix for platform VPNs. */
+ public static final String PLATFORM_VPN = "PLATFORM_VPN_";
+
/** Key prefix for WIFI. */
public static final String WIFI = "WIFI_";
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index c276a23..c462eb7 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -10,1439 +10,1466 @@
/* 6 */ {'B', 'a', 's', 's'},
/* 7 */ {'B', 'e', 'n', 'g'},
/* 8 */ {'B', 'r', 'a', 'h'},
- /* 9 */ {'C', 'a', 'n', 's'},
- /* 10 */ {'C', 'a', 'r', 'i'},
- /* 11 */ {'C', 'h', 'a', 'm'},
- /* 12 */ {'C', 'h', 'e', 'r'},
- /* 13 */ {'C', 'o', 'p', 't'},
- /* 14 */ {'C', 'p', 'r', 't'},
- /* 15 */ {'C', 'y', 'r', 'l'},
- /* 16 */ {'D', 'e', 'v', 'a'},
- /* 17 */ {'E', 'g', 'y', 'p'},
- /* 18 */ {'E', 't', 'h', 'i'},
- /* 19 */ {'G', 'e', 'o', 'r'},
- /* 20 */ {'G', 'o', 't', 'h'},
- /* 21 */ {'G', 'r', 'e', 'k'},
- /* 22 */ {'G', 'u', 'j', 'r'},
- /* 23 */ {'G', 'u', 'r', 'u'},
- /* 24 */ {'H', 'a', 'n', 's'},
- /* 25 */ {'H', 'a', 'n', 't'},
- /* 26 */ {'H', 'a', 't', 'r'},
- /* 27 */ {'H', 'e', 'b', 'r'},
- /* 28 */ {'H', 'l', 'u', 'w'},
- /* 29 */ {'H', 'm', 'n', 'g'},
- /* 30 */ {'I', 't', 'a', 'l'},
- /* 31 */ {'J', 'p', 'a', 'n'},
- /* 32 */ {'K', 'a', 'l', 'i'},
- /* 33 */ {'K', 'a', 'n', 'a'},
- /* 34 */ {'K', 'h', 'a', 'r'},
- /* 35 */ {'K', 'h', 'm', 'r'},
- /* 36 */ {'K', 'n', 'd', 'a'},
- /* 37 */ {'K', 'o', 'r', 'e'},
- /* 38 */ {'L', 'a', 'n', 'a'},
- /* 39 */ {'L', 'a', 'o', 'o'},
- /* 40 */ {'L', 'a', 't', 'n'},
- /* 41 */ {'L', 'e', 'p', 'c'},
- /* 42 */ {'L', 'i', 'n', 'a'},
- /* 43 */ {'L', 'i', 's', 'u'},
- /* 44 */ {'L', 'y', 'c', 'i'},
- /* 45 */ {'L', 'y', 'd', 'i'},
- /* 46 */ {'M', 'a', 'n', 'd'},
- /* 47 */ {'M', 'a', 'n', 'i'},
- /* 48 */ {'M', 'e', 'r', 'c'},
- /* 49 */ {'M', 'l', 'y', 'm'},
- /* 50 */ {'M', 'o', 'n', 'g'},
- /* 51 */ {'M', 'r', 'o', 'o'},
- /* 52 */ {'M', 'y', 'm', 'r'},
- /* 53 */ {'N', 'a', 'r', 'b'},
- /* 54 */ {'N', 'k', 'o', 'o'},
- /* 55 */ {'O', 'g', 'a', 'm'},
- /* 56 */ {'O', 'r', 'k', 'h'},
- /* 57 */ {'O', 'r', 'y', 'a'},
- /* 58 */ {'O', 's', 'g', 'e'},
- /* 59 */ {'P', 'a', 'u', 'c'},
- /* 60 */ {'P', 'h', 'l', 'i'},
- /* 61 */ {'P', 'h', 'n', 'x'},
- /* 62 */ {'P', 'l', 'r', 'd'},
- /* 63 */ {'P', 'r', 't', 'i'},
- /* 64 */ {'R', 'u', 'n', 'r'},
- /* 65 */ {'S', 'a', 'm', 'r'},
- /* 66 */ {'S', 'a', 'r', 'b'},
- /* 67 */ {'S', 'a', 'u', 'r'},
- /* 68 */ {'S', 'g', 'n', 'w'},
- /* 69 */ {'S', 'i', 'n', 'h'},
- /* 70 */ {'S', 'o', 'r', 'a'},
- /* 71 */ {'S', 'y', 'r', 'c'},
- /* 72 */ {'T', 'a', 'l', 'e'},
- /* 73 */ {'T', 'a', 'l', 'u'},
- /* 74 */ {'T', 'a', 'm', 'l'},
- /* 75 */ {'T', 'a', 'n', 'g'},
- /* 76 */ {'T', 'a', 'v', 't'},
- /* 77 */ {'T', 'e', 'l', 'u'},
- /* 78 */ {'T', 'f', 'n', 'g'},
- /* 79 */ {'T', 'h', 'a', 'a'},
- /* 80 */ {'T', 'h', 'a', 'i'},
- /* 81 */ {'T', 'i', 'b', 't'},
- /* 82 */ {'U', 'g', 'a', 'r'},
- /* 83 */ {'V', 'a', 'i', 'i'},
- /* 84 */ {'X', 'p', 'e', 'o'},
- /* 85 */ {'X', 's', 'u', 'x'},
- /* 86 */ {'Y', 'i', 'i', 'i'},
- /* 87 */ {'~', '~', '~', 'A'},
- /* 88 */ {'~', '~', '~', 'B'},
+ /* 9 */ {'C', 'a', 'k', 'm'},
+ /* 10 */ {'C', 'a', 'n', 's'},
+ /* 11 */ {'C', 'a', 'r', 'i'},
+ /* 12 */ {'C', 'h', 'a', 'm'},
+ /* 13 */ {'C', 'h', 'e', 'r'},
+ /* 14 */ {'C', 'o', 'p', 't'},
+ /* 15 */ {'C', 'p', 'r', 't'},
+ /* 16 */ {'C', 'y', 'r', 'l'},
+ /* 17 */ {'D', 'e', 'v', 'a'},
+ /* 18 */ {'E', 'g', 'y', 'p'},
+ /* 19 */ {'E', 't', 'h', 'i'},
+ /* 20 */ {'G', 'e', 'o', 'r'},
+ /* 21 */ {'G', 'o', 'n', 'g'},
+ /* 22 */ {'G', 'o', 'n', 'm'},
+ /* 23 */ {'G', 'o', 't', 'h'},
+ /* 24 */ {'G', 'r', 'e', 'k'},
+ /* 25 */ {'G', 'u', 'j', 'r'},
+ /* 26 */ {'G', 'u', 'r', 'u'},
+ /* 27 */ {'H', 'a', 'n', 's'},
+ /* 28 */ {'H', 'a', 'n', 't'},
+ /* 29 */ {'H', 'a', 't', 'r'},
+ /* 30 */ {'H', 'e', 'b', 'r'},
+ /* 31 */ {'H', 'l', 'u', 'w'},
+ /* 32 */ {'H', 'm', 'n', 'g'},
+ /* 33 */ {'H', 'm', 'n', 'p'},
+ /* 34 */ {'I', 't', 'a', 'l'},
+ /* 35 */ {'J', 'p', 'a', 'n'},
+ /* 36 */ {'K', 'a', 'l', 'i'},
+ /* 37 */ {'K', 'a', 'n', 'a'},
+ /* 38 */ {'K', 'h', 'a', 'r'},
+ /* 39 */ {'K', 'h', 'm', 'r'},
+ /* 40 */ {'K', 'n', 'd', 'a'},
+ /* 41 */ {'K', 'o', 'r', 'e'},
+ /* 42 */ {'L', 'a', 'n', 'a'},
+ /* 43 */ {'L', 'a', 'o', 'o'},
+ /* 44 */ {'L', 'a', 't', 'n'},
+ /* 45 */ {'L', 'e', 'p', 'c'},
+ /* 46 */ {'L', 'i', 'n', 'a'},
+ /* 47 */ {'L', 'i', 's', 'u'},
+ /* 48 */ {'L', 'y', 'c', 'i'},
+ /* 49 */ {'L', 'y', 'd', 'i'},
+ /* 50 */ {'M', 'a', 'n', 'd'},
+ /* 51 */ {'M', 'a', 'n', 'i'},
+ /* 52 */ {'M', 'e', 'r', 'c'},
+ /* 53 */ {'M', 'l', 'y', 'm'},
+ /* 54 */ {'M', 'o', 'n', 'g'},
+ /* 55 */ {'M', 'r', 'o', 'o'},
+ /* 56 */ {'M', 'y', 'm', 'r'},
+ /* 57 */ {'N', 'a', 'r', 'b'},
+ /* 58 */ {'N', 'k', 'o', 'o'},
+ /* 59 */ {'N', 's', 'h', 'u'},
+ /* 60 */ {'O', 'g', 'a', 'm'},
+ /* 61 */ {'O', 'r', 'k', 'h'},
+ /* 62 */ {'O', 'r', 'y', 'a'},
+ /* 63 */ {'O', 's', 'g', 'e'},
+ /* 64 */ {'P', 'a', 'u', 'c'},
+ /* 65 */ {'P', 'h', 'l', 'i'},
+ /* 66 */ {'P', 'h', 'n', 'x'},
+ /* 67 */ {'P', 'l', 'r', 'd'},
+ /* 68 */ {'P', 'r', 't', 'i'},
+ /* 69 */ {'R', 'u', 'n', 'r'},
+ /* 70 */ {'S', 'a', 'm', 'r'},
+ /* 71 */ {'S', 'a', 'r', 'b'},
+ /* 72 */ {'S', 'a', 'u', 'r'},
+ /* 73 */ {'S', 'g', 'n', 'w'},
+ /* 74 */ {'S', 'i', 'n', 'h'},
+ /* 75 */ {'S', 'o', 'g', 'd'},
+ /* 76 */ {'S', 'o', 'r', 'a'},
+ /* 77 */ {'S', 'o', 'y', 'o'},
+ /* 78 */ {'S', 'y', 'r', 'c'},
+ /* 79 */ {'T', 'a', 'l', 'e'},
+ /* 80 */ {'T', 'a', 'l', 'u'},
+ /* 81 */ {'T', 'a', 'm', 'l'},
+ /* 82 */ {'T', 'a', 'n', 'g'},
+ /* 83 */ {'T', 'a', 'v', 't'},
+ /* 84 */ {'T', 'e', 'l', 'u'},
+ /* 85 */ {'T', 'f', 'n', 'g'},
+ /* 86 */ {'T', 'h', 'a', 'a'},
+ /* 87 */ {'T', 'h', 'a', 'i'},
+ /* 88 */ {'T', 'i', 'b', 't'},
+ /* 89 */ {'U', 'g', 'a', 'r'},
+ /* 90 */ {'V', 'a', 'i', 'i'},
+ /* 91 */ {'W', 'c', 'h', 'o'},
+ /* 92 */ {'X', 'p', 'e', 'o'},
+ /* 93 */ {'X', 's', 'u', 'x'},
+ /* 94 */ {'Y', 'i', 'i', 'i'},
+ /* 95 */ {'~', '~', '~', 'A'},
+ /* 96 */ {'~', '~', '~', 'B'},
};
const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
- {0x61610000u, 40u}, // aa -> Latn
- {0xA0000000u, 40u}, // aai -> Latn
- {0xA8000000u, 40u}, // aak -> Latn
- {0xD0000000u, 40u}, // aau -> Latn
- {0x61620000u, 15u}, // ab -> Cyrl
- {0xA0200000u, 40u}, // abi -> Latn
- {0xC4200000u, 40u}, // abr -> Latn
- {0xCC200000u, 40u}, // abt -> Latn
- {0xE0200000u, 40u}, // aby -> Latn
- {0x8C400000u, 40u}, // acd -> Latn
- {0x90400000u, 40u}, // ace -> Latn
- {0x9C400000u, 40u}, // ach -> Latn
- {0x80600000u, 40u}, // ada -> Latn
- {0x90600000u, 40u}, // ade -> Latn
- {0xA4600000u, 40u}, // adj -> Latn
- {0xE0600000u, 15u}, // ady -> Cyrl
- {0xE4600000u, 40u}, // adz -> Latn
+ {0x61610000u, 44u}, // aa -> Latn
+ {0xA0000000u, 44u}, // aai -> Latn
+ {0xA8000000u, 44u}, // aak -> Latn
+ {0xD0000000u, 44u}, // aau -> Latn
+ {0x61620000u, 16u}, // ab -> Cyrl
+ {0xA0200000u, 44u}, // abi -> Latn
+ {0xC0200000u, 16u}, // abq -> Cyrl
+ {0xC4200000u, 44u}, // abr -> Latn
+ {0xCC200000u, 44u}, // abt -> Latn
+ {0xE0200000u, 44u}, // aby -> Latn
+ {0x8C400000u, 44u}, // acd -> Latn
+ {0x90400000u, 44u}, // ace -> Latn
+ {0x9C400000u, 44u}, // ach -> Latn
+ {0x80600000u, 44u}, // ada -> Latn
+ {0x90600000u, 44u}, // ade -> Latn
+ {0xA4600000u, 44u}, // adj -> Latn
+ {0xE0600000u, 16u}, // ady -> Cyrl
+ {0xE4600000u, 44u}, // adz -> Latn
{0x61650000u, 4u}, // ae -> Avst
{0x84800000u, 1u}, // aeb -> Arab
- {0xE0800000u, 40u}, // aey -> Latn
- {0x61660000u, 40u}, // af -> Latn
- {0x88C00000u, 40u}, // agc -> Latn
- {0x8CC00000u, 40u}, // agd -> Latn
- {0x98C00000u, 40u}, // agg -> Latn
- {0xB0C00000u, 40u}, // agm -> Latn
- {0xB8C00000u, 40u}, // ago -> Latn
- {0xC0C00000u, 40u}, // agq -> Latn
- {0x80E00000u, 40u}, // aha -> Latn
- {0xACE00000u, 40u}, // ahl -> Latn
+ {0xE0800000u, 44u}, // aey -> Latn
+ {0x61660000u, 44u}, // af -> Latn
+ {0x88C00000u, 44u}, // agc -> Latn
+ {0x8CC00000u, 44u}, // agd -> Latn
+ {0x98C00000u, 44u}, // agg -> Latn
+ {0xB0C00000u, 44u}, // agm -> Latn
+ {0xB8C00000u, 44u}, // ago -> Latn
+ {0xC0C00000u, 44u}, // agq -> Latn
+ {0x80E00000u, 44u}, // aha -> Latn
+ {0xACE00000u, 44u}, // ahl -> Latn
{0xB8E00000u, 0u}, // aho -> Ahom
- {0x99200000u, 40u}, // ajg -> Latn
- {0x616B0000u, 40u}, // ak -> Latn
- {0xA9400000u, 85u}, // akk -> Xsux
- {0x81600000u, 40u}, // ala -> Latn
- {0xA1600000u, 40u}, // ali -> Latn
- {0xB5600000u, 40u}, // aln -> Latn
- {0xCD600000u, 15u}, // alt -> Cyrl
- {0x616D0000u, 18u}, // am -> Ethi
- {0xB1800000u, 40u}, // amm -> Latn
- {0xB5800000u, 40u}, // amn -> Latn
- {0xB9800000u, 40u}, // amo -> Latn
- {0xBD800000u, 40u}, // amp -> Latn
- {0x89A00000u, 40u}, // anc -> Latn
- {0xA9A00000u, 40u}, // ank -> Latn
- {0xB5A00000u, 40u}, // ann -> Latn
- {0xE1A00000u, 40u}, // any -> Latn
- {0xA5C00000u, 40u}, // aoj -> Latn
- {0xB1C00000u, 40u}, // aom -> Latn
- {0xE5C00000u, 40u}, // aoz -> Latn
+ {0x99200000u, 44u}, // ajg -> Latn
+ {0x616B0000u, 44u}, // ak -> Latn
+ {0xA9400000u, 93u}, // akk -> Xsux
+ {0x81600000u, 44u}, // ala -> Latn
+ {0xA1600000u, 44u}, // ali -> Latn
+ {0xB5600000u, 44u}, // aln -> Latn
+ {0xCD600000u, 16u}, // alt -> Cyrl
+ {0x616D0000u, 19u}, // am -> Ethi
+ {0xB1800000u, 44u}, // amm -> Latn
+ {0xB5800000u, 44u}, // amn -> Latn
+ {0xB9800000u, 44u}, // amo -> Latn
+ {0xBD800000u, 44u}, // amp -> Latn
+ {0x89A00000u, 44u}, // anc -> Latn
+ {0xA9A00000u, 44u}, // ank -> Latn
+ {0xB5A00000u, 44u}, // ann -> Latn
+ {0xE1A00000u, 44u}, // any -> Latn
+ {0xA5C00000u, 44u}, // aoj -> Latn
+ {0xB1C00000u, 44u}, // aom -> Latn
+ {0xE5C00000u, 44u}, // aoz -> Latn
{0x89E00000u, 1u}, // apc -> Arab
{0x8DE00000u, 1u}, // apd -> Arab
- {0x91E00000u, 40u}, // ape -> Latn
- {0xC5E00000u, 40u}, // apr -> Latn
- {0xC9E00000u, 40u}, // aps -> Latn
- {0xE5E00000u, 40u}, // apz -> Latn
+ {0x91E00000u, 44u}, // ape -> Latn
+ {0xC5E00000u, 44u}, // apr -> Latn
+ {0xC9E00000u, 44u}, // aps -> Latn
+ {0xE5E00000u, 44u}, // apz -> Latn
{0x61720000u, 1u}, // ar -> Arab
- {0x61725842u, 88u}, // ar-XB -> ~~~B
+ {0x61725842u, 96u}, // ar-XB -> ~~~B
{0x8A200000u, 2u}, // arc -> Armi
- {0x9E200000u, 40u}, // arh -> Latn
- {0xB6200000u, 40u}, // arn -> Latn
- {0xBA200000u, 40u}, // aro -> Latn
+ {0x9E200000u, 44u}, // arh -> Latn
+ {0xB6200000u, 44u}, // arn -> Latn
+ {0xBA200000u, 44u}, // aro -> Latn
{0xC2200000u, 1u}, // arq -> Arab
{0xE2200000u, 1u}, // ary -> Arab
{0xE6200000u, 1u}, // arz -> Arab
{0x61730000u, 7u}, // as -> Beng
- {0x82400000u, 40u}, // asa -> Latn
- {0x92400000u, 68u}, // ase -> Sgnw
- {0x9A400000u, 40u}, // asg -> Latn
- {0xBA400000u, 40u}, // aso -> Latn
- {0xCE400000u, 40u}, // ast -> Latn
- {0x82600000u, 40u}, // ata -> Latn
- {0x9A600000u, 40u}, // atg -> Latn
- {0xA6600000u, 40u}, // atj -> Latn
- {0xE2800000u, 40u}, // auy -> Latn
- {0x61760000u, 15u}, // av -> Cyrl
+ {0x82400000u, 44u}, // asa -> Latn
+ {0x92400000u, 73u}, // ase -> Sgnw
+ {0x9A400000u, 44u}, // asg -> Latn
+ {0xBA400000u, 44u}, // aso -> Latn
+ {0xCE400000u, 44u}, // ast -> Latn
+ {0x82600000u, 44u}, // ata -> Latn
+ {0x9A600000u, 44u}, // atg -> Latn
+ {0xA6600000u, 44u}, // atj -> Latn
+ {0xE2800000u, 44u}, // auy -> Latn
+ {0x61760000u, 16u}, // av -> Cyrl
{0xAEA00000u, 1u}, // avl -> Arab
- {0xB6A00000u, 40u}, // avn -> Latn
- {0xCEA00000u, 40u}, // avt -> Latn
- {0xD2A00000u, 40u}, // avu -> Latn
- {0x82C00000u, 16u}, // awa -> Deva
- {0x86C00000u, 40u}, // awb -> Latn
- {0xBAC00000u, 40u}, // awo -> Latn
- {0xDEC00000u, 40u}, // awx -> Latn
- {0x61790000u, 40u}, // ay -> Latn
- {0x87000000u, 40u}, // ayb -> Latn
- {0x617A0000u, 40u}, // az -> Latn
+ {0xB6A00000u, 44u}, // avn -> Latn
+ {0xCEA00000u, 44u}, // avt -> Latn
+ {0xD2A00000u, 44u}, // avu -> Latn
+ {0x82C00000u, 17u}, // awa -> Deva
+ {0x86C00000u, 44u}, // awb -> Latn
+ {0xBAC00000u, 44u}, // awo -> Latn
+ {0xDEC00000u, 44u}, // awx -> Latn
+ {0x61790000u, 44u}, // ay -> Latn
+ {0x87000000u, 44u}, // ayb -> Latn
+ {0x617A0000u, 44u}, // az -> Latn
{0x617A4951u, 1u}, // az-IQ -> Arab
{0x617A4952u, 1u}, // az-IR -> Arab
- {0x617A5255u, 15u}, // az-RU -> Cyrl
- {0x62610000u, 15u}, // ba -> Cyrl
+ {0x617A5255u, 16u}, // az-RU -> Cyrl
+ {0x62610000u, 16u}, // ba -> Cyrl
{0xAC010000u, 1u}, // bal -> Arab
- {0xB4010000u, 40u}, // ban -> Latn
- {0xBC010000u, 16u}, // bap -> Deva
- {0xC4010000u, 40u}, // bar -> Latn
- {0xC8010000u, 40u}, // bas -> Latn
- {0xD4010000u, 40u}, // bav -> Latn
+ {0xB4010000u, 44u}, // ban -> Latn
+ {0xBC010000u, 17u}, // bap -> Deva
+ {0xC4010000u, 44u}, // bar -> Latn
+ {0xC8010000u, 44u}, // bas -> Latn
+ {0xD4010000u, 44u}, // bav -> Latn
{0xDC010000u, 5u}, // bax -> Bamu
- {0x80210000u, 40u}, // bba -> Latn
- {0x84210000u, 40u}, // bbb -> Latn
- {0x88210000u, 40u}, // bbc -> Latn
- {0x8C210000u, 40u}, // bbd -> Latn
- {0xA4210000u, 40u}, // bbj -> Latn
- {0xBC210000u, 40u}, // bbp -> Latn
- {0xC4210000u, 40u}, // bbr -> Latn
- {0x94410000u, 40u}, // bcf -> Latn
- {0x9C410000u, 40u}, // bch -> Latn
- {0xA0410000u, 40u}, // bci -> Latn
- {0xB0410000u, 40u}, // bcm -> Latn
- {0xB4410000u, 40u}, // bcn -> Latn
- {0xB8410000u, 40u}, // bco -> Latn
- {0xC0410000u, 18u}, // bcq -> Ethi
- {0xD0410000u, 40u}, // bcu -> Latn
- {0x8C610000u, 40u}, // bdd -> Latn
- {0x62650000u, 15u}, // be -> Cyrl
- {0x94810000u, 40u}, // bef -> Latn
- {0x9C810000u, 40u}, // beh -> Latn
+ {0x80210000u, 44u}, // bba -> Latn
+ {0x84210000u, 44u}, // bbb -> Latn
+ {0x88210000u, 44u}, // bbc -> Latn
+ {0x8C210000u, 44u}, // bbd -> Latn
+ {0xA4210000u, 44u}, // bbj -> Latn
+ {0xBC210000u, 44u}, // bbp -> Latn
+ {0xC4210000u, 44u}, // bbr -> Latn
+ {0x94410000u, 44u}, // bcf -> Latn
+ {0x9C410000u, 44u}, // bch -> Latn
+ {0xA0410000u, 44u}, // bci -> Latn
+ {0xB0410000u, 44u}, // bcm -> Latn
+ {0xB4410000u, 44u}, // bcn -> Latn
+ {0xB8410000u, 44u}, // bco -> Latn
+ {0xC0410000u, 19u}, // bcq -> Ethi
+ {0xD0410000u, 44u}, // bcu -> Latn
+ {0x8C610000u, 44u}, // bdd -> Latn
+ {0x62650000u, 16u}, // be -> Cyrl
+ {0x94810000u, 44u}, // bef -> Latn
+ {0x9C810000u, 44u}, // beh -> Latn
{0xA4810000u, 1u}, // bej -> Arab
- {0xB0810000u, 40u}, // bem -> Latn
- {0xCC810000u, 40u}, // bet -> Latn
- {0xD8810000u, 40u}, // bew -> Latn
- {0xDC810000u, 40u}, // bex -> Latn
- {0xE4810000u, 40u}, // bez -> Latn
- {0x8CA10000u, 40u}, // bfd -> Latn
- {0xC0A10000u, 74u}, // bfq -> Taml
+ {0xB0810000u, 44u}, // bem -> Latn
+ {0xCC810000u, 44u}, // bet -> Latn
+ {0xD8810000u, 44u}, // bew -> Latn
+ {0xDC810000u, 44u}, // bex -> Latn
+ {0xE4810000u, 44u}, // bez -> Latn
+ {0x8CA10000u, 44u}, // bfd -> Latn
+ {0xC0A10000u, 81u}, // bfq -> Taml
{0xCCA10000u, 1u}, // bft -> Arab
- {0xE0A10000u, 16u}, // bfy -> Deva
- {0x62670000u, 15u}, // bg -> Cyrl
- {0x88C10000u, 16u}, // bgc -> Deva
+ {0xE0A10000u, 17u}, // bfy -> Deva
+ {0x62670000u, 16u}, // bg -> Cyrl
+ {0x88C10000u, 17u}, // bgc -> Deva
{0xB4C10000u, 1u}, // bgn -> Arab
- {0xDCC10000u, 21u}, // bgx -> Grek
- {0x84E10000u, 16u}, // bhb -> Deva
- {0x98E10000u, 40u}, // bhg -> Latn
- {0xA0E10000u, 16u}, // bhi -> Deva
- {0xA8E10000u, 40u}, // bhk -> Latn
- {0xACE10000u, 40u}, // bhl -> Latn
- {0xB8E10000u, 16u}, // bho -> Deva
- {0xE0E10000u, 40u}, // bhy -> Latn
- {0x62690000u, 40u}, // bi -> Latn
- {0x85010000u, 40u}, // bib -> Latn
- {0x99010000u, 40u}, // big -> Latn
- {0xA9010000u, 40u}, // bik -> Latn
- {0xB1010000u, 40u}, // bim -> Latn
- {0xB5010000u, 40u}, // bin -> Latn
- {0xB9010000u, 40u}, // bio -> Latn
- {0xC1010000u, 40u}, // biq -> Latn
- {0x9D210000u, 40u}, // bjh -> Latn
- {0xA1210000u, 18u}, // bji -> Ethi
- {0xA5210000u, 16u}, // bjj -> Deva
- {0xB5210000u, 40u}, // bjn -> Latn
- {0xB9210000u, 40u}, // bjo -> Latn
- {0xC5210000u, 40u}, // bjr -> Latn
- {0xE5210000u, 40u}, // bjz -> Latn
- {0x89410000u, 40u}, // bkc -> Latn
- {0xB1410000u, 40u}, // bkm -> Latn
- {0xC1410000u, 40u}, // bkq -> Latn
- {0xD1410000u, 40u}, // bku -> Latn
- {0xD5410000u, 40u}, // bkv -> Latn
- {0xCD610000u, 76u}, // blt -> Tavt
- {0x626D0000u, 40u}, // bm -> Latn
- {0x9D810000u, 40u}, // bmh -> Latn
- {0xA9810000u, 40u}, // bmk -> Latn
- {0xC1810000u, 40u}, // bmq -> Latn
- {0xD1810000u, 40u}, // bmu -> Latn
+ {0xDCC10000u, 24u}, // bgx -> Grek
+ {0x84E10000u, 17u}, // bhb -> Deva
+ {0x98E10000u, 44u}, // bhg -> Latn
+ {0xA0E10000u, 17u}, // bhi -> Deva
+ {0xA8E10000u, 44u}, // bhk -> Latn
+ {0xACE10000u, 44u}, // bhl -> Latn
+ {0xB8E10000u, 17u}, // bho -> Deva
+ {0xE0E10000u, 44u}, // bhy -> Latn
+ {0x62690000u, 44u}, // bi -> Latn
+ {0x85010000u, 44u}, // bib -> Latn
+ {0x99010000u, 44u}, // big -> Latn
+ {0xA9010000u, 44u}, // bik -> Latn
+ {0xB1010000u, 44u}, // bim -> Latn
+ {0xB5010000u, 44u}, // bin -> Latn
+ {0xB9010000u, 44u}, // bio -> Latn
+ {0xC1010000u, 44u}, // biq -> Latn
+ {0x9D210000u, 44u}, // bjh -> Latn
+ {0xA1210000u, 19u}, // bji -> Ethi
+ {0xA5210000u, 17u}, // bjj -> Deva
+ {0xB5210000u, 44u}, // bjn -> Latn
+ {0xB9210000u, 44u}, // bjo -> Latn
+ {0xC5210000u, 44u}, // bjr -> Latn
+ {0xCD210000u, 44u}, // bjt -> Latn
+ {0xE5210000u, 44u}, // bjz -> Latn
+ {0x89410000u, 44u}, // bkc -> Latn
+ {0xB1410000u, 44u}, // bkm -> Latn
+ {0xC1410000u, 44u}, // bkq -> Latn
+ {0xD1410000u, 44u}, // bku -> Latn
+ {0xD5410000u, 44u}, // bkv -> Latn
+ {0xCD610000u, 83u}, // blt -> Tavt
+ {0x626D0000u, 44u}, // bm -> Latn
+ {0x9D810000u, 44u}, // bmh -> Latn
+ {0xA9810000u, 44u}, // bmk -> Latn
+ {0xC1810000u, 44u}, // bmq -> Latn
+ {0xD1810000u, 44u}, // bmu -> Latn
{0x626E0000u, 7u}, // bn -> Beng
- {0x99A10000u, 40u}, // bng -> Latn
- {0xB1A10000u, 40u}, // bnm -> Latn
- {0xBDA10000u, 40u}, // bnp -> Latn
- {0x626F0000u, 81u}, // bo -> Tibt
- {0xA5C10000u, 40u}, // boj -> Latn
- {0xB1C10000u, 40u}, // bom -> Latn
- {0xB5C10000u, 40u}, // bon -> Latn
+ {0x99A10000u, 44u}, // bng -> Latn
+ {0xB1A10000u, 44u}, // bnm -> Latn
+ {0xBDA10000u, 44u}, // bnp -> Latn
+ {0x626F0000u, 88u}, // bo -> Tibt
+ {0xA5C10000u, 44u}, // boj -> Latn
+ {0xB1C10000u, 44u}, // bom -> Latn
+ {0xB5C10000u, 44u}, // bon -> Latn
{0xE1E10000u, 7u}, // bpy -> Beng
- {0x8A010000u, 40u}, // bqc -> Latn
+ {0x8A010000u, 44u}, // bqc -> Latn
{0xA2010000u, 1u}, // bqi -> Arab
- {0xBE010000u, 40u}, // bqp -> Latn
- {0xD6010000u, 40u}, // bqv -> Latn
- {0x62720000u, 40u}, // br -> Latn
- {0x82210000u, 16u}, // bra -> Deva
+ {0xBE010000u, 44u}, // bqp -> Latn
+ {0xD6010000u, 44u}, // bqv -> Latn
+ {0x62720000u, 44u}, // br -> Latn
+ {0x82210000u, 17u}, // bra -> Deva
{0x9E210000u, 1u}, // brh -> Arab
- {0xDE210000u, 16u}, // brx -> Deva
- {0xE6210000u, 40u}, // brz -> Latn
- {0x62730000u, 40u}, // bs -> Latn
- {0xA6410000u, 40u}, // bsj -> Latn
+ {0xDE210000u, 17u}, // brx -> Deva
+ {0xE6210000u, 44u}, // brz -> Latn
+ {0x62730000u, 44u}, // bs -> Latn
+ {0xA6410000u, 44u}, // bsj -> Latn
{0xC2410000u, 6u}, // bsq -> Bass
- {0xCA410000u, 40u}, // bss -> Latn
- {0xCE410000u, 18u}, // bst -> Ethi
- {0xBA610000u, 40u}, // bto -> Latn
- {0xCE610000u, 40u}, // btt -> Latn
- {0xD6610000u, 16u}, // btv -> Deva
- {0x82810000u, 15u}, // bua -> Cyrl
- {0x8A810000u, 40u}, // buc -> Latn
- {0x8E810000u, 40u}, // bud -> Latn
- {0x9A810000u, 40u}, // bug -> Latn
- {0xAA810000u, 40u}, // buk -> Latn
- {0xB2810000u, 40u}, // bum -> Latn
- {0xBA810000u, 40u}, // buo -> Latn
- {0xCA810000u, 40u}, // bus -> Latn
- {0xD2810000u, 40u}, // buu -> Latn
- {0x86A10000u, 40u}, // bvb -> Latn
- {0x8EC10000u, 40u}, // bwd -> Latn
- {0xC6C10000u, 40u}, // bwr -> Latn
- {0x9EE10000u, 40u}, // bxh -> Latn
- {0x93010000u, 40u}, // bye -> Latn
- {0xB7010000u, 18u}, // byn -> Ethi
- {0xC7010000u, 40u}, // byr -> Latn
- {0xCB010000u, 40u}, // bys -> Latn
- {0xD7010000u, 40u}, // byv -> Latn
- {0xDF010000u, 40u}, // byx -> Latn
- {0x83210000u, 40u}, // bza -> Latn
- {0x93210000u, 40u}, // bze -> Latn
- {0x97210000u, 40u}, // bzf -> Latn
- {0x9F210000u, 40u}, // bzh -> Latn
- {0xDB210000u, 40u}, // bzw -> Latn
- {0x63610000u, 40u}, // ca -> Latn
- {0xB4020000u, 40u}, // can -> Latn
- {0xA4220000u, 40u}, // cbj -> Latn
- {0x9C420000u, 40u}, // cch -> Latn
- {0xBC420000u, 7u}, // ccp -> Beng
- {0x63650000u, 15u}, // ce -> Cyrl
- {0x84820000u, 40u}, // ceb -> Latn
- {0x80A20000u, 40u}, // cfa -> Latn
- {0x98C20000u, 40u}, // cgg -> Latn
- {0x63680000u, 40u}, // ch -> Latn
- {0xA8E20000u, 40u}, // chk -> Latn
- {0xB0E20000u, 15u}, // chm -> Cyrl
- {0xB8E20000u, 40u}, // cho -> Latn
- {0xBCE20000u, 40u}, // chp -> Latn
- {0xC4E20000u, 12u}, // chr -> Cher
+ {0xCA410000u, 44u}, // bss -> Latn
+ {0xCE410000u, 19u}, // bst -> Ethi
+ {0xBA610000u, 44u}, // bto -> Latn
+ {0xCE610000u, 44u}, // btt -> Latn
+ {0xD6610000u, 17u}, // btv -> Deva
+ {0x82810000u, 16u}, // bua -> Cyrl
+ {0x8A810000u, 44u}, // buc -> Latn
+ {0x8E810000u, 44u}, // bud -> Latn
+ {0x9A810000u, 44u}, // bug -> Latn
+ {0xAA810000u, 44u}, // buk -> Latn
+ {0xB2810000u, 44u}, // bum -> Latn
+ {0xBA810000u, 44u}, // buo -> Latn
+ {0xCA810000u, 44u}, // bus -> Latn
+ {0xD2810000u, 44u}, // buu -> Latn
+ {0x86A10000u, 44u}, // bvb -> Latn
+ {0x8EC10000u, 44u}, // bwd -> Latn
+ {0xC6C10000u, 44u}, // bwr -> Latn
+ {0x9EE10000u, 44u}, // bxh -> Latn
+ {0x93010000u, 44u}, // bye -> Latn
+ {0xB7010000u, 19u}, // byn -> Ethi
+ {0xC7010000u, 44u}, // byr -> Latn
+ {0xCB010000u, 44u}, // bys -> Latn
+ {0xD7010000u, 44u}, // byv -> Latn
+ {0xDF010000u, 44u}, // byx -> Latn
+ {0x83210000u, 44u}, // bza -> Latn
+ {0x93210000u, 44u}, // bze -> Latn
+ {0x97210000u, 44u}, // bzf -> Latn
+ {0x9F210000u, 44u}, // bzh -> Latn
+ {0xDB210000u, 44u}, // bzw -> Latn
+ {0x63610000u, 44u}, // ca -> Latn
+ {0xB4020000u, 44u}, // can -> Latn
+ {0xA4220000u, 44u}, // cbj -> Latn
+ {0x9C420000u, 44u}, // cch -> Latn
+ {0xBC420000u, 9u}, // ccp -> Cakm
+ {0x63650000u, 16u}, // ce -> Cyrl
+ {0x84820000u, 44u}, // ceb -> Latn
+ {0x80A20000u, 44u}, // cfa -> Latn
+ {0x98C20000u, 44u}, // cgg -> Latn
+ {0x63680000u, 44u}, // ch -> Latn
+ {0xA8E20000u, 44u}, // chk -> Latn
+ {0xB0E20000u, 16u}, // chm -> Cyrl
+ {0xB8E20000u, 44u}, // cho -> Latn
+ {0xBCE20000u, 44u}, // chp -> Latn
+ {0xC4E20000u, 13u}, // chr -> Cher
{0x81220000u, 1u}, // cja -> Arab
- {0xB1220000u, 11u}, // cjm -> Cham
- {0xD5220000u, 40u}, // cjv -> Latn
+ {0xB1220000u, 12u}, // cjm -> Cham
+ {0xD5220000u, 44u}, // cjv -> Latn
{0x85420000u, 1u}, // ckb -> Arab
- {0xAD420000u, 40u}, // ckl -> Latn
- {0xB9420000u, 40u}, // cko -> Latn
- {0xE1420000u, 40u}, // cky -> Latn
- {0x81620000u, 40u}, // cla -> Latn
- {0x91820000u, 40u}, // cme -> Latn
- {0x636F0000u, 40u}, // co -> Latn
- {0xBDC20000u, 13u}, // cop -> Copt
- {0xC9E20000u, 40u}, // cps -> Latn
- {0x63720000u, 9u}, // cr -> Cans
- {0xA6220000u, 9u}, // crj -> Cans
- {0xAA220000u, 9u}, // crk -> Cans
- {0xAE220000u, 9u}, // crl -> Cans
- {0xB2220000u, 9u}, // crm -> Cans
- {0xCA220000u, 40u}, // crs -> Latn
- {0x63730000u, 40u}, // cs -> Latn
- {0x86420000u, 40u}, // csb -> Latn
- {0xDA420000u, 9u}, // csw -> Cans
- {0x8E620000u, 59u}, // ctd -> Pauc
- {0x63750000u, 15u}, // cu -> Cyrl
- {0x63760000u, 15u}, // cv -> Cyrl
- {0x63790000u, 40u}, // cy -> Latn
- {0x64610000u, 40u}, // da -> Latn
- {0x8C030000u, 40u}, // dad -> Latn
- {0x94030000u, 40u}, // daf -> Latn
- {0x98030000u, 40u}, // dag -> Latn
- {0x9C030000u, 40u}, // dah -> Latn
- {0xA8030000u, 40u}, // dak -> Latn
- {0xC4030000u, 15u}, // dar -> Cyrl
- {0xD4030000u, 40u}, // dav -> Latn
- {0x8C230000u, 40u}, // dbd -> Latn
- {0xC0230000u, 40u}, // dbq -> Latn
+ {0xAD420000u, 44u}, // ckl -> Latn
+ {0xB9420000u, 44u}, // cko -> Latn
+ {0xE1420000u, 44u}, // cky -> Latn
+ {0x81620000u, 44u}, // cla -> Latn
+ {0x91820000u, 44u}, // cme -> Latn
+ {0x99820000u, 77u}, // cmg -> Soyo
+ {0x636F0000u, 44u}, // co -> Latn
+ {0xBDC20000u, 14u}, // cop -> Copt
+ {0xC9E20000u, 44u}, // cps -> Latn
+ {0x63720000u, 10u}, // cr -> Cans
+ {0x9E220000u, 16u}, // crh -> Cyrl
+ {0xA6220000u, 10u}, // crj -> Cans
+ {0xAA220000u, 10u}, // crk -> Cans
+ {0xAE220000u, 10u}, // crl -> Cans
+ {0xB2220000u, 10u}, // crm -> Cans
+ {0xCA220000u, 44u}, // crs -> Latn
+ {0x63730000u, 44u}, // cs -> Latn
+ {0x86420000u, 44u}, // csb -> Latn
+ {0xDA420000u, 10u}, // csw -> Cans
+ {0x8E620000u, 64u}, // ctd -> Pauc
+ {0x63750000u, 16u}, // cu -> Cyrl
+ {0x63760000u, 16u}, // cv -> Cyrl
+ {0x63790000u, 44u}, // cy -> Latn
+ {0x64610000u, 44u}, // da -> Latn
+ {0x8C030000u, 44u}, // dad -> Latn
+ {0x94030000u, 44u}, // daf -> Latn
+ {0x98030000u, 44u}, // dag -> Latn
+ {0x9C030000u, 44u}, // dah -> Latn
+ {0xA8030000u, 44u}, // dak -> Latn
+ {0xC4030000u, 16u}, // dar -> Cyrl
+ {0xD4030000u, 44u}, // dav -> Latn
+ {0x8C230000u, 44u}, // dbd -> Latn
+ {0xC0230000u, 44u}, // dbq -> Latn
{0x88430000u, 1u}, // dcc -> Arab
- {0xB4630000u, 40u}, // ddn -> Latn
- {0x64650000u, 40u}, // de -> Latn
- {0x8C830000u, 40u}, // ded -> Latn
- {0xB4830000u, 40u}, // den -> Latn
- {0x80C30000u, 40u}, // dga -> Latn
- {0x9CC30000u, 40u}, // dgh -> Latn
- {0xA0C30000u, 40u}, // dgi -> Latn
+ {0xB4630000u, 44u}, // ddn -> Latn
+ {0x64650000u, 44u}, // de -> Latn
+ {0x8C830000u, 44u}, // ded -> Latn
+ {0xB4830000u, 44u}, // den -> Latn
+ {0x80C30000u, 44u}, // dga -> Latn
+ {0x9CC30000u, 44u}, // dgh -> Latn
+ {0xA0C30000u, 44u}, // dgi -> Latn
{0xACC30000u, 1u}, // dgl -> Arab
- {0xC4C30000u, 40u}, // dgr -> Latn
- {0xE4C30000u, 40u}, // dgz -> Latn
- {0x81030000u, 40u}, // dia -> Latn
- {0x91230000u, 40u}, // dje -> Latn
- {0xA5A30000u, 40u}, // dnj -> Latn
- {0x85C30000u, 40u}, // dob -> Latn
+ {0xC4C30000u, 44u}, // dgr -> Latn
+ {0xE4C30000u, 44u}, // dgz -> Latn
+ {0x81030000u, 44u}, // dia -> Latn
+ {0x91230000u, 44u}, // dje -> Latn
+ {0xA5A30000u, 44u}, // dnj -> Latn
+ {0x85C30000u, 44u}, // dob -> Latn
{0xA1C30000u, 1u}, // doi -> Arab
- {0xBDC30000u, 40u}, // dop -> Latn
- {0xD9C30000u, 40u}, // dow -> Latn
- {0xA2230000u, 40u}, // dri -> Latn
- {0xCA230000u, 18u}, // drs -> Ethi
- {0x86430000u, 40u}, // dsb -> Latn
- {0xB2630000u, 40u}, // dtm -> Latn
- {0xBE630000u, 40u}, // dtp -> Latn
- {0xCA630000u, 40u}, // dts -> Latn
- {0xE2630000u, 16u}, // dty -> Deva
- {0x82830000u, 40u}, // dua -> Latn
- {0x8A830000u, 40u}, // duc -> Latn
- {0x8E830000u, 40u}, // dud -> Latn
- {0x9A830000u, 40u}, // dug -> Latn
- {0x64760000u, 79u}, // dv -> Thaa
- {0x82A30000u, 40u}, // dva -> Latn
- {0xDAC30000u, 40u}, // dww -> Latn
- {0xBB030000u, 40u}, // dyo -> Latn
- {0xD3030000u, 40u}, // dyu -> Latn
- {0x647A0000u, 81u}, // dz -> Tibt
- {0x9B230000u, 40u}, // dzg -> Latn
- {0xD0240000u, 40u}, // ebu -> Latn
- {0x65650000u, 40u}, // ee -> Latn
- {0xA0A40000u, 40u}, // efi -> Latn
- {0xACC40000u, 40u}, // egl -> Latn
- {0xE0C40000u, 17u}, // egy -> Egyp
- {0xE1440000u, 32u}, // eky -> Kali
- {0x656C0000u, 21u}, // el -> Grek
- {0x81840000u, 40u}, // ema -> Latn
- {0xA1840000u, 40u}, // emi -> Latn
- {0x656E0000u, 40u}, // en -> Latn
- {0x656E5841u, 87u}, // en-XA -> ~~~A
- {0xB5A40000u, 40u}, // enn -> Latn
- {0xC1A40000u, 40u}, // enq -> Latn
- {0x656F0000u, 40u}, // eo -> Latn
- {0xA2240000u, 40u}, // eri -> Latn
- {0x65730000u, 40u}, // es -> Latn
- {0xD2440000u, 40u}, // esu -> Latn
- {0x65740000u, 40u}, // et -> Latn
- {0xC6640000u, 40u}, // etr -> Latn
- {0xCE640000u, 30u}, // ett -> Ital
- {0xD2640000u, 40u}, // etu -> Latn
- {0xDE640000u, 40u}, // etx -> Latn
- {0x65750000u, 40u}, // eu -> Latn
- {0xBAC40000u, 40u}, // ewo -> Latn
- {0xCEE40000u, 40u}, // ext -> Latn
+ {0xBDC30000u, 44u}, // dop -> Latn
+ {0xD9C30000u, 44u}, // dow -> Latn
+ {0xA2230000u, 44u}, // dri -> Latn
+ {0xCA230000u, 19u}, // drs -> Ethi
+ {0x86430000u, 44u}, // dsb -> Latn
+ {0xB2630000u, 44u}, // dtm -> Latn
+ {0xBE630000u, 44u}, // dtp -> Latn
+ {0xCA630000u, 44u}, // dts -> Latn
+ {0xE2630000u, 17u}, // dty -> Deva
+ {0x82830000u, 44u}, // dua -> Latn
+ {0x8A830000u, 44u}, // duc -> Latn
+ {0x8E830000u, 44u}, // dud -> Latn
+ {0x9A830000u, 44u}, // dug -> Latn
+ {0x64760000u, 86u}, // dv -> Thaa
+ {0x82A30000u, 44u}, // dva -> Latn
+ {0xDAC30000u, 44u}, // dww -> Latn
+ {0xBB030000u, 44u}, // dyo -> Latn
+ {0xD3030000u, 44u}, // dyu -> Latn
+ {0x647A0000u, 88u}, // dz -> Tibt
+ {0x9B230000u, 44u}, // dzg -> Latn
+ {0xD0240000u, 44u}, // ebu -> Latn
+ {0x65650000u, 44u}, // ee -> Latn
+ {0xA0A40000u, 44u}, // efi -> Latn
+ {0xACC40000u, 44u}, // egl -> Latn
+ {0xE0C40000u, 18u}, // egy -> Egyp
+ {0x81440000u, 44u}, // eka -> Latn
+ {0xE1440000u, 36u}, // eky -> Kali
+ {0x656C0000u, 24u}, // el -> Grek
+ {0x81840000u, 44u}, // ema -> Latn
+ {0xA1840000u, 44u}, // emi -> Latn
+ {0x656E0000u, 44u}, // en -> Latn
+ {0x656E5841u, 95u}, // en-XA -> ~~~A
+ {0xB5A40000u, 44u}, // enn -> Latn
+ {0xC1A40000u, 44u}, // enq -> Latn
+ {0x656F0000u, 44u}, // eo -> Latn
+ {0xA2240000u, 44u}, // eri -> Latn
+ {0x65730000u, 44u}, // es -> Latn
+ {0x9A440000u, 22u}, // esg -> Gonm
+ {0xD2440000u, 44u}, // esu -> Latn
+ {0x65740000u, 44u}, // et -> Latn
+ {0xC6640000u, 44u}, // etr -> Latn
+ {0xCE640000u, 34u}, // ett -> Ital
+ {0xD2640000u, 44u}, // etu -> Latn
+ {0xDE640000u, 44u}, // etx -> Latn
+ {0x65750000u, 44u}, // eu -> Latn
+ {0xBAC40000u, 44u}, // ewo -> Latn
+ {0xCEE40000u, 44u}, // ext -> Latn
{0x66610000u, 1u}, // fa -> Arab
- {0x80050000u, 40u}, // faa -> Latn
- {0x84050000u, 40u}, // fab -> Latn
- {0x98050000u, 40u}, // fag -> Latn
- {0xA0050000u, 40u}, // fai -> Latn
- {0xB4050000u, 40u}, // fan -> Latn
- {0x66660000u, 40u}, // ff -> Latn
- {0xA0A50000u, 40u}, // ffi -> Latn
- {0xB0A50000u, 40u}, // ffm -> Latn
- {0x66690000u, 40u}, // fi -> Latn
+ {0x80050000u, 44u}, // faa -> Latn
+ {0x84050000u, 44u}, // fab -> Latn
+ {0x98050000u, 44u}, // fag -> Latn
+ {0xA0050000u, 44u}, // fai -> Latn
+ {0xB4050000u, 44u}, // fan -> Latn
+ {0x66660000u, 44u}, // ff -> Latn
+ {0xA0A50000u, 44u}, // ffi -> Latn
+ {0xB0A50000u, 44u}, // ffm -> Latn
+ {0x66690000u, 44u}, // fi -> Latn
{0x81050000u, 1u}, // fia -> Arab
- {0xAD050000u, 40u}, // fil -> Latn
- {0xCD050000u, 40u}, // fit -> Latn
- {0x666A0000u, 40u}, // fj -> Latn
- {0xC5650000u, 40u}, // flr -> Latn
- {0xBD850000u, 40u}, // fmp -> Latn
- {0x666F0000u, 40u}, // fo -> Latn
- {0x8DC50000u, 40u}, // fod -> Latn
- {0xB5C50000u, 40u}, // fon -> Latn
- {0xC5C50000u, 40u}, // for -> Latn
- {0x91E50000u, 40u}, // fpe -> Latn
- {0xCA050000u, 40u}, // fqs -> Latn
- {0x66720000u, 40u}, // fr -> Latn
- {0x8A250000u, 40u}, // frc -> Latn
- {0xBE250000u, 40u}, // frp -> Latn
- {0xC6250000u, 40u}, // frr -> Latn
- {0xCA250000u, 40u}, // frs -> Latn
+ {0xAD050000u, 44u}, // fil -> Latn
+ {0xCD050000u, 44u}, // fit -> Latn
+ {0x666A0000u, 44u}, // fj -> Latn
+ {0xC5650000u, 44u}, // flr -> Latn
+ {0xBD850000u, 44u}, // fmp -> Latn
+ {0x666F0000u, 44u}, // fo -> Latn
+ {0x8DC50000u, 44u}, // fod -> Latn
+ {0xB5C50000u, 44u}, // fon -> Latn
+ {0xC5C50000u, 44u}, // for -> Latn
+ {0x91E50000u, 44u}, // fpe -> Latn
+ {0xCA050000u, 44u}, // fqs -> Latn
+ {0x66720000u, 44u}, // fr -> Latn
+ {0x8A250000u, 44u}, // frc -> Latn
+ {0xBE250000u, 44u}, // frp -> Latn
+ {0xC6250000u, 44u}, // frr -> Latn
+ {0xCA250000u, 44u}, // frs -> Latn
{0x86850000u, 1u}, // fub -> Arab
- {0x8E850000u, 40u}, // fud -> Latn
- {0x92850000u, 40u}, // fue -> Latn
- {0x96850000u, 40u}, // fuf -> Latn
- {0x9E850000u, 40u}, // fuh -> Latn
- {0xC2850000u, 40u}, // fuq -> Latn
- {0xC6850000u, 40u}, // fur -> Latn
- {0xD6850000u, 40u}, // fuv -> Latn
- {0xE2850000u, 40u}, // fuy -> Latn
- {0xC6A50000u, 40u}, // fvr -> Latn
- {0x66790000u, 40u}, // fy -> Latn
- {0x67610000u, 40u}, // ga -> Latn
- {0x80060000u, 40u}, // gaa -> Latn
- {0x94060000u, 40u}, // gaf -> Latn
- {0x98060000u, 40u}, // gag -> Latn
- {0x9C060000u, 40u}, // gah -> Latn
- {0xA4060000u, 40u}, // gaj -> Latn
- {0xB0060000u, 40u}, // gam -> Latn
- {0xB4060000u, 24u}, // gan -> Hans
- {0xD8060000u, 40u}, // gaw -> Latn
- {0xE0060000u, 40u}, // gay -> Latn
- {0x94260000u, 40u}, // gbf -> Latn
- {0xB0260000u, 16u}, // gbm -> Deva
- {0xE0260000u, 40u}, // gby -> Latn
+ {0x8E850000u, 44u}, // fud -> Latn
+ {0x92850000u, 44u}, // fue -> Latn
+ {0x96850000u, 44u}, // fuf -> Latn
+ {0x9E850000u, 44u}, // fuh -> Latn
+ {0xC2850000u, 44u}, // fuq -> Latn
+ {0xC6850000u, 44u}, // fur -> Latn
+ {0xD6850000u, 44u}, // fuv -> Latn
+ {0xE2850000u, 44u}, // fuy -> Latn
+ {0xC6A50000u, 44u}, // fvr -> Latn
+ {0x66790000u, 44u}, // fy -> Latn
+ {0x67610000u, 44u}, // ga -> Latn
+ {0x80060000u, 44u}, // gaa -> Latn
+ {0x94060000u, 44u}, // gaf -> Latn
+ {0x98060000u, 44u}, // gag -> Latn
+ {0x9C060000u, 44u}, // gah -> Latn
+ {0xA4060000u, 44u}, // gaj -> Latn
+ {0xB0060000u, 44u}, // gam -> Latn
+ {0xB4060000u, 27u}, // gan -> Hans
+ {0xD8060000u, 44u}, // gaw -> Latn
+ {0xE0060000u, 44u}, // gay -> Latn
+ {0x80260000u, 44u}, // gba -> Latn
+ {0x94260000u, 44u}, // gbf -> Latn
+ {0xB0260000u, 17u}, // gbm -> Deva
+ {0xE0260000u, 44u}, // gby -> Latn
{0xE4260000u, 1u}, // gbz -> Arab
- {0xC4460000u, 40u}, // gcr -> Latn
- {0x67640000u, 40u}, // gd -> Latn
- {0x90660000u, 40u}, // gde -> Latn
- {0xB4660000u, 40u}, // gdn -> Latn
- {0xC4660000u, 40u}, // gdr -> Latn
- {0x84860000u, 40u}, // geb -> Latn
- {0xA4860000u, 40u}, // gej -> Latn
- {0xAC860000u, 40u}, // gel -> Latn
- {0xE4860000u, 18u}, // gez -> Ethi
- {0xA8A60000u, 40u}, // gfk -> Latn
- {0xB4C60000u, 16u}, // ggn -> Deva
- {0xC8E60000u, 40u}, // ghs -> Latn
- {0xAD060000u, 40u}, // gil -> Latn
- {0xB1060000u, 40u}, // gim -> Latn
+ {0xC4460000u, 44u}, // gcr -> Latn
+ {0x67640000u, 44u}, // gd -> Latn
+ {0x90660000u, 44u}, // gde -> Latn
+ {0xB4660000u, 44u}, // gdn -> Latn
+ {0xC4660000u, 44u}, // gdr -> Latn
+ {0x84860000u, 44u}, // geb -> Latn
+ {0xA4860000u, 44u}, // gej -> Latn
+ {0xAC860000u, 44u}, // gel -> Latn
+ {0xE4860000u, 19u}, // gez -> Ethi
+ {0xA8A60000u, 44u}, // gfk -> Latn
+ {0xB4C60000u, 17u}, // ggn -> Deva
+ {0xC8E60000u, 44u}, // ghs -> Latn
+ {0xAD060000u, 44u}, // gil -> Latn
+ {0xB1060000u, 44u}, // gim -> Latn
{0xA9260000u, 1u}, // gjk -> Arab
- {0xB5260000u, 40u}, // gjn -> Latn
+ {0xB5260000u, 44u}, // gjn -> Latn
{0xD1260000u, 1u}, // gju -> Arab
- {0xB5460000u, 40u}, // gkn -> Latn
- {0xBD460000u, 40u}, // gkp -> Latn
- {0x676C0000u, 40u}, // gl -> Latn
+ {0xB5460000u, 44u}, // gkn -> Latn
+ {0xBD460000u, 44u}, // gkp -> Latn
+ {0x676C0000u, 44u}, // gl -> Latn
{0xA9660000u, 1u}, // glk -> Arab
- {0xB1860000u, 40u}, // gmm -> Latn
- {0xD5860000u, 18u}, // gmv -> Ethi
- {0x676E0000u, 40u}, // gn -> Latn
- {0x8DA60000u, 40u}, // gnd -> Latn
- {0x99A60000u, 40u}, // gng -> Latn
- {0x8DC60000u, 40u}, // god -> Latn
- {0x95C60000u, 18u}, // gof -> Ethi
- {0xA1C60000u, 40u}, // goi -> Latn
- {0xB1C60000u, 16u}, // gom -> Deva
- {0xB5C60000u, 77u}, // gon -> Telu
- {0xC5C60000u, 40u}, // gor -> Latn
- {0xC9C60000u, 40u}, // gos -> Latn
- {0xCDC60000u, 20u}, // got -> Goth
- {0x8A260000u, 14u}, // grc -> Cprt
+ {0xB1860000u, 44u}, // gmm -> Latn
+ {0xD5860000u, 19u}, // gmv -> Ethi
+ {0x676E0000u, 44u}, // gn -> Latn
+ {0x8DA60000u, 44u}, // gnd -> Latn
+ {0x99A60000u, 44u}, // gng -> Latn
+ {0x8DC60000u, 44u}, // god -> Latn
+ {0x95C60000u, 19u}, // gof -> Ethi
+ {0xA1C60000u, 44u}, // goi -> Latn
+ {0xB1C60000u, 17u}, // gom -> Deva
+ {0xB5C60000u, 84u}, // gon -> Telu
+ {0xC5C60000u, 44u}, // gor -> Latn
+ {0xC9C60000u, 44u}, // gos -> Latn
+ {0xCDC60000u, 23u}, // got -> Goth
+ {0x86260000u, 44u}, // grb -> Latn
+ {0x8A260000u, 15u}, // grc -> Cprt
{0xCE260000u, 7u}, // grt -> Beng
- {0xDA260000u, 40u}, // grw -> Latn
- {0xDA460000u, 40u}, // gsw -> Latn
- {0x67750000u, 22u}, // gu -> Gujr
- {0x86860000u, 40u}, // gub -> Latn
- {0x8A860000u, 40u}, // guc -> Latn
- {0x8E860000u, 40u}, // gud -> Latn
- {0xC6860000u, 40u}, // gur -> Latn
- {0xDA860000u, 40u}, // guw -> Latn
- {0xDE860000u, 40u}, // gux -> Latn
- {0xE6860000u, 40u}, // guz -> Latn
- {0x67760000u, 40u}, // gv -> Latn
- {0x96A60000u, 40u}, // gvf -> Latn
- {0xC6A60000u, 16u}, // gvr -> Deva
- {0xCAA60000u, 40u}, // gvs -> Latn
+ {0xDA260000u, 44u}, // grw -> Latn
+ {0xDA460000u, 44u}, // gsw -> Latn
+ {0x67750000u, 25u}, // gu -> Gujr
+ {0x86860000u, 44u}, // gub -> Latn
+ {0x8A860000u, 44u}, // guc -> Latn
+ {0x8E860000u, 44u}, // gud -> Latn
+ {0xC6860000u, 44u}, // gur -> Latn
+ {0xDA860000u, 44u}, // guw -> Latn
+ {0xDE860000u, 44u}, // gux -> Latn
+ {0xE6860000u, 44u}, // guz -> Latn
+ {0x67760000u, 44u}, // gv -> Latn
+ {0x96A60000u, 44u}, // gvf -> Latn
+ {0xC6A60000u, 17u}, // gvr -> Deva
+ {0xCAA60000u, 44u}, // gvs -> Latn
{0x8AC60000u, 1u}, // gwc -> Arab
- {0xA2C60000u, 40u}, // gwi -> Latn
+ {0xA2C60000u, 44u}, // gwi -> Latn
{0xCEC60000u, 1u}, // gwt -> Arab
- {0xA3060000u, 40u}, // gyi -> Latn
- {0x68610000u, 40u}, // ha -> Latn
+ {0xA3060000u, 44u}, // gyi -> Latn
+ {0x68610000u, 44u}, // ha -> Latn
{0x6861434Du, 1u}, // ha-CM -> Arab
{0x68615344u, 1u}, // ha-SD -> Arab
- {0x98070000u, 40u}, // hag -> Latn
- {0xA8070000u, 24u}, // hak -> Hans
- {0xB0070000u, 40u}, // ham -> Latn
- {0xD8070000u, 40u}, // haw -> Latn
+ {0x98070000u, 44u}, // hag -> Latn
+ {0xA8070000u, 27u}, // hak -> Hans
+ {0xB0070000u, 44u}, // ham -> Latn
+ {0xD8070000u, 44u}, // haw -> Latn
{0xE4070000u, 1u}, // haz -> Arab
- {0x84270000u, 40u}, // hbb -> Latn
- {0xE0670000u, 18u}, // hdy -> Ethi
- {0x68650000u, 27u}, // he -> Hebr
- {0xE0E70000u, 40u}, // hhy -> Latn
- {0x68690000u, 16u}, // hi -> Deva
- {0x81070000u, 40u}, // hia -> Latn
- {0x95070000u, 40u}, // hif -> Latn
- {0x99070000u, 40u}, // hig -> Latn
- {0x9D070000u, 40u}, // hih -> Latn
- {0xAD070000u, 40u}, // hil -> Latn
- {0x81670000u, 40u}, // hla -> Latn
- {0xD1670000u, 28u}, // hlu -> Hluw
- {0x8D870000u, 62u}, // hmd -> Plrd
- {0xCD870000u, 40u}, // hmt -> Latn
+ {0x84270000u, 44u}, // hbb -> Latn
+ {0xE0670000u, 19u}, // hdy -> Ethi
+ {0x68650000u, 30u}, // he -> Hebr
+ {0xE0E70000u, 44u}, // hhy -> Latn
+ {0x68690000u, 17u}, // hi -> Deva
+ {0x81070000u, 44u}, // hia -> Latn
+ {0x95070000u, 44u}, // hif -> Latn
+ {0x99070000u, 44u}, // hig -> Latn
+ {0x9D070000u, 44u}, // hih -> Latn
+ {0xAD070000u, 44u}, // hil -> Latn
+ {0x81670000u, 44u}, // hla -> Latn
+ {0xD1670000u, 31u}, // hlu -> Hluw
+ {0x8D870000u, 67u}, // hmd -> Plrd
+ {0xCD870000u, 44u}, // hmt -> Latn
{0x8DA70000u, 1u}, // hnd -> Arab
- {0x91A70000u, 16u}, // hne -> Deva
- {0xA5A70000u, 29u}, // hnj -> Hmng
- {0xB5A70000u, 40u}, // hnn -> Latn
+ {0x91A70000u, 17u}, // hne -> Deva
+ {0xA5A70000u, 32u}, // hnj -> Hmng
+ {0xB5A70000u, 44u}, // hnn -> Latn
{0xB9A70000u, 1u}, // hno -> Arab
- {0x686F0000u, 40u}, // ho -> Latn
- {0x89C70000u, 16u}, // hoc -> Deva
- {0xA5C70000u, 16u}, // hoj -> Deva
- {0xCDC70000u, 40u}, // hot -> Latn
- {0x68720000u, 40u}, // hr -> Latn
- {0x86470000u, 40u}, // hsb -> Latn
- {0xB6470000u, 24u}, // hsn -> Hans
- {0x68740000u, 40u}, // ht -> Latn
- {0x68750000u, 40u}, // hu -> Latn
- {0xA2870000u, 40u}, // hui -> Latn
+ {0x686F0000u, 44u}, // ho -> Latn
+ {0x89C70000u, 17u}, // hoc -> Deva
+ {0xA5C70000u, 17u}, // hoj -> Deva
+ {0xCDC70000u, 44u}, // hot -> Latn
+ {0x68720000u, 44u}, // hr -> Latn
+ {0x86470000u, 44u}, // hsb -> Latn
+ {0xB6470000u, 27u}, // hsn -> Hans
+ {0x68740000u, 44u}, // ht -> Latn
+ {0x68750000u, 44u}, // hu -> Latn
+ {0xA2870000u, 44u}, // hui -> Latn
{0x68790000u, 3u}, // hy -> Armn
- {0x687A0000u, 40u}, // hz -> Latn
- {0x69610000u, 40u}, // ia -> Latn
- {0xB4080000u, 40u}, // ian -> Latn
- {0xC4080000u, 40u}, // iar -> Latn
- {0x80280000u, 40u}, // iba -> Latn
- {0x84280000u, 40u}, // ibb -> Latn
- {0xE0280000u, 40u}, // iby -> Latn
- {0x80480000u, 40u}, // ica -> Latn
- {0x9C480000u, 40u}, // ich -> Latn
- {0x69640000u, 40u}, // id -> Latn
- {0x8C680000u, 40u}, // idd -> Latn
- {0xA0680000u, 40u}, // idi -> Latn
- {0xD0680000u, 40u}, // idu -> Latn
- {0x69670000u, 40u}, // ig -> Latn
- {0x84C80000u, 40u}, // igb -> Latn
- {0x90C80000u, 40u}, // ige -> Latn
- {0x69690000u, 86u}, // ii -> Yiii
- {0xA5280000u, 40u}, // ijj -> Latn
- {0x696B0000u, 40u}, // ik -> Latn
- {0xA9480000u, 40u}, // ikk -> Latn
- {0xCD480000u, 40u}, // ikt -> Latn
- {0xD9480000u, 40u}, // ikw -> Latn
- {0xDD480000u, 40u}, // ikx -> Latn
- {0xB9680000u, 40u}, // ilo -> Latn
- {0xB9880000u, 40u}, // imo -> Latn
- {0x696E0000u, 40u}, // in -> Latn
- {0x9DA80000u, 15u}, // inh -> Cyrl
- {0xD1C80000u, 40u}, // iou -> Latn
- {0xA2280000u, 40u}, // iri -> Latn
- {0x69730000u, 40u}, // is -> Latn
- {0x69740000u, 40u}, // it -> Latn
- {0x69750000u, 9u}, // iu -> Cans
- {0x69770000u, 27u}, // iw -> Hebr
- {0xB2C80000u, 40u}, // iwm -> Latn
- {0xCAC80000u, 40u}, // iws -> Latn
- {0x9F280000u, 40u}, // izh -> Latn
- {0xA3280000u, 40u}, // izi -> Latn
- {0x6A610000u, 31u}, // ja -> Jpan
- {0x84090000u, 40u}, // jab -> Latn
- {0xB0090000u, 40u}, // jam -> Latn
- {0xD0290000u, 40u}, // jbu -> Latn
- {0xB4890000u, 40u}, // jen -> Latn
- {0xA8C90000u, 40u}, // jgk -> Latn
- {0xB8C90000u, 40u}, // jgo -> Latn
- {0x6A690000u, 27u}, // ji -> Hebr
- {0x85090000u, 40u}, // jib -> Latn
- {0x89890000u, 40u}, // jmc -> Latn
- {0xAD890000u, 16u}, // jml -> Deva
- {0x82290000u, 40u}, // jra -> Latn
- {0xCE890000u, 40u}, // jut -> Latn
- {0x6A760000u, 40u}, // jv -> Latn
- {0x6A770000u, 40u}, // jw -> Latn
- {0x6B610000u, 19u}, // ka -> Geor
- {0x800A0000u, 15u}, // kaa -> Cyrl
- {0x840A0000u, 40u}, // kab -> Latn
- {0x880A0000u, 40u}, // kac -> Latn
- {0x8C0A0000u, 40u}, // kad -> Latn
- {0xA00A0000u, 40u}, // kai -> Latn
- {0xA40A0000u, 40u}, // kaj -> Latn
- {0xB00A0000u, 40u}, // kam -> Latn
- {0xB80A0000u, 40u}, // kao -> Latn
- {0x8C2A0000u, 15u}, // kbd -> Cyrl
- {0xB02A0000u, 40u}, // kbm -> Latn
- {0xBC2A0000u, 40u}, // kbp -> Latn
- {0xC02A0000u, 40u}, // kbq -> Latn
- {0xDC2A0000u, 40u}, // kbx -> Latn
+ {0x687A0000u, 44u}, // hz -> Latn
+ {0x69610000u, 44u}, // ia -> Latn
+ {0xB4080000u, 44u}, // ian -> Latn
+ {0xC4080000u, 44u}, // iar -> Latn
+ {0x80280000u, 44u}, // iba -> Latn
+ {0x84280000u, 44u}, // ibb -> Latn
+ {0xE0280000u, 44u}, // iby -> Latn
+ {0x80480000u, 44u}, // ica -> Latn
+ {0x9C480000u, 44u}, // ich -> Latn
+ {0x69640000u, 44u}, // id -> Latn
+ {0x8C680000u, 44u}, // idd -> Latn
+ {0xA0680000u, 44u}, // idi -> Latn
+ {0xD0680000u, 44u}, // idu -> Latn
+ {0x90A80000u, 44u}, // ife -> Latn
+ {0x69670000u, 44u}, // ig -> Latn
+ {0x84C80000u, 44u}, // igb -> Latn
+ {0x90C80000u, 44u}, // ige -> Latn
+ {0x69690000u, 94u}, // ii -> Yiii
+ {0xA5280000u, 44u}, // ijj -> Latn
+ {0x696B0000u, 44u}, // ik -> Latn
+ {0xA9480000u, 44u}, // ikk -> Latn
+ {0xCD480000u, 44u}, // ikt -> Latn
+ {0xD9480000u, 44u}, // ikw -> Latn
+ {0xDD480000u, 44u}, // ikx -> Latn
+ {0xB9680000u, 44u}, // ilo -> Latn
+ {0xB9880000u, 44u}, // imo -> Latn
+ {0x696E0000u, 44u}, // in -> Latn
+ {0x9DA80000u, 16u}, // inh -> Cyrl
+ {0x696F0000u, 44u}, // io -> Latn
+ {0xD1C80000u, 44u}, // iou -> Latn
+ {0xA2280000u, 44u}, // iri -> Latn
+ {0x69730000u, 44u}, // is -> Latn
+ {0x69740000u, 44u}, // it -> Latn
+ {0x69750000u, 10u}, // iu -> Cans
+ {0x69770000u, 30u}, // iw -> Hebr
+ {0xB2C80000u, 44u}, // iwm -> Latn
+ {0xCAC80000u, 44u}, // iws -> Latn
+ {0x9F280000u, 44u}, // izh -> Latn
+ {0xA3280000u, 44u}, // izi -> Latn
+ {0x6A610000u, 35u}, // ja -> Jpan
+ {0x84090000u, 44u}, // jab -> Latn
+ {0xB0090000u, 44u}, // jam -> Latn
+ {0xB8290000u, 44u}, // jbo -> Latn
+ {0xD0290000u, 44u}, // jbu -> Latn
+ {0xB4890000u, 44u}, // jen -> Latn
+ {0xA8C90000u, 44u}, // jgk -> Latn
+ {0xB8C90000u, 44u}, // jgo -> Latn
+ {0x6A690000u, 30u}, // ji -> Hebr
+ {0x85090000u, 44u}, // jib -> Latn
+ {0x89890000u, 44u}, // jmc -> Latn
+ {0xAD890000u, 17u}, // jml -> Deva
+ {0x82290000u, 44u}, // jra -> Latn
+ {0xCE890000u, 44u}, // jut -> Latn
+ {0x6A760000u, 44u}, // jv -> Latn
+ {0x6A770000u, 44u}, // jw -> Latn
+ {0x6B610000u, 20u}, // ka -> Geor
+ {0x800A0000u, 16u}, // kaa -> Cyrl
+ {0x840A0000u, 44u}, // kab -> Latn
+ {0x880A0000u, 44u}, // kac -> Latn
+ {0x8C0A0000u, 44u}, // kad -> Latn
+ {0xA00A0000u, 44u}, // kai -> Latn
+ {0xA40A0000u, 44u}, // kaj -> Latn
+ {0xB00A0000u, 44u}, // kam -> Latn
+ {0xB80A0000u, 44u}, // kao -> Latn
+ {0x8C2A0000u, 16u}, // kbd -> Cyrl
+ {0xB02A0000u, 44u}, // kbm -> Latn
+ {0xBC2A0000u, 44u}, // kbp -> Latn
+ {0xC02A0000u, 44u}, // kbq -> Latn
+ {0xDC2A0000u, 44u}, // kbx -> Latn
{0xE02A0000u, 1u}, // kby -> Arab
- {0x984A0000u, 40u}, // kcg -> Latn
- {0xA84A0000u, 40u}, // kck -> Latn
- {0xAC4A0000u, 40u}, // kcl -> Latn
- {0xCC4A0000u, 40u}, // kct -> Latn
- {0x906A0000u, 40u}, // kde -> Latn
+ {0x984A0000u, 44u}, // kcg -> Latn
+ {0xA84A0000u, 44u}, // kck -> Latn
+ {0xAC4A0000u, 44u}, // kcl -> Latn
+ {0xCC4A0000u, 44u}, // kct -> Latn
+ {0x906A0000u, 44u}, // kde -> Latn
{0x9C6A0000u, 1u}, // kdh -> Arab
- {0xAC6A0000u, 40u}, // kdl -> Latn
- {0xCC6A0000u, 80u}, // kdt -> Thai
- {0x808A0000u, 40u}, // kea -> Latn
- {0xB48A0000u, 40u}, // ken -> Latn
- {0xE48A0000u, 40u}, // kez -> Latn
- {0xB8AA0000u, 40u}, // kfo -> Latn
- {0xC4AA0000u, 16u}, // kfr -> Deva
- {0xE0AA0000u, 16u}, // kfy -> Deva
- {0x6B670000u, 40u}, // kg -> Latn
- {0x90CA0000u, 40u}, // kge -> Latn
- {0x94CA0000u, 40u}, // kgf -> Latn
- {0xBCCA0000u, 40u}, // kgp -> Latn
- {0x80EA0000u, 40u}, // kha -> Latn
- {0x84EA0000u, 73u}, // khb -> Talu
- {0xB4EA0000u, 16u}, // khn -> Deva
- {0xC0EA0000u, 40u}, // khq -> Latn
- {0xC8EA0000u, 40u}, // khs -> Latn
- {0xCCEA0000u, 52u}, // kht -> Mymr
+ {0xAC6A0000u, 44u}, // kdl -> Latn
+ {0xCC6A0000u, 87u}, // kdt -> Thai
+ {0x808A0000u, 44u}, // kea -> Latn
+ {0xB48A0000u, 44u}, // ken -> Latn
+ {0xE48A0000u, 44u}, // kez -> Latn
+ {0xB8AA0000u, 44u}, // kfo -> Latn
+ {0xC4AA0000u, 17u}, // kfr -> Deva
+ {0xE0AA0000u, 17u}, // kfy -> Deva
+ {0x6B670000u, 44u}, // kg -> Latn
+ {0x90CA0000u, 44u}, // kge -> Latn
+ {0x94CA0000u, 44u}, // kgf -> Latn
+ {0xBCCA0000u, 44u}, // kgp -> Latn
+ {0x80EA0000u, 44u}, // kha -> Latn
+ {0x84EA0000u, 80u}, // khb -> Talu
+ {0xB4EA0000u, 17u}, // khn -> Deva
+ {0xC0EA0000u, 44u}, // khq -> Latn
+ {0xC8EA0000u, 44u}, // khs -> Latn
+ {0xCCEA0000u, 56u}, // kht -> Mymr
{0xD8EA0000u, 1u}, // khw -> Arab
- {0xE4EA0000u, 40u}, // khz -> Latn
- {0x6B690000u, 40u}, // ki -> Latn
- {0xA50A0000u, 40u}, // kij -> Latn
- {0xD10A0000u, 40u}, // kiu -> Latn
- {0xD90A0000u, 40u}, // kiw -> Latn
- {0x6B6A0000u, 40u}, // kj -> Latn
- {0x8D2A0000u, 40u}, // kjd -> Latn
- {0x992A0000u, 39u}, // kjg -> Laoo
- {0xC92A0000u, 40u}, // kjs -> Latn
- {0xE12A0000u, 40u}, // kjy -> Latn
- {0x6B6B0000u, 15u}, // kk -> Cyrl
+ {0xE4EA0000u, 44u}, // khz -> Latn
+ {0x6B690000u, 44u}, // ki -> Latn
+ {0xA50A0000u, 44u}, // kij -> Latn
+ {0xD10A0000u, 44u}, // kiu -> Latn
+ {0xD90A0000u, 44u}, // kiw -> Latn
+ {0x6B6A0000u, 44u}, // kj -> Latn
+ {0x8D2A0000u, 44u}, // kjd -> Latn
+ {0x992A0000u, 43u}, // kjg -> Laoo
+ {0xC92A0000u, 44u}, // kjs -> Latn
+ {0xE12A0000u, 44u}, // kjy -> Latn
+ {0x6B6B0000u, 16u}, // kk -> Cyrl
{0x6B6B4146u, 1u}, // kk-AF -> Arab
{0x6B6B434Eu, 1u}, // kk-CN -> Arab
{0x6B6B4952u, 1u}, // kk-IR -> Arab
{0x6B6B4D4Eu, 1u}, // kk-MN -> Arab
- {0x894A0000u, 40u}, // kkc -> Latn
- {0xA54A0000u, 40u}, // kkj -> Latn
- {0x6B6C0000u, 40u}, // kl -> Latn
- {0xB56A0000u, 40u}, // kln -> Latn
- {0xC16A0000u, 40u}, // klq -> Latn
- {0xCD6A0000u, 40u}, // klt -> Latn
- {0xDD6A0000u, 40u}, // klx -> Latn
- {0x6B6D0000u, 35u}, // km -> Khmr
- {0x858A0000u, 40u}, // kmb -> Latn
- {0x9D8A0000u, 40u}, // kmh -> Latn
- {0xB98A0000u, 40u}, // kmo -> Latn
- {0xC98A0000u, 40u}, // kms -> Latn
- {0xD18A0000u, 40u}, // kmu -> Latn
- {0xD98A0000u, 40u}, // kmw -> Latn
- {0x6B6E0000u, 36u}, // kn -> Knda
- {0xBDAA0000u, 40u}, // knp -> Latn
- {0x6B6F0000u, 37u}, // ko -> Kore
- {0xA1CA0000u, 15u}, // koi -> Cyrl
- {0xA9CA0000u, 16u}, // kok -> Deva
- {0xADCA0000u, 40u}, // kol -> Latn
- {0xC9CA0000u, 40u}, // kos -> Latn
- {0xE5CA0000u, 40u}, // koz -> Latn
- {0x91EA0000u, 40u}, // kpe -> Latn
- {0x95EA0000u, 40u}, // kpf -> Latn
- {0xB9EA0000u, 40u}, // kpo -> Latn
- {0xC5EA0000u, 40u}, // kpr -> Latn
- {0xDDEA0000u, 40u}, // kpx -> Latn
- {0x860A0000u, 40u}, // kqb -> Latn
- {0x960A0000u, 40u}, // kqf -> Latn
- {0xCA0A0000u, 40u}, // kqs -> Latn
- {0xE20A0000u, 18u}, // kqy -> Ethi
- {0x8A2A0000u, 15u}, // krc -> Cyrl
- {0xA22A0000u, 40u}, // kri -> Latn
- {0xA62A0000u, 40u}, // krj -> Latn
- {0xAE2A0000u, 40u}, // krl -> Latn
- {0xCA2A0000u, 40u}, // krs -> Latn
- {0xD22A0000u, 16u}, // kru -> Deva
+ {0x894A0000u, 44u}, // kkc -> Latn
+ {0xA54A0000u, 44u}, // kkj -> Latn
+ {0x6B6C0000u, 44u}, // kl -> Latn
+ {0xB56A0000u, 44u}, // kln -> Latn
+ {0xC16A0000u, 44u}, // klq -> Latn
+ {0xCD6A0000u, 44u}, // klt -> Latn
+ {0xDD6A0000u, 44u}, // klx -> Latn
+ {0x6B6D0000u, 39u}, // km -> Khmr
+ {0x858A0000u, 44u}, // kmb -> Latn
+ {0x9D8A0000u, 44u}, // kmh -> Latn
+ {0xB98A0000u, 44u}, // kmo -> Latn
+ {0xC98A0000u, 44u}, // kms -> Latn
+ {0xD18A0000u, 44u}, // kmu -> Latn
+ {0xD98A0000u, 44u}, // kmw -> Latn
+ {0x6B6E0000u, 40u}, // kn -> Knda
+ {0x95AA0000u, 44u}, // knf -> Latn
+ {0xBDAA0000u, 44u}, // knp -> Latn
+ {0x6B6F0000u, 41u}, // ko -> Kore
+ {0xA1CA0000u, 16u}, // koi -> Cyrl
+ {0xA9CA0000u, 17u}, // kok -> Deva
+ {0xADCA0000u, 44u}, // kol -> Latn
+ {0xC9CA0000u, 44u}, // kos -> Latn
+ {0xE5CA0000u, 44u}, // koz -> Latn
+ {0x91EA0000u, 44u}, // kpe -> Latn
+ {0x95EA0000u, 44u}, // kpf -> Latn
+ {0xB9EA0000u, 44u}, // kpo -> Latn
+ {0xC5EA0000u, 44u}, // kpr -> Latn
+ {0xDDEA0000u, 44u}, // kpx -> Latn
+ {0x860A0000u, 44u}, // kqb -> Latn
+ {0x960A0000u, 44u}, // kqf -> Latn
+ {0xCA0A0000u, 44u}, // kqs -> Latn
+ {0xE20A0000u, 19u}, // kqy -> Ethi
+ {0x6B720000u, 44u}, // kr -> Latn
+ {0x8A2A0000u, 16u}, // krc -> Cyrl
+ {0xA22A0000u, 44u}, // kri -> Latn
+ {0xA62A0000u, 44u}, // krj -> Latn
+ {0xAE2A0000u, 44u}, // krl -> Latn
+ {0xCA2A0000u, 44u}, // krs -> Latn
+ {0xD22A0000u, 17u}, // kru -> Deva
{0x6B730000u, 1u}, // ks -> Arab
- {0x864A0000u, 40u}, // ksb -> Latn
- {0x8E4A0000u, 40u}, // ksd -> Latn
- {0x964A0000u, 40u}, // ksf -> Latn
- {0x9E4A0000u, 40u}, // ksh -> Latn
- {0xA64A0000u, 40u}, // ksj -> Latn
- {0xC64A0000u, 40u}, // ksr -> Latn
- {0x866A0000u, 18u}, // ktb -> Ethi
- {0xB26A0000u, 40u}, // ktm -> Latn
- {0xBA6A0000u, 40u}, // kto -> Latn
- {0x6B750000u, 40u}, // ku -> Latn
+ {0x864A0000u, 44u}, // ksb -> Latn
+ {0x8E4A0000u, 44u}, // ksd -> Latn
+ {0x964A0000u, 44u}, // ksf -> Latn
+ {0x9E4A0000u, 44u}, // ksh -> Latn
+ {0xA64A0000u, 44u}, // ksj -> Latn
+ {0xC64A0000u, 44u}, // ksr -> Latn
+ {0x866A0000u, 19u}, // ktb -> Ethi
+ {0xB26A0000u, 44u}, // ktm -> Latn
+ {0xBA6A0000u, 44u}, // kto -> Latn
+ {0x6B750000u, 44u}, // ku -> Latn
{0x6B754952u, 1u}, // ku-IR -> Arab
{0x6B754C42u, 1u}, // ku-LB -> Arab
- {0x868A0000u, 40u}, // kub -> Latn
- {0x8E8A0000u, 40u}, // kud -> Latn
- {0x928A0000u, 40u}, // kue -> Latn
- {0xA68A0000u, 40u}, // kuj -> Latn
- {0xB28A0000u, 15u}, // kum -> Cyrl
- {0xB68A0000u, 40u}, // kun -> Latn
- {0xBE8A0000u, 40u}, // kup -> Latn
- {0xCA8A0000u, 40u}, // kus -> Latn
- {0x6B760000u, 15u}, // kv -> Cyrl
- {0x9AAA0000u, 40u}, // kvg -> Latn
- {0xC6AA0000u, 40u}, // kvr -> Latn
+ {0x868A0000u, 44u}, // kub -> Latn
+ {0x8E8A0000u, 44u}, // kud -> Latn
+ {0x928A0000u, 44u}, // kue -> Latn
+ {0xA68A0000u, 44u}, // kuj -> Latn
+ {0xB28A0000u, 16u}, // kum -> Cyrl
+ {0xB68A0000u, 44u}, // kun -> Latn
+ {0xBE8A0000u, 44u}, // kup -> Latn
+ {0xCA8A0000u, 44u}, // kus -> Latn
+ {0x6B760000u, 16u}, // kv -> Cyrl
+ {0x9AAA0000u, 44u}, // kvg -> Latn
+ {0xC6AA0000u, 44u}, // kvr -> Latn
{0xDEAA0000u, 1u}, // kvx -> Arab
- {0x6B770000u, 40u}, // kw -> Latn
- {0xA6CA0000u, 40u}, // kwj -> Latn
- {0xBACA0000u, 40u}, // kwo -> Latn
- {0x82EA0000u, 40u}, // kxa -> Latn
- {0x8AEA0000u, 18u}, // kxc -> Ethi
- {0xB2EA0000u, 80u}, // kxm -> Thai
+ {0x6B770000u, 44u}, // kw -> Latn
+ {0xA6CA0000u, 44u}, // kwj -> Latn
+ {0xBACA0000u, 44u}, // kwo -> Latn
+ {0x82EA0000u, 44u}, // kxa -> Latn
+ {0x8AEA0000u, 19u}, // kxc -> Ethi
+ {0xB2EA0000u, 87u}, // kxm -> Thai
{0xBEEA0000u, 1u}, // kxp -> Arab
- {0xDAEA0000u, 40u}, // kxw -> Latn
- {0xE6EA0000u, 40u}, // kxz -> Latn
- {0x6B790000u, 15u}, // ky -> Cyrl
+ {0xDAEA0000u, 44u}, // kxw -> Latn
+ {0xE6EA0000u, 44u}, // kxz -> Latn
+ {0x6B790000u, 16u}, // ky -> Cyrl
{0x6B79434Eu, 1u}, // ky-CN -> Arab
- {0x6B795452u, 40u}, // ky-TR -> Latn
- {0x930A0000u, 40u}, // kye -> Latn
- {0xDF0A0000u, 40u}, // kyx -> Latn
- {0xC72A0000u, 40u}, // kzr -> Latn
- {0x6C610000u, 40u}, // la -> Latn
- {0x840B0000u, 42u}, // lab -> Lina
- {0x8C0B0000u, 27u}, // lad -> Hebr
- {0x980B0000u, 40u}, // lag -> Latn
+ {0x6B795452u, 44u}, // ky-TR -> Latn
+ {0x930A0000u, 44u}, // kye -> Latn
+ {0xDF0A0000u, 44u}, // kyx -> Latn
+ {0xC72A0000u, 44u}, // kzr -> Latn
+ {0x6C610000u, 44u}, // la -> Latn
+ {0x840B0000u, 46u}, // lab -> Lina
+ {0x8C0B0000u, 30u}, // lad -> Hebr
+ {0x980B0000u, 44u}, // lag -> Latn
{0x9C0B0000u, 1u}, // lah -> Arab
- {0xA40B0000u, 40u}, // laj -> Latn
- {0xC80B0000u, 40u}, // las -> Latn
- {0x6C620000u, 40u}, // lb -> Latn
- {0x902B0000u, 15u}, // lbe -> Cyrl
- {0xD02B0000u, 40u}, // lbu -> Latn
- {0xD82B0000u, 40u}, // lbw -> Latn
- {0xB04B0000u, 40u}, // lcm -> Latn
- {0xBC4B0000u, 80u}, // lcp -> Thai
- {0x846B0000u, 40u}, // ldb -> Latn
- {0x8C8B0000u, 40u}, // led -> Latn
- {0x908B0000u, 40u}, // lee -> Latn
- {0xB08B0000u, 40u}, // lem -> Latn
- {0xBC8B0000u, 41u}, // lep -> Lepc
- {0xC08B0000u, 40u}, // leq -> Latn
- {0xD08B0000u, 40u}, // leu -> Latn
- {0xE48B0000u, 15u}, // lez -> Cyrl
- {0x6C670000u, 40u}, // lg -> Latn
- {0x98CB0000u, 40u}, // lgg -> Latn
- {0x6C690000u, 40u}, // li -> Latn
- {0x810B0000u, 40u}, // lia -> Latn
- {0x8D0B0000u, 40u}, // lid -> Latn
- {0x950B0000u, 16u}, // lif -> Deva
- {0x990B0000u, 40u}, // lig -> Latn
- {0x9D0B0000u, 40u}, // lih -> Latn
- {0xA50B0000u, 40u}, // lij -> Latn
- {0xC90B0000u, 43u}, // lis -> Lisu
- {0xBD2B0000u, 40u}, // ljp -> Latn
+ {0xA40B0000u, 44u}, // laj -> Latn
+ {0xC80B0000u, 44u}, // las -> Latn
+ {0x6C620000u, 44u}, // lb -> Latn
+ {0x902B0000u, 16u}, // lbe -> Cyrl
+ {0xD02B0000u, 44u}, // lbu -> Latn
+ {0xD82B0000u, 44u}, // lbw -> Latn
+ {0xB04B0000u, 44u}, // lcm -> Latn
+ {0xBC4B0000u, 87u}, // lcp -> Thai
+ {0x846B0000u, 44u}, // ldb -> Latn
+ {0x8C8B0000u, 44u}, // led -> Latn
+ {0x908B0000u, 44u}, // lee -> Latn
+ {0xB08B0000u, 44u}, // lem -> Latn
+ {0xBC8B0000u, 45u}, // lep -> Lepc
+ {0xC08B0000u, 44u}, // leq -> Latn
+ {0xD08B0000u, 44u}, // leu -> Latn
+ {0xE48B0000u, 16u}, // lez -> Cyrl
+ {0x6C670000u, 44u}, // lg -> Latn
+ {0x98CB0000u, 44u}, // lgg -> Latn
+ {0x6C690000u, 44u}, // li -> Latn
+ {0x810B0000u, 44u}, // lia -> Latn
+ {0x8D0B0000u, 44u}, // lid -> Latn
+ {0x950B0000u, 17u}, // lif -> Deva
+ {0x990B0000u, 44u}, // lig -> Latn
+ {0x9D0B0000u, 44u}, // lih -> Latn
+ {0xA50B0000u, 44u}, // lij -> Latn
+ {0xC90B0000u, 47u}, // lis -> Lisu
+ {0xBD2B0000u, 44u}, // ljp -> Latn
{0xA14B0000u, 1u}, // lki -> Arab
- {0xCD4B0000u, 40u}, // lkt -> Latn
- {0x916B0000u, 40u}, // lle -> Latn
- {0xB56B0000u, 40u}, // lln -> Latn
- {0xB58B0000u, 77u}, // lmn -> Telu
- {0xB98B0000u, 40u}, // lmo -> Latn
- {0xBD8B0000u, 40u}, // lmp -> Latn
- {0x6C6E0000u, 40u}, // ln -> Latn
- {0xC9AB0000u, 40u}, // lns -> Latn
- {0xD1AB0000u, 40u}, // lnu -> Latn
- {0x6C6F0000u, 39u}, // lo -> Laoo
- {0xA5CB0000u, 40u}, // loj -> Latn
- {0xA9CB0000u, 40u}, // lok -> Latn
- {0xADCB0000u, 40u}, // lol -> Latn
- {0xC5CB0000u, 40u}, // lor -> Latn
- {0xC9CB0000u, 40u}, // los -> Latn
- {0xE5CB0000u, 40u}, // loz -> Latn
+ {0xCD4B0000u, 44u}, // lkt -> Latn
+ {0x916B0000u, 44u}, // lle -> Latn
+ {0xB56B0000u, 44u}, // lln -> Latn
+ {0xB58B0000u, 84u}, // lmn -> Telu
+ {0xB98B0000u, 44u}, // lmo -> Latn
+ {0xBD8B0000u, 44u}, // lmp -> Latn
+ {0x6C6E0000u, 44u}, // ln -> Latn
+ {0xC9AB0000u, 44u}, // lns -> Latn
+ {0xD1AB0000u, 44u}, // lnu -> Latn
+ {0x6C6F0000u, 43u}, // lo -> Laoo
+ {0xA5CB0000u, 44u}, // loj -> Latn
+ {0xA9CB0000u, 44u}, // lok -> Latn
+ {0xADCB0000u, 44u}, // lol -> Latn
+ {0xC5CB0000u, 44u}, // lor -> Latn
+ {0xC9CB0000u, 44u}, // los -> Latn
+ {0xE5CB0000u, 44u}, // loz -> Latn
{0x8A2B0000u, 1u}, // lrc -> Arab
- {0x6C740000u, 40u}, // lt -> Latn
- {0x9A6B0000u, 40u}, // ltg -> Latn
- {0x6C750000u, 40u}, // lu -> Latn
- {0x828B0000u, 40u}, // lua -> Latn
- {0xBA8B0000u, 40u}, // luo -> Latn
- {0xE28B0000u, 40u}, // luy -> Latn
+ {0x6C740000u, 44u}, // lt -> Latn
+ {0x9A6B0000u, 44u}, // ltg -> Latn
+ {0x6C750000u, 44u}, // lu -> Latn
+ {0x828B0000u, 44u}, // lua -> Latn
+ {0xBA8B0000u, 44u}, // luo -> Latn
+ {0xE28B0000u, 44u}, // luy -> Latn
{0xE68B0000u, 1u}, // luz -> Arab
- {0x6C760000u, 40u}, // lv -> Latn
- {0xAECB0000u, 80u}, // lwl -> Thai
- {0x9F2B0000u, 24u}, // lzh -> Hans
- {0xE72B0000u, 40u}, // lzz -> Latn
- {0x8C0C0000u, 40u}, // mad -> Latn
- {0x940C0000u, 40u}, // maf -> Latn
- {0x980C0000u, 16u}, // mag -> Deva
- {0xA00C0000u, 16u}, // mai -> Deva
- {0xA80C0000u, 40u}, // mak -> Latn
- {0xB40C0000u, 40u}, // man -> Latn
- {0xB40C474Eu, 54u}, // man-GN -> Nkoo
- {0xC80C0000u, 40u}, // mas -> Latn
- {0xD80C0000u, 40u}, // maw -> Latn
- {0xE40C0000u, 40u}, // maz -> Latn
- {0x9C2C0000u, 40u}, // mbh -> Latn
- {0xB82C0000u, 40u}, // mbo -> Latn
- {0xC02C0000u, 40u}, // mbq -> Latn
- {0xD02C0000u, 40u}, // mbu -> Latn
- {0xD82C0000u, 40u}, // mbw -> Latn
- {0xA04C0000u, 40u}, // mci -> Latn
- {0xBC4C0000u, 40u}, // mcp -> Latn
- {0xC04C0000u, 40u}, // mcq -> Latn
- {0xC44C0000u, 40u}, // mcr -> Latn
- {0xD04C0000u, 40u}, // mcu -> Latn
- {0x806C0000u, 40u}, // mda -> Latn
+ {0x6C760000u, 44u}, // lv -> Latn
+ {0xAECB0000u, 87u}, // lwl -> Thai
+ {0x9F2B0000u, 27u}, // lzh -> Hans
+ {0xE72B0000u, 44u}, // lzz -> Latn
+ {0x8C0C0000u, 44u}, // mad -> Latn
+ {0x940C0000u, 44u}, // maf -> Latn
+ {0x980C0000u, 17u}, // mag -> Deva
+ {0xA00C0000u, 17u}, // mai -> Deva
+ {0xA80C0000u, 44u}, // mak -> Latn
+ {0xB40C0000u, 44u}, // man -> Latn
+ {0xB40C474Eu, 58u}, // man-GN -> Nkoo
+ {0xC80C0000u, 44u}, // mas -> Latn
+ {0xD80C0000u, 44u}, // maw -> Latn
+ {0xE40C0000u, 44u}, // maz -> Latn
+ {0x9C2C0000u, 44u}, // mbh -> Latn
+ {0xB82C0000u, 44u}, // mbo -> Latn
+ {0xC02C0000u, 44u}, // mbq -> Latn
+ {0xD02C0000u, 44u}, // mbu -> Latn
+ {0xD82C0000u, 44u}, // mbw -> Latn
+ {0xA04C0000u, 44u}, // mci -> Latn
+ {0xBC4C0000u, 44u}, // mcp -> Latn
+ {0xC04C0000u, 44u}, // mcq -> Latn
+ {0xC44C0000u, 44u}, // mcr -> Latn
+ {0xD04C0000u, 44u}, // mcu -> Latn
+ {0x806C0000u, 44u}, // mda -> Latn
{0x906C0000u, 1u}, // mde -> Arab
- {0x946C0000u, 15u}, // mdf -> Cyrl
- {0x9C6C0000u, 40u}, // mdh -> Latn
- {0xA46C0000u, 40u}, // mdj -> Latn
- {0xC46C0000u, 40u}, // mdr -> Latn
- {0xDC6C0000u, 18u}, // mdx -> Ethi
- {0x8C8C0000u, 40u}, // med -> Latn
- {0x908C0000u, 40u}, // mee -> Latn
- {0xA88C0000u, 40u}, // mek -> Latn
- {0xB48C0000u, 40u}, // men -> Latn
- {0xC48C0000u, 40u}, // mer -> Latn
- {0xCC8C0000u, 40u}, // met -> Latn
- {0xD08C0000u, 40u}, // meu -> Latn
+ {0x946C0000u, 16u}, // mdf -> Cyrl
+ {0x9C6C0000u, 44u}, // mdh -> Latn
+ {0xA46C0000u, 44u}, // mdj -> Latn
+ {0xC46C0000u, 44u}, // mdr -> Latn
+ {0xDC6C0000u, 19u}, // mdx -> Ethi
+ {0x8C8C0000u, 44u}, // med -> Latn
+ {0x908C0000u, 44u}, // mee -> Latn
+ {0xA88C0000u, 44u}, // mek -> Latn
+ {0xB48C0000u, 44u}, // men -> Latn
+ {0xC48C0000u, 44u}, // mer -> Latn
+ {0xCC8C0000u, 44u}, // met -> Latn
+ {0xD08C0000u, 44u}, // meu -> Latn
{0x80AC0000u, 1u}, // mfa -> Arab
- {0x90AC0000u, 40u}, // mfe -> Latn
- {0xB4AC0000u, 40u}, // mfn -> Latn
- {0xB8AC0000u, 40u}, // mfo -> Latn
- {0xC0AC0000u, 40u}, // mfq -> Latn
- {0x6D670000u, 40u}, // mg -> Latn
- {0x9CCC0000u, 40u}, // mgh -> Latn
- {0xACCC0000u, 40u}, // mgl -> Latn
- {0xB8CC0000u, 40u}, // mgo -> Latn
- {0xBCCC0000u, 16u}, // mgp -> Deva
- {0xE0CC0000u, 40u}, // mgy -> Latn
- {0x6D680000u, 40u}, // mh -> Latn
- {0xA0EC0000u, 40u}, // mhi -> Latn
- {0xACEC0000u, 40u}, // mhl -> Latn
- {0x6D690000u, 40u}, // mi -> Latn
- {0x950C0000u, 40u}, // mif -> Latn
- {0xB50C0000u, 40u}, // min -> Latn
- {0xC90C0000u, 26u}, // mis -> Hatr
- {0xD90C0000u, 40u}, // miw -> Latn
- {0x6D6B0000u, 15u}, // mk -> Cyrl
+ {0x90AC0000u, 44u}, // mfe -> Latn
+ {0xB4AC0000u, 44u}, // mfn -> Latn
+ {0xB8AC0000u, 44u}, // mfo -> Latn
+ {0xC0AC0000u, 44u}, // mfq -> Latn
+ {0x6D670000u, 44u}, // mg -> Latn
+ {0x9CCC0000u, 44u}, // mgh -> Latn
+ {0xACCC0000u, 44u}, // mgl -> Latn
+ {0xB8CC0000u, 44u}, // mgo -> Latn
+ {0xBCCC0000u, 17u}, // mgp -> Deva
+ {0xE0CC0000u, 44u}, // mgy -> Latn
+ {0x6D680000u, 44u}, // mh -> Latn
+ {0xA0EC0000u, 44u}, // mhi -> Latn
+ {0xACEC0000u, 44u}, // mhl -> Latn
+ {0x6D690000u, 44u}, // mi -> Latn
+ {0x950C0000u, 44u}, // mif -> Latn
+ {0xB50C0000u, 44u}, // min -> Latn
+ {0xC90C0000u, 29u}, // mis -> Hatr
+ {0xD90C0000u, 44u}, // miw -> Latn
+ {0x6D6B0000u, 16u}, // mk -> Cyrl
{0xA14C0000u, 1u}, // mki -> Arab
- {0xAD4C0000u, 40u}, // mkl -> Latn
- {0xBD4C0000u, 40u}, // mkp -> Latn
- {0xD94C0000u, 40u}, // mkw -> Latn
- {0x6D6C0000u, 49u}, // ml -> Mlym
- {0x916C0000u, 40u}, // mle -> Latn
- {0xBD6C0000u, 40u}, // mlp -> Latn
- {0xC96C0000u, 40u}, // mls -> Latn
- {0xB98C0000u, 40u}, // mmo -> Latn
- {0xD18C0000u, 40u}, // mmu -> Latn
- {0xDD8C0000u, 40u}, // mmx -> Latn
- {0x6D6E0000u, 15u}, // mn -> Cyrl
- {0x6D6E434Eu, 50u}, // mn-CN -> Mong
- {0x81AC0000u, 40u}, // mna -> Latn
- {0x95AC0000u, 40u}, // mnf -> Latn
+ {0xAD4C0000u, 44u}, // mkl -> Latn
+ {0xBD4C0000u, 44u}, // mkp -> Latn
+ {0xD94C0000u, 44u}, // mkw -> Latn
+ {0x6D6C0000u, 53u}, // ml -> Mlym
+ {0x916C0000u, 44u}, // mle -> Latn
+ {0xBD6C0000u, 44u}, // mlp -> Latn
+ {0xC96C0000u, 44u}, // mls -> Latn
+ {0xB98C0000u, 44u}, // mmo -> Latn
+ {0xD18C0000u, 44u}, // mmu -> Latn
+ {0xDD8C0000u, 44u}, // mmx -> Latn
+ {0x6D6E0000u, 16u}, // mn -> Cyrl
+ {0x6D6E434Eu, 54u}, // mn-CN -> Mong
+ {0x81AC0000u, 44u}, // mna -> Latn
+ {0x95AC0000u, 44u}, // mnf -> Latn
{0xA1AC0000u, 7u}, // mni -> Beng
- {0xD9AC0000u, 52u}, // mnw -> Mymr
- {0x81CC0000u, 40u}, // moa -> Latn
- {0x91CC0000u, 40u}, // moe -> Latn
- {0x9DCC0000u, 40u}, // moh -> Latn
- {0xC9CC0000u, 40u}, // mos -> Latn
- {0xDDCC0000u, 40u}, // mox -> Latn
- {0xBDEC0000u, 40u}, // mpp -> Latn
- {0xC9EC0000u, 40u}, // mps -> Latn
- {0xCDEC0000u, 40u}, // mpt -> Latn
- {0xDDEC0000u, 40u}, // mpx -> Latn
- {0xAE0C0000u, 40u}, // mql -> Latn
- {0x6D720000u, 16u}, // mr -> Deva
- {0x8E2C0000u, 16u}, // mrd -> Deva
- {0xA62C0000u, 15u}, // mrj -> Cyrl
- {0xBA2C0000u, 51u}, // mro -> Mroo
- {0x6D730000u, 40u}, // ms -> Latn
+ {0xD9AC0000u, 56u}, // mnw -> Mymr
+ {0x81CC0000u, 44u}, // moa -> Latn
+ {0x91CC0000u, 44u}, // moe -> Latn
+ {0x9DCC0000u, 44u}, // moh -> Latn
+ {0xC9CC0000u, 44u}, // mos -> Latn
+ {0xDDCC0000u, 44u}, // mox -> Latn
+ {0xBDEC0000u, 44u}, // mpp -> Latn
+ {0xC9EC0000u, 44u}, // mps -> Latn
+ {0xCDEC0000u, 44u}, // mpt -> Latn
+ {0xDDEC0000u, 44u}, // mpx -> Latn
+ {0xAE0C0000u, 44u}, // mql -> Latn
+ {0x6D720000u, 17u}, // mr -> Deva
+ {0x8E2C0000u, 17u}, // mrd -> Deva
+ {0xA62C0000u, 16u}, // mrj -> Cyrl
+ {0xBA2C0000u, 55u}, // mro -> Mroo
+ {0x6D730000u, 44u}, // ms -> Latn
{0x6D734343u, 1u}, // ms-CC -> Arab
{0x6D734944u, 1u}, // ms-ID -> Arab
- {0x6D740000u, 40u}, // mt -> Latn
- {0x8A6C0000u, 40u}, // mtc -> Latn
- {0x966C0000u, 40u}, // mtf -> Latn
- {0xA26C0000u, 40u}, // mti -> Latn
- {0xC66C0000u, 16u}, // mtr -> Deva
- {0x828C0000u, 40u}, // mua -> Latn
- {0xC68C0000u, 40u}, // mur -> Latn
- {0xCA8C0000u, 40u}, // mus -> Latn
- {0x82AC0000u, 40u}, // mva -> Latn
- {0xB6AC0000u, 40u}, // mvn -> Latn
+ {0x6D740000u, 44u}, // mt -> Latn
+ {0x8A6C0000u, 44u}, // mtc -> Latn
+ {0x966C0000u, 44u}, // mtf -> Latn
+ {0xA26C0000u, 44u}, // mti -> Latn
+ {0xC66C0000u, 17u}, // mtr -> Deva
+ {0x828C0000u, 44u}, // mua -> Latn
+ {0xC68C0000u, 44u}, // mur -> Latn
+ {0xCA8C0000u, 44u}, // mus -> Latn
+ {0x82AC0000u, 44u}, // mva -> Latn
+ {0xB6AC0000u, 44u}, // mvn -> Latn
{0xE2AC0000u, 1u}, // mvy -> Arab
- {0xAACC0000u, 40u}, // mwk -> Latn
- {0xC6CC0000u, 16u}, // mwr -> Deva
- {0xD6CC0000u, 40u}, // mwv -> Latn
- {0x8AEC0000u, 40u}, // mxc -> Latn
- {0xB2EC0000u, 40u}, // mxm -> Latn
- {0x6D790000u, 52u}, // my -> Mymr
- {0xAB0C0000u, 40u}, // myk -> Latn
- {0xB30C0000u, 18u}, // mym -> Ethi
- {0xD70C0000u, 15u}, // myv -> Cyrl
- {0xDB0C0000u, 40u}, // myw -> Latn
- {0xDF0C0000u, 40u}, // myx -> Latn
- {0xE70C0000u, 46u}, // myz -> Mand
- {0xAB2C0000u, 40u}, // mzk -> Latn
- {0xB32C0000u, 40u}, // mzm -> Latn
+ {0xAACC0000u, 44u}, // mwk -> Latn
+ {0xC6CC0000u, 17u}, // mwr -> Deva
+ {0xD6CC0000u, 44u}, // mwv -> Latn
+ {0xDACC0000u, 33u}, // mww -> Hmnp
+ {0x8AEC0000u, 44u}, // mxc -> Latn
+ {0xB2EC0000u, 44u}, // mxm -> Latn
+ {0x6D790000u, 56u}, // my -> Mymr
+ {0xAB0C0000u, 44u}, // myk -> Latn
+ {0xB30C0000u, 19u}, // mym -> Ethi
+ {0xD70C0000u, 16u}, // myv -> Cyrl
+ {0xDB0C0000u, 44u}, // myw -> Latn
+ {0xDF0C0000u, 44u}, // myx -> Latn
+ {0xE70C0000u, 50u}, // myz -> Mand
+ {0xAB2C0000u, 44u}, // mzk -> Latn
+ {0xB32C0000u, 44u}, // mzm -> Latn
{0xB72C0000u, 1u}, // mzn -> Arab
- {0xBF2C0000u, 40u}, // mzp -> Latn
- {0xDB2C0000u, 40u}, // mzw -> Latn
- {0xE72C0000u, 40u}, // mzz -> Latn
- {0x6E610000u, 40u}, // na -> Latn
- {0x880D0000u, 40u}, // nac -> Latn
- {0x940D0000u, 40u}, // naf -> Latn
- {0xA80D0000u, 40u}, // nak -> Latn
- {0xB40D0000u, 24u}, // nan -> Hans
- {0xBC0D0000u, 40u}, // nap -> Latn
- {0xC00D0000u, 40u}, // naq -> Latn
- {0xC80D0000u, 40u}, // nas -> Latn
- {0x6E620000u, 40u}, // nb -> Latn
- {0x804D0000u, 40u}, // nca -> Latn
- {0x904D0000u, 40u}, // nce -> Latn
- {0x944D0000u, 40u}, // ncf -> Latn
- {0x9C4D0000u, 40u}, // nch -> Latn
- {0xB84D0000u, 40u}, // nco -> Latn
- {0xD04D0000u, 40u}, // ncu -> Latn
- {0x6E640000u, 40u}, // nd -> Latn
- {0x886D0000u, 40u}, // ndc -> Latn
- {0xC86D0000u, 40u}, // nds -> Latn
- {0x6E650000u, 16u}, // ne -> Deva
- {0x848D0000u, 40u}, // neb -> Latn
- {0xD88D0000u, 16u}, // new -> Deva
- {0xDC8D0000u, 40u}, // nex -> Latn
- {0xC4AD0000u, 40u}, // nfr -> Latn
- {0x6E670000u, 40u}, // ng -> Latn
- {0x80CD0000u, 40u}, // nga -> Latn
- {0x84CD0000u, 40u}, // ngb -> Latn
- {0xACCD0000u, 40u}, // ngl -> Latn
- {0x84ED0000u, 40u}, // nhb -> Latn
- {0x90ED0000u, 40u}, // nhe -> Latn
- {0xD8ED0000u, 40u}, // nhw -> Latn
- {0x950D0000u, 40u}, // nif -> Latn
- {0xA10D0000u, 40u}, // nii -> Latn
- {0xA50D0000u, 40u}, // nij -> Latn
- {0xB50D0000u, 40u}, // nin -> Latn
- {0xD10D0000u, 40u}, // niu -> Latn
- {0xE10D0000u, 40u}, // niy -> Latn
- {0xE50D0000u, 40u}, // niz -> Latn
- {0xB92D0000u, 40u}, // njo -> Latn
- {0x994D0000u, 40u}, // nkg -> Latn
- {0xB94D0000u, 40u}, // nko -> Latn
- {0x6E6C0000u, 40u}, // nl -> Latn
- {0x998D0000u, 40u}, // nmg -> Latn
- {0xE58D0000u, 40u}, // nmz -> Latn
- {0x6E6E0000u, 40u}, // nn -> Latn
- {0x95AD0000u, 40u}, // nnf -> Latn
- {0x9DAD0000u, 40u}, // nnh -> Latn
- {0xA9AD0000u, 40u}, // nnk -> Latn
- {0xB1AD0000u, 40u}, // nnm -> Latn
- {0x6E6F0000u, 40u}, // no -> Latn
- {0x8DCD0000u, 38u}, // nod -> Lana
- {0x91CD0000u, 16u}, // noe -> Deva
- {0xB5CD0000u, 64u}, // non -> Runr
- {0xBDCD0000u, 40u}, // nop -> Latn
- {0xD1CD0000u, 40u}, // nou -> Latn
- {0xBA0D0000u, 54u}, // nqo -> Nkoo
- {0x6E720000u, 40u}, // nr -> Latn
- {0x862D0000u, 40u}, // nrb -> Latn
- {0xAA4D0000u, 9u}, // nsk -> Cans
- {0xB64D0000u, 40u}, // nsn -> Latn
- {0xBA4D0000u, 40u}, // nso -> Latn
- {0xCA4D0000u, 40u}, // nss -> Latn
- {0xB26D0000u, 40u}, // ntm -> Latn
- {0xC66D0000u, 40u}, // ntr -> Latn
- {0xA28D0000u, 40u}, // nui -> Latn
- {0xBE8D0000u, 40u}, // nup -> Latn
- {0xCA8D0000u, 40u}, // nus -> Latn
- {0xD68D0000u, 40u}, // nuv -> Latn
- {0xDE8D0000u, 40u}, // nux -> Latn
- {0x6E760000u, 40u}, // nv -> Latn
- {0x86CD0000u, 40u}, // nwb -> Latn
- {0xC2ED0000u, 40u}, // nxq -> Latn
- {0xC6ED0000u, 40u}, // nxr -> Latn
- {0x6E790000u, 40u}, // ny -> Latn
- {0xB30D0000u, 40u}, // nym -> Latn
- {0xB70D0000u, 40u}, // nyn -> Latn
- {0xA32D0000u, 40u}, // nzi -> Latn
- {0x6F630000u, 40u}, // oc -> Latn
- {0x88CE0000u, 40u}, // ogc -> Latn
- {0xC54E0000u, 40u}, // okr -> Latn
- {0xD54E0000u, 40u}, // okv -> Latn
- {0x6F6D0000u, 40u}, // om -> Latn
- {0x99AE0000u, 40u}, // ong -> Latn
- {0xB5AE0000u, 40u}, // onn -> Latn
- {0xC9AE0000u, 40u}, // ons -> Latn
- {0xB1EE0000u, 40u}, // opm -> Latn
- {0x6F720000u, 57u}, // or -> Orya
- {0xBA2E0000u, 40u}, // oro -> Latn
+ {0xBF2C0000u, 44u}, // mzp -> Latn
+ {0xDB2C0000u, 44u}, // mzw -> Latn
+ {0xE72C0000u, 44u}, // mzz -> Latn
+ {0x6E610000u, 44u}, // na -> Latn
+ {0x880D0000u, 44u}, // nac -> Latn
+ {0x940D0000u, 44u}, // naf -> Latn
+ {0xA80D0000u, 44u}, // nak -> Latn
+ {0xB40D0000u, 27u}, // nan -> Hans
+ {0xBC0D0000u, 44u}, // nap -> Latn
+ {0xC00D0000u, 44u}, // naq -> Latn
+ {0xC80D0000u, 44u}, // nas -> Latn
+ {0x6E620000u, 44u}, // nb -> Latn
+ {0x804D0000u, 44u}, // nca -> Latn
+ {0x904D0000u, 44u}, // nce -> Latn
+ {0x944D0000u, 44u}, // ncf -> Latn
+ {0x9C4D0000u, 44u}, // nch -> Latn
+ {0xB84D0000u, 44u}, // nco -> Latn
+ {0xD04D0000u, 44u}, // ncu -> Latn
+ {0x6E640000u, 44u}, // nd -> Latn
+ {0x886D0000u, 44u}, // ndc -> Latn
+ {0xC86D0000u, 44u}, // nds -> Latn
+ {0x6E650000u, 17u}, // ne -> Deva
+ {0x848D0000u, 44u}, // neb -> Latn
+ {0xD88D0000u, 17u}, // new -> Deva
+ {0xDC8D0000u, 44u}, // nex -> Latn
+ {0xC4AD0000u, 44u}, // nfr -> Latn
+ {0x6E670000u, 44u}, // ng -> Latn
+ {0x80CD0000u, 44u}, // nga -> Latn
+ {0x84CD0000u, 44u}, // ngb -> Latn
+ {0xACCD0000u, 44u}, // ngl -> Latn
+ {0x84ED0000u, 44u}, // nhb -> Latn
+ {0x90ED0000u, 44u}, // nhe -> Latn
+ {0xD8ED0000u, 44u}, // nhw -> Latn
+ {0x950D0000u, 44u}, // nif -> Latn
+ {0xA10D0000u, 44u}, // nii -> Latn
+ {0xA50D0000u, 44u}, // nij -> Latn
+ {0xB50D0000u, 44u}, // nin -> Latn
+ {0xD10D0000u, 44u}, // niu -> Latn
+ {0xE10D0000u, 44u}, // niy -> Latn
+ {0xE50D0000u, 44u}, // niz -> Latn
+ {0xB92D0000u, 44u}, // njo -> Latn
+ {0x994D0000u, 44u}, // nkg -> Latn
+ {0xB94D0000u, 44u}, // nko -> Latn
+ {0x6E6C0000u, 44u}, // nl -> Latn
+ {0x998D0000u, 44u}, // nmg -> Latn
+ {0xE58D0000u, 44u}, // nmz -> Latn
+ {0x6E6E0000u, 44u}, // nn -> Latn
+ {0x95AD0000u, 44u}, // nnf -> Latn
+ {0x9DAD0000u, 44u}, // nnh -> Latn
+ {0xA9AD0000u, 44u}, // nnk -> Latn
+ {0xB1AD0000u, 44u}, // nnm -> Latn
+ {0xBDAD0000u, 91u}, // nnp -> Wcho
+ {0x6E6F0000u, 44u}, // no -> Latn
+ {0x8DCD0000u, 42u}, // nod -> Lana
+ {0x91CD0000u, 17u}, // noe -> Deva
+ {0xB5CD0000u, 69u}, // non -> Runr
+ {0xBDCD0000u, 44u}, // nop -> Latn
+ {0xD1CD0000u, 44u}, // nou -> Latn
+ {0xBA0D0000u, 58u}, // nqo -> Nkoo
+ {0x6E720000u, 44u}, // nr -> Latn
+ {0x862D0000u, 44u}, // nrb -> Latn
+ {0xAA4D0000u, 10u}, // nsk -> Cans
+ {0xB64D0000u, 44u}, // nsn -> Latn
+ {0xBA4D0000u, 44u}, // nso -> Latn
+ {0xCA4D0000u, 44u}, // nss -> Latn
+ {0xB26D0000u, 44u}, // ntm -> Latn
+ {0xC66D0000u, 44u}, // ntr -> Latn
+ {0xA28D0000u, 44u}, // nui -> Latn
+ {0xBE8D0000u, 44u}, // nup -> Latn
+ {0xCA8D0000u, 44u}, // nus -> Latn
+ {0xD68D0000u, 44u}, // nuv -> Latn
+ {0xDE8D0000u, 44u}, // nux -> Latn
+ {0x6E760000u, 44u}, // nv -> Latn
+ {0x86CD0000u, 44u}, // nwb -> Latn
+ {0xC2ED0000u, 44u}, // nxq -> Latn
+ {0xC6ED0000u, 44u}, // nxr -> Latn
+ {0x6E790000u, 44u}, // ny -> Latn
+ {0xB30D0000u, 44u}, // nym -> Latn
+ {0xB70D0000u, 44u}, // nyn -> Latn
+ {0xA32D0000u, 44u}, // nzi -> Latn
+ {0x6F630000u, 44u}, // oc -> Latn
+ {0x88CE0000u, 44u}, // ogc -> Latn
+ {0xC54E0000u, 44u}, // okr -> Latn
+ {0xD54E0000u, 44u}, // okv -> Latn
+ {0x6F6D0000u, 44u}, // om -> Latn
+ {0x99AE0000u, 44u}, // ong -> Latn
+ {0xB5AE0000u, 44u}, // onn -> Latn
+ {0xC9AE0000u, 44u}, // ons -> Latn
+ {0xB1EE0000u, 44u}, // opm -> Latn
+ {0x6F720000u, 62u}, // or -> Orya
+ {0xBA2E0000u, 44u}, // oro -> Latn
{0xD22E0000u, 1u}, // oru -> Arab
- {0x6F730000u, 15u}, // os -> Cyrl
- {0x824E0000u, 58u}, // osa -> Osge
+ {0x6F730000u, 16u}, // os -> Cyrl
+ {0x824E0000u, 63u}, // osa -> Osge
{0x826E0000u, 1u}, // ota -> Arab
- {0xAA6E0000u, 56u}, // otk -> Orkh
- {0xB32E0000u, 40u}, // ozm -> Latn
- {0x70610000u, 23u}, // pa -> Guru
+ {0xAA6E0000u, 61u}, // otk -> Orkh
+ {0xB32E0000u, 44u}, // ozm -> Latn
+ {0x70610000u, 26u}, // pa -> Guru
{0x7061504Bu, 1u}, // pa-PK -> Arab
- {0x980F0000u, 40u}, // pag -> Latn
- {0xAC0F0000u, 60u}, // pal -> Phli
- {0xB00F0000u, 40u}, // pam -> Latn
- {0xBC0F0000u, 40u}, // pap -> Latn
- {0xD00F0000u, 40u}, // pau -> Latn
- {0xA02F0000u, 40u}, // pbi -> Latn
- {0x8C4F0000u, 40u}, // pcd -> Latn
- {0xB04F0000u, 40u}, // pcm -> Latn
- {0x886F0000u, 40u}, // pdc -> Latn
- {0xCC6F0000u, 40u}, // pdt -> Latn
- {0x8C8F0000u, 40u}, // ped -> Latn
- {0xB88F0000u, 84u}, // peo -> Xpeo
- {0xDC8F0000u, 40u}, // pex -> Latn
- {0xACAF0000u, 40u}, // pfl -> Latn
+ {0x980F0000u, 44u}, // pag -> Latn
+ {0xAC0F0000u, 65u}, // pal -> Phli
+ {0xB00F0000u, 44u}, // pam -> Latn
+ {0xBC0F0000u, 44u}, // pap -> Latn
+ {0xD00F0000u, 44u}, // pau -> Latn
+ {0xA02F0000u, 44u}, // pbi -> Latn
+ {0x8C4F0000u, 44u}, // pcd -> Latn
+ {0xB04F0000u, 44u}, // pcm -> Latn
+ {0x886F0000u, 44u}, // pdc -> Latn
+ {0xCC6F0000u, 44u}, // pdt -> Latn
+ {0x8C8F0000u, 44u}, // ped -> Latn
+ {0xB88F0000u, 92u}, // peo -> Xpeo
+ {0xDC8F0000u, 44u}, // pex -> Latn
+ {0xACAF0000u, 44u}, // pfl -> Latn
{0xACEF0000u, 1u}, // phl -> Arab
- {0xB4EF0000u, 61u}, // phn -> Phnx
- {0xAD0F0000u, 40u}, // pil -> Latn
- {0xBD0F0000u, 40u}, // pip -> Latn
+ {0xB4EF0000u, 66u}, // phn -> Phnx
+ {0xAD0F0000u, 44u}, // pil -> Latn
+ {0xBD0F0000u, 44u}, // pip -> Latn
{0x814F0000u, 8u}, // pka -> Brah
- {0xB94F0000u, 40u}, // pko -> Latn
- {0x706C0000u, 40u}, // pl -> Latn
- {0x816F0000u, 40u}, // pla -> Latn
- {0xC98F0000u, 40u}, // pms -> Latn
- {0x99AF0000u, 40u}, // png -> Latn
- {0xB5AF0000u, 40u}, // pnn -> Latn
- {0xCDAF0000u, 21u}, // pnt -> Grek
- {0xB5CF0000u, 40u}, // pon -> Latn
- {0xB9EF0000u, 40u}, // ppo -> Latn
- {0x822F0000u, 34u}, // pra -> Khar
+ {0xB94F0000u, 44u}, // pko -> Latn
+ {0x706C0000u, 44u}, // pl -> Latn
+ {0x816F0000u, 44u}, // pla -> Latn
+ {0xC98F0000u, 44u}, // pms -> Latn
+ {0x99AF0000u, 44u}, // png -> Latn
+ {0xB5AF0000u, 44u}, // pnn -> Latn
+ {0xCDAF0000u, 24u}, // pnt -> Grek
+ {0xB5CF0000u, 44u}, // pon -> Latn
+ {0xB9EF0000u, 44u}, // ppo -> Latn
+ {0x822F0000u, 38u}, // pra -> Khar
{0x8E2F0000u, 1u}, // prd -> Arab
- {0x9A2F0000u, 40u}, // prg -> Latn
+ {0x9A2F0000u, 44u}, // prg -> Latn
{0x70730000u, 1u}, // ps -> Arab
- {0xCA4F0000u, 40u}, // pss -> Latn
- {0x70740000u, 40u}, // pt -> Latn
- {0xBE6F0000u, 40u}, // ptp -> Latn
- {0xD28F0000u, 40u}, // puu -> Latn
- {0x82CF0000u, 40u}, // pwa -> Latn
- {0x71750000u, 40u}, // qu -> Latn
- {0x8A900000u, 40u}, // quc -> Latn
- {0x9A900000u, 40u}, // qug -> Latn
- {0xA0110000u, 40u}, // rai -> Latn
- {0xA4110000u, 16u}, // raj -> Deva
- {0xB8110000u, 40u}, // rao -> Latn
- {0x94510000u, 40u}, // rcf -> Latn
- {0xA4910000u, 40u}, // rej -> Latn
- {0xAC910000u, 40u}, // rel -> Latn
- {0xC8910000u, 40u}, // res -> Latn
- {0xB4D10000u, 40u}, // rgn -> Latn
+ {0xCA4F0000u, 44u}, // pss -> Latn
+ {0x70740000u, 44u}, // pt -> Latn
+ {0xBE6F0000u, 44u}, // ptp -> Latn
+ {0xD28F0000u, 44u}, // puu -> Latn
+ {0x82CF0000u, 44u}, // pwa -> Latn
+ {0x71750000u, 44u}, // qu -> Latn
+ {0x8A900000u, 44u}, // quc -> Latn
+ {0x9A900000u, 44u}, // qug -> Latn
+ {0xA0110000u, 44u}, // rai -> Latn
+ {0xA4110000u, 17u}, // raj -> Deva
+ {0xB8110000u, 44u}, // rao -> Latn
+ {0x94510000u, 44u}, // rcf -> Latn
+ {0xA4910000u, 44u}, // rej -> Latn
+ {0xAC910000u, 44u}, // rel -> Latn
+ {0xC8910000u, 44u}, // res -> Latn
+ {0xB4D10000u, 44u}, // rgn -> Latn
{0x98F10000u, 1u}, // rhg -> Arab
- {0x81110000u, 40u}, // ria -> Latn
- {0x95110000u, 78u}, // rif -> Tfng
- {0x95114E4Cu, 40u}, // rif-NL -> Latn
- {0xC9310000u, 16u}, // rjs -> Deva
+ {0x81110000u, 44u}, // ria -> Latn
+ {0x95110000u, 85u}, // rif -> Tfng
+ {0x95114E4Cu, 44u}, // rif-NL -> Latn
+ {0xC9310000u, 17u}, // rjs -> Deva
{0xCD510000u, 7u}, // rkt -> Beng
- {0x726D0000u, 40u}, // rm -> Latn
- {0x95910000u, 40u}, // rmf -> Latn
- {0xB9910000u, 40u}, // rmo -> Latn
+ {0x726D0000u, 44u}, // rm -> Latn
+ {0x95910000u, 44u}, // rmf -> Latn
+ {0xB9910000u, 44u}, // rmo -> Latn
{0xCD910000u, 1u}, // rmt -> Arab
- {0xD1910000u, 40u}, // rmu -> Latn
- {0x726E0000u, 40u}, // rn -> Latn
- {0x81B10000u, 40u}, // rna -> Latn
- {0x99B10000u, 40u}, // rng -> Latn
- {0x726F0000u, 40u}, // ro -> Latn
- {0x85D10000u, 40u}, // rob -> Latn
- {0x95D10000u, 40u}, // rof -> Latn
- {0xB9D10000u, 40u}, // roo -> Latn
- {0xBA310000u, 40u}, // rro -> Latn
- {0xB2710000u, 40u}, // rtm -> Latn
- {0x72750000u, 15u}, // ru -> Cyrl
- {0x92910000u, 15u}, // rue -> Cyrl
- {0x9A910000u, 40u}, // rug -> Latn
- {0x72770000u, 40u}, // rw -> Latn
- {0xAAD10000u, 40u}, // rwk -> Latn
- {0xBAD10000u, 40u}, // rwo -> Latn
- {0xD3110000u, 33u}, // ryu -> Kana
- {0x73610000u, 16u}, // sa -> Deva
- {0x94120000u, 40u}, // saf -> Latn
- {0x9C120000u, 15u}, // sah -> Cyrl
- {0xC0120000u, 40u}, // saq -> Latn
- {0xC8120000u, 40u}, // sas -> Latn
- {0xCC120000u, 40u}, // sat -> Latn
- {0xE4120000u, 67u}, // saz -> Saur
- {0x80320000u, 40u}, // sba -> Latn
- {0x90320000u, 40u}, // sbe -> Latn
- {0xBC320000u, 40u}, // sbp -> Latn
- {0x73630000u, 40u}, // sc -> Latn
- {0xA8520000u, 16u}, // sck -> Deva
+ {0xD1910000u, 44u}, // rmu -> Latn
+ {0x726E0000u, 44u}, // rn -> Latn
+ {0x81B10000u, 44u}, // rna -> Latn
+ {0x99B10000u, 44u}, // rng -> Latn
+ {0x726F0000u, 44u}, // ro -> Latn
+ {0x85D10000u, 44u}, // rob -> Latn
+ {0x95D10000u, 44u}, // rof -> Latn
+ {0xB9D10000u, 44u}, // roo -> Latn
+ {0xBA310000u, 44u}, // rro -> Latn
+ {0xB2710000u, 44u}, // rtm -> Latn
+ {0x72750000u, 16u}, // ru -> Cyrl
+ {0x92910000u, 16u}, // rue -> Cyrl
+ {0x9A910000u, 44u}, // rug -> Latn
+ {0x72770000u, 44u}, // rw -> Latn
+ {0xAAD10000u, 44u}, // rwk -> Latn
+ {0xBAD10000u, 44u}, // rwo -> Latn
+ {0xD3110000u, 37u}, // ryu -> Kana
+ {0x73610000u, 17u}, // sa -> Deva
+ {0x94120000u, 44u}, // saf -> Latn
+ {0x9C120000u, 16u}, // sah -> Cyrl
+ {0xC0120000u, 44u}, // saq -> Latn
+ {0xC8120000u, 44u}, // sas -> Latn
+ {0xCC120000u, 44u}, // sat -> Latn
+ {0xD4120000u, 44u}, // sav -> Latn
+ {0xE4120000u, 72u}, // saz -> Saur
+ {0x80320000u, 44u}, // sba -> Latn
+ {0x90320000u, 44u}, // sbe -> Latn
+ {0xBC320000u, 44u}, // sbp -> Latn
+ {0x73630000u, 44u}, // sc -> Latn
+ {0xA8520000u, 17u}, // sck -> Deva
{0xAC520000u, 1u}, // scl -> Arab
- {0xB4520000u, 40u}, // scn -> Latn
- {0xB8520000u, 40u}, // sco -> Latn
- {0xC8520000u, 40u}, // scs -> Latn
+ {0xB4520000u, 44u}, // scn -> Latn
+ {0xB8520000u, 44u}, // sco -> Latn
+ {0xC8520000u, 44u}, // scs -> Latn
{0x73640000u, 1u}, // sd -> Arab
- {0x88720000u, 40u}, // sdc -> Latn
+ {0x88720000u, 44u}, // sdc -> Latn
{0x9C720000u, 1u}, // sdh -> Arab
- {0x73650000u, 40u}, // se -> Latn
- {0x94920000u, 40u}, // sef -> Latn
- {0x9C920000u, 40u}, // seh -> Latn
- {0xA0920000u, 40u}, // sei -> Latn
- {0xC8920000u, 40u}, // ses -> Latn
- {0x73670000u, 40u}, // sg -> Latn
- {0x80D20000u, 55u}, // sga -> Ogam
- {0xC8D20000u, 40u}, // sgs -> Latn
- {0xD8D20000u, 18u}, // sgw -> Ethi
- {0xE4D20000u, 40u}, // sgz -> Latn
- {0x73680000u, 40u}, // sh -> Latn
- {0xA0F20000u, 78u}, // shi -> Tfng
- {0xA8F20000u, 40u}, // shk -> Latn
- {0xB4F20000u, 52u}, // shn -> Mymr
+ {0x73650000u, 44u}, // se -> Latn
+ {0x94920000u, 44u}, // sef -> Latn
+ {0x9C920000u, 44u}, // seh -> Latn
+ {0xA0920000u, 44u}, // sei -> Latn
+ {0xC8920000u, 44u}, // ses -> Latn
+ {0x73670000u, 44u}, // sg -> Latn
+ {0x80D20000u, 60u}, // sga -> Ogam
+ {0xC8D20000u, 44u}, // sgs -> Latn
+ {0xD8D20000u, 19u}, // sgw -> Ethi
+ {0xE4D20000u, 44u}, // sgz -> Latn
+ {0x73680000u, 44u}, // sh -> Latn
+ {0xA0F20000u, 85u}, // shi -> Tfng
+ {0xA8F20000u, 44u}, // shk -> Latn
+ {0xB4F20000u, 56u}, // shn -> Mymr
{0xD0F20000u, 1u}, // shu -> Arab
- {0x73690000u, 69u}, // si -> Sinh
- {0x8D120000u, 40u}, // sid -> Latn
- {0x99120000u, 40u}, // sig -> Latn
- {0xAD120000u, 40u}, // sil -> Latn
- {0xB1120000u, 40u}, // sim -> Latn
- {0xC5320000u, 40u}, // sjr -> Latn
- {0x736B0000u, 40u}, // sk -> Latn
- {0x89520000u, 40u}, // skc -> Latn
+ {0x73690000u, 74u}, // si -> Sinh
+ {0x8D120000u, 44u}, // sid -> Latn
+ {0x99120000u, 44u}, // sig -> Latn
+ {0xAD120000u, 44u}, // sil -> Latn
+ {0xB1120000u, 44u}, // sim -> Latn
+ {0xC5320000u, 44u}, // sjr -> Latn
+ {0x736B0000u, 44u}, // sk -> Latn
+ {0x89520000u, 44u}, // skc -> Latn
{0xC5520000u, 1u}, // skr -> Arab
- {0xC9520000u, 40u}, // sks -> Latn
- {0x736C0000u, 40u}, // sl -> Latn
- {0x8D720000u, 40u}, // sld -> Latn
- {0xA1720000u, 40u}, // sli -> Latn
- {0xAD720000u, 40u}, // sll -> Latn
- {0xE1720000u, 40u}, // sly -> Latn
- {0x736D0000u, 40u}, // sm -> Latn
- {0x81920000u, 40u}, // sma -> Latn
- {0xA5920000u, 40u}, // smj -> Latn
- {0xB5920000u, 40u}, // smn -> Latn
- {0xBD920000u, 65u}, // smp -> Samr
- {0xC1920000u, 40u}, // smq -> Latn
- {0xC9920000u, 40u}, // sms -> Latn
- {0x736E0000u, 40u}, // sn -> Latn
- {0x89B20000u, 40u}, // snc -> Latn
- {0xA9B20000u, 40u}, // snk -> Latn
- {0xBDB20000u, 40u}, // snp -> Latn
- {0xDDB20000u, 40u}, // snx -> Latn
- {0xE1B20000u, 40u}, // sny -> Latn
- {0x736F0000u, 40u}, // so -> Latn
- {0xA9D20000u, 40u}, // sok -> Latn
- {0xC1D20000u, 40u}, // soq -> Latn
- {0xD1D20000u, 80u}, // sou -> Thai
- {0xE1D20000u, 40u}, // soy -> Latn
- {0x8DF20000u, 40u}, // spd -> Latn
- {0xADF20000u, 40u}, // spl -> Latn
- {0xC9F20000u, 40u}, // sps -> Latn
- {0x73710000u, 40u}, // sq -> Latn
- {0x73720000u, 15u}, // sr -> Cyrl
- {0x73724D45u, 40u}, // sr-ME -> Latn
- {0x7372524Fu, 40u}, // sr-RO -> Latn
- {0x73725255u, 40u}, // sr-RU -> Latn
- {0x73725452u, 40u}, // sr-TR -> Latn
- {0x86320000u, 70u}, // srb -> Sora
- {0xB6320000u, 40u}, // srn -> Latn
- {0xC6320000u, 40u}, // srr -> Latn
- {0xDE320000u, 16u}, // srx -> Deva
- {0x73730000u, 40u}, // ss -> Latn
- {0x8E520000u, 40u}, // ssd -> Latn
- {0x9A520000u, 40u}, // ssg -> Latn
- {0xE2520000u, 40u}, // ssy -> Latn
- {0x73740000u, 40u}, // st -> Latn
- {0xAA720000u, 40u}, // stk -> Latn
- {0xC2720000u, 40u}, // stq -> Latn
- {0x73750000u, 40u}, // su -> Latn
- {0x82920000u, 40u}, // sua -> Latn
- {0x92920000u, 40u}, // sue -> Latn
- {0xAA920000u, 40u}, // suk -> Latn
- {0xC6920000u, 40u}, // sur -> Latn
- {0xCA920000u, 40u}, // sus -> Latn
- {0x73760000u, 40u}, // sv -> Latn
- {0x73770000u, 40u}, // sw -> Latn
+ {0xC9520000u, 44u}, // sks -> Latn
+ {0x736C0000u, 44u}, // sl -> Latn
+ {0x8D720000u, 44u}, // sld -> Latn
+ {0xA1720000u, 44u}, // sli -> Latn
+ {0xAD720000u, 44u}, // sll -> Latn
+ {0xE1720000u, 44u}, // sly -> Latn
+ {0x736D0000u, 44u}, // sm -> Latn
+ {0x81920000u, 44u}, // sma -> Latn
+ {0xA5920000u, 44u}, // smj -> Latn
+ {0xB5920000u, 44u}, // smn -> Latn
+ {0xBD920000u, 70u}, // smp -> Samr
+ {0xC1920000u, 44u}, // smq -> Latn
+ {0xC9920000u, 44u}, // sms -> Latn
+ {0x736E0000u, 44u}, // sn -> Latn
+ {0x89B20000u, 44u}, // snc -> Latn
+ {0xA9B20000u, 44u}, // snk -> Latn
+ {0xBDB20000u, 44u}, // snp -> Latn
+ {0xDDB20000u, 44u}, // snx -> Latn
+ {0xE1B20000u, 44u}, // sny -> Latn
+ {0x736F0000u, 44u}, // so -> Latn
+ {0x99D20000u, 75u}, // sog -> Sogd
+ {0xA9D20000u, 44u}, // sok -> Latn
+ {0xC1D20000u, 44u}, // soq -> Latn
+ {0xD1D20000u, 87u}, // sou -> Thai
+ {0xE1D20000u, 44u}, // soy -> Latn
+ {0x8DF20000u, 44u}, // spd -> Latn
+ {0xADF20000u, 44u}, // spl -> Latn
+ {0xC9F20000u, 44u}, // sps -> Latn
+ {0x73710000u, 44u}, // sq -> Latn
+ {0x73720000u, 16u}, // sr -> Cyrl
+ {0x73724D45u, 44u}, // sr-ME -> Latn
+ {0x7372524Fu, 44u}, // sr-RO -> Latn
+ {0x73725255u, 44u}, // sr-RU -> Latn
+ {0x73725452u, 44u}, // sr-TR -> Latn
+ {0x86320000u, 76u}, // srb -> Sora
+ {0xB6320000u, 44u}, // srn -> Latn
+ {0xC6320000u, 44u}, // srr -> Latn
+ {0xDE320000u, 17u}, // srx -> Deva
+ {0x73730000u, 44u}, // ss -> Latn
+ {0x8E520000u, 44u}, // ssd -> Latn
+ {0x9A520000u, 44u}, // ssg -> Latn
+ {0xE2520000u, 44u}, // ssy -> Latn
+ {0x73740000u, 44u}, // st -> Latn
+ {0xAA720000u, 44u}, // stk -> Latn
+ {0xC2720000u, 44u}, // stq -> Latn
+ {0x73750000u, 44u}, // su -> Latn
+ {0x82920000u, 44u}, // sua -> Latn
+ {0x92920000u, 44u}, // sue -> Latn
+ {0xAA920000u, 44u}, // suk -> Latn
+ {0xC6920000u, 44u}, // sur -> Latn
+ {0xCA920000u, 44u}, // sus -> Latn
+ {0x73760000u, 44u}, // sv -> Latn
+ {0x73770000u, 44u}, // sw -> Latn
{0x86D20000u, 1u}, // swb -> Arab
- {0x8AD20000u, 40u}, // swc -> Latn
- {0x9AD20000u, 40u}, // swg -> Latn
- {0xBED20000u, 40u}, // swp -> Latn
- {0xD6D20000u, 16u}, // swv -> Deva
- {0xB6F20000u, 40u}, // sxn -> Latn
- {0xDAF20000u, 40u}, // sxw -> Latn
+ {0x8AD20000u, 44u}, // swc -> Latn
+ {0x9AD20000u, 44u}, // swg -> Latn
+ {0xBED20000u, 44u}, // swp -> Latn
+ {0xD6D20000u, 17u}, // swv -> Deva
+ {0xB6F20000u, 44u}, // sxn -> Latn
+ {0xDAF20000u, 44u}, // sxw -> Latn
{0xAF120000u, 7u}, // syl -> Beng
- {0xC7120000u, 71u}, // syr -> Syrc
- {0xAF320000u, 40u}, // szl -> Latn
- {0x74610000u, 74u}, // ta -> Taml
- {0xA4130000u, 16u}, // taj -> Deva
- {0xAC130000u, 40u}, // tal -> Latn
- {0xB4130000u, 40u}, // tan -> Latn
- {0xC0130000u, 40u}, // taq -> Latn
- {0x88330000u, 40u}, // tbc -> Latn
- {0x8C330000u, 40u}, // tbd -> Latn
- {0x94330000u, 40u}, // tbf -> Latn
- {0x98330000u, 40u}, // tbg -> Latn
- {0xB8330000u, 40u}, // tbo -> Latn
- {0xD8330000u, 40u}, // tbw -> Latn
- {0xE4330000u, 40u}, // tbz -> Latn
- {0xA0530000u, 40u}, // tci -> Latn
- {0xE0530000u, 36u}, // tcy -> Knda
- {0x8C730000u, 72u}, // tdd -> Tale
- {0x98730000u, 16u}, // tdg -> Deva
- {0x9C730000u, 16u}, // tdh -> Deva
- {0x74650000u, 77u}, // te -> Telu
- {0x8C930000u, 40u}, // ted -> Latn
- {0xB0930000u, 40u}, // tem -> Latn
- {0xB8930000u, 40u}, // teo -> Latn
- {0xCC930000u, 40u}, // tet -> Latn
- {0xA0B30000u, 40u}, // tfi -> Latn
- {0x74670000u, 15u}, // tg -> Cyrl
+ {0xC7120000u, 78u}, // syr -> Syrc
+ {0xAF320000u, 44u}, // szl -> Latn
+ {0x74610000u, 81u}, // ta -> Taml
+ {0xA4130000u, 17u}, // taj -> Deva
+ {0xAC130000u, 44u}, // tal -> Latn
+ {0xB4130000u, 44u}, // tan -> Latn
+ {0xC0130000u, 44u}, // taq -> Latn
+ {0x88330000u, 44u}, // tbc -> Latn
+ {0x8C330000u, 44u}, // tbd -> Latn
+ {0x94330000u, 44u}, // tbf -> Latn
+ {0x98330000u, 44u}, // tbg -> Latn
+ {0xB8330000u, 44u}, // tbo -> Latn
+ {0xD8330000u, 44u}, // tbw -> Latn
+ {0xE4330000u, 44u}, // tbz -> Latn
+ {0xA0530000u, 44u}, // tci -> Latn
+ {0xE0530000u, 40u}, // tcy -> Knda
+ {0x8C730000u, 79u}, // tdd -> Tale
+ {0x98730000u, 17u}, // tdg -> Deva
+ {0x9C730000u, 17u}, // tdh -> Deva
+ {0x74650000u, 84u}, // te -> Telu
+ {0x8C930000u, 44u}, // ted -> Latn
+ {0xB0930000u, 44u}, // tem -> Latn
+ {0xB8930000u, 44u}, // teo -> Latn
+ {0xCC930000u, 44u}, // tet -> Latn
+ {0xA0B30000u, 44u}, // tfi -> Latn
+ {0x74670000u, 16u}, // tg -> Cyrl
{0x7467504Bu, 1u}, // tg-PK -> Arab
- {0x88D30000u, 40u}, // tgc -> Latn
- {0xB8D30000u, 40u}, // tgo -> Latn
- {0xD0D30000u, 40u}, // tgu -> Latn
- {0x74680000u, 80u}, // th -> Thai
- {0xACF30000u, 16u}, // thl -> Deva
- {0xC0F30000u, 16u}, // thq -> Deva
- {0xC4F30000u, 16u}, // thr -> Deva
- {0x74690000u, 18u}, // ti -> Ethi
- {0x95130000u, 40u}, // tif -> Latn
- {0x99130000u, 18u}, // tig -> Ethi
- {0xA9130000u, 40u}, // tik -> Latn
- {0xB1130000u, 40u}, // tim -> Latn
- {0xB9130000u, 40u}, // tio -> Latn
- {0xD5130000u, 40u}, // tiv -> Latn
- {0x746B0000u, 40u}, // tk -> Latn
- {0xAD530000u, 40u}, // tkl -> Latn
- {0xC5530000u, 40u}, // tkr -> Latn
- {0xCD530000u, 16u}, // tkt -> Deva
- {0x746C0000u, 40u}, // tl -> Latn
- {0x95730000u, 40u}, // tlf -> Latn
- {0xDD730000u, 40u}, // tlx -> Latn
- {0xE1730000u, 40u}, // tly -> Latn
- {0x9D930000u, 40u}, // tmh -> Latn
- {0xE1930000u, 40u}, // tmy -> Latn
- {0x746E0000u, 40u}, // tn -> Latn
- {0x9DB30000u, 40u}, // tnh -> Latn
- {0x746F0000u, 40u}, // to -> Latn
- {0x95D30000u, 40u}, // tof -> Latn
- {0x99D30000u, 40u}, // tog -> Latn
- {0xC1D30000u, 40u}, // toq -> Latn
- {0xA1F30000u, 40u}, // tpi -> Latn
- {0xB1F30000u, 40u}, // tpm -> Latn
- {0xE5F30000u, 40u}, // tpz -> Latn
- {0xBA130000u, 40u}, // tqo -> Latn
- {0x74720000u, 40u}, // tr -> Latn
- {0xD2330000u, 40u}, // tru -> Latn
- {0xD6330000u, 40u}, // trv -> Latn
+ {0x88D30000u, 44u}, // tgc -> Latn
+ {0xB8D30000u, 44u}, // tgo -> Latn
+ {0xD0D30000u, 44u}, // tgu -> Latn
+ {0x74680000u, 87u}, // th -> Thai
+ {0xACF30000u, 17u}, // thl -> Deva
+ {0xC0F30000u, 17u}, // thq -> Deva
+ {0xC4F30000u, 17u}, // thr -> Deva
+ {0x74690000u, 19u}, // ti -> Ethi
+ {0x95130000u, 44u}, // tif -> Latn
+ {0x99130000u, 19u}, // tig -> Ethi
+ {0xA9130000u, 44u}, // tik -> Latn
+ {0xB1130000u, 44u}, // tim -> Latn
+ {0xB9130000u, 44u}, // tio -> Latn
+ {0xD5130000u, 44u}, // tiv -> Latn
+ {0x746B0000u, 44u}, // tk -> Latn
+ {0xAD530000u, 44u}, // tkl -> Latn
+ {0xC5530000u, 44u}, // tkr -> Latn
+ {0xCD530000u, 17u}, // tkt -> Deva
+ {0x746C0000u, 44u}, // tl -> Latn
+ {0x95730000u, 44u}, // tlf -> Latn
+ {0xDD730000u, 44u}, // tlx -> Latn
+ {0xE1730000u, 44u}, // tly -> Latn
+ {0x9D930000u, 44u}, // tmh -> Latn
+ {0xE1930000u, 44u}, // tmy -> Latn
+ {0x746E0000u, 44u}, // tn -> Latn
+ {0x9DB30000u, 44u}, // tnh -> Latn
+ {0x746F0000u, 44u}, // to -> Latn
+ {0x95D30000u, 44u}, // tof -> Latn
+ {0x99D30000u, 44u}, // tog -> Latn
+ {0xC1D30000u, 44u}, // toq -> Latn
+ {0xA1F30000u, 44u}, // tpi -> Latn
+ {0xB1F30000u, 44u}, // tpm -> Latn
+ {0xE5F30000u, 44u}, // tpz -> Latn
+ {0xBA130000u, 44u}, // tqo -> Latn
+ {0x74720000u, 44u}, // tr -> Latn
+ {0xD2330000u, 44u}, // tru -> Latn
+ {0xD6330000u, 44u}, // trv -> Latn
{0xDA330000u, 1u}, // trw -> Arab
- {0x74730000u, 40u}, // ts -> Latn
- {0x8E530000u, 21u}, // tsd -> Grek
- {0x96530000u, 16u}, // tsf -> Deva
- {0x9A530000u, 40u}, // tsg -> Latn
- {0xA6530000u, 81u}, // tsj -> Tibt
- {0xDA530000u, 40u}, // tsw -> Latn
- {0x74740000u, 15u}, // tt -> Cyrl
- {0x8E730000u, 40u}, // ttd -> Latn
- {0x92730000u, 40u}, // tte -> Latn
- {0xA6730000u, 40u}, // ttj -> Latn
- {0xC6730000u, 40u}, // ttr -> Latn
- {0xCA730000u, 80u}, // tts -> Thai
- {0xCE730000u, 40u}, // ttt -> Latn
- {0x9E930000u, 40u}, // tuh -> Latn
- {0xAE930000u, 40u}, // tul -> Latn
- {0xB2930000u, 40u}, // tum -> Latn
- {0xC2930000u, 40u}, // tuq -> Latn
- {0x8EB30000u, 40u}, // tvd -> Latn
- {0xAEB30000u, 40u}, // tvl -> Latn
- {0xD2B30000u, 40u}, // tvu -> Latn
- {0x9ED30000u, 40u}, // twh -> Latn
- {0xC2D30000u, 40u}, // twq -> Latn
- {0x9AF30000u, 75u}, // txg -> Tang
- {0x74790000u, 40u}, // ty -> Latn
- {0x83130000u, 40u}, // tya -> Latn
- {0xD7130000u, 15u}, // tyv -> Cyrl
- {0xB3330000u, 40u}, // tzm -> Latn
- {0xD0340000u, 40u}, // ubu -> Latn
- {0xB0740000u, 15u}, // udm -> Cyrl
+ {0x74730000u, 44u}, // ts -> Latn
+ {0x8E530000u, 24u}, // tsd -> Grek
+ {0x96530000u, 17u}, // tsf -> Deva
+ {0x9A530000u, 44u}, // tsg -> Latn
+ {0xA6530000u, 88u}, // tsj -> Tibt
+ {0xDA530000u, 44u}, // tsw -> Latn
+ {0x74740000u, 16u}, // tt -> Cyrl
+ {0x8E730000u, 44u}, // ttd -> Latn
+ {0x92730000u, 44u}, // tte -> Latn
+ {0xA6730000u, 44u}, // ttj -> Latn
+ {0xC6730000u, 44u}, // ttr -> Latn
+ {0xCA730000u, 87u}, // tts -> Thai
+ {0xCE730000u, 44u}, // ttt -> Latn
+ {0x9E930000u, 44u}, // tuh -> Latn
+ {0xAE930000u, 44u}, // tul -> Latn
+ {0xB2930000u, 44u}, // tum -> Latn
+ {0xC2930000u, 44u}, // tuq -> Latn
+ {0x8EB30000u, 44u}, // tvd -> Latn
+ {0xAEB30000u, 44u}, // tvl -> Latn
+ {0xD2B30000u, 44u}, // tvu -> Latn
+ {0x9ED30000u, 44u}, // twh -> Latn
+ {0xC2D30000u, 44u}, // twq -> Latn
+ {0x9AF30000u, 82u}, // txg -> Tang
+ {0x74790000u, 44u}, // ty -> Latn
+ {0x83130000u, 44u}, // tya -> Latn
+ {0xD7130000u, 16u}, // tyv -> Cyrl
+ {0xB3330000u, 44u}, // tzm -> Latn
+ {0xD0340000u, 44u}, // ubu -> Latn
+ {0xB0740000u, 16u}, // udm -> Cyrl
{0x75670000u, 1u}, // ug -> Arab
- {0x75674B5Au, 15u}, // ug-KZ -> Cyrl
- {0x75674D4Eu, 15u}, // ug-MN -> Cyrl
- {0x80D40000u, 82u}, // uga -> Ugar
- {0x756B0000u, 15u}, // uk -> Cyrl
- {0xA1740000u, 40u}, // uli -> Latn
- {0x85940000u, 40u}, // umb -> Latn
+ {0x75674B5Au, 16u}, // ug-KZ -> Cyrl
+ {0x75674D4Eu, 16u}, // ug-MN -> Cyrl
+ {0x80D40000u, 89u}, // uga -> Ugar
+ {0x756B0000u, 16u}, // uk -> Cyrl
+ {0xA1740000u, 44u}, // uli -> Latn
+ {0x85940000u, 44u}, // umb -> Latn
{0xC5B40000u, 7u}, // unr -> Beng
- {0xC5B44E50u, 16u}, // unr-NP -> Deva
+ {0xC5B44E50u, 17u}, // unr-NP -> Deva
{0xDDB40000u, 7u}, // unx -> Beng
{0x75720000u, 1u}, // ur -> Arab
- {0xA2340000u, 40u}, // uri -> Latn
- {0xCE340000u, 40u}, // urt -> Latn
- {0xDA340000u, 40u}, // urw -> Latn
- {0x82540000u, 40u}, // usa -> Latn
- {0xC6740000u, 40u}, // utr -> Latn
- {0x9EB40000u, 40u}, // uvh -> Latn
- {0xAEB40000u, 40u}, // uvl -> Latn
- {0x757A0000u, 40u}, // uz -> Latn
+ {0xA2340000u, 44u}, // uri -> Latn
+ {0xCE340000u, 44u}, // urt -> Latn
+ {0xDA340000u, 44u}, // urw -> Latn
+ {0x82540000u, 44u}, // usa -> Latn
+ {0xC6740000u, 44u}, // utr -> Latn
+ {0x9EB40000u, 44u}, // uvh -> Latn
+ {0xAEB40000u, 44u}, // uvl -> Latn
+ {0x757A0000u, 44u}, // uz -> Latn
{0x757A4146u, 1u}, // uz-AF -> Arab
- {0x757A434Eu, 15u}, // uz-CN -> Cyrl
- {0x98150000u, 40u}, // vag -> Latn
- {0xA0150000u, 83u}, // vai -> Vaii
- {0xB4150000u, 40u}, // van -> Latn
- {0x76650000u, 40u}, // ve -> Latn
- {0x88950000u, 40u}, // vec -> Latn
- {0xBC950000u, 40u}, // vep -> Latn
- {0x76690000u, 40u}, // vi -> Latn
- {0x89150000u, 40u}, // vic -> Latn
- {0xD5150000u, 40u}, // viv -> Latn
- {0xC9750000u, 40u}, // vls -> Latn
- {0x95950000u, 40u}, // vmf -> Latn
- {0xD9950000u, 40u}, // vmw -> Latn
- {0x766F0000u, 40u}, // vo -> Latn
- {0xCDD50000u, 40u}, // vot -> Latn
- {0xBA350000u, 40u}, // vro -> Latn
- {0xB6950000u, 40u}, // vun -> Latn
- {0xCE950000u, 40u}, // vut -> Latn
- {0x77610000u, 40u}, // wa -> Latn
- {0x90160000u, 40u}, // wae -> Latn
- {0xA4160000u, 40u}, // waj -> Latn
- {0xAC160000u, 18u}, // wal -> Ethi
- {0xB4160000u, 40u}, // wan -> Latn
- {0xC4160000u, 40u}, // war -> Latn
- {0xBC360000u, 40u}, // wbp -> Latn
- {0xC0360000u, 77u}, // wbq -> Telu
- {0xC4360000u, 16u}, // wbr -> Deva
- {0xA0560000u, 40u}, // wci -> Latn
- {0xC4960000u, 40u}, // wer -> Latn
- {0xA0D60000u, 40u}, // wgi -> Latn
- {0x98F60000u, 40u}, // whg -> Latn
- {0x85160000u, 40u}, // wib -> Latn
- {0xD1160000u, 40u}, // wiu -> Latn
- {0xD5160000u, 40u}, // wiv -> Latn
- {0x81360000u, 40u}, // wja -> Latn
- {0xA1360000u, 40u}, // wji -> Latn
- {0xC9760000u, 40u}, // wls -> Latn
- {0xB9960000u, 40u}, // wmo -> Latn
- {0x89B60000u, 40u}, // wnc -> Latn
+ {0x757A434Eu, 16u}, // uz-CN -> Cyrl
+ {0x98150000u, 44u}, // vag -> Latn
+ {0xA0150000u, 90u}, // vai -> Vaii
+ {0xB4150000u, 44u}, // van -> Latn
+ {0x76650000u, 44u}, // ve -> Latn
+ {0x88950000u, 44u}, // vec -> Latn
+ {0xBC950000u, 44u}, // vep -> Latn
+ {0x76690000u, 44u}, // vi -> Latn
+ {0x89150000u, 44u}, // vic -> Latn
+ {0xD5150000u, 44u}, // viv -> Latn
+ {0xC9750000u, 44u}, // vls -> Latn
+ {0x95950000u, 44u}, // vmf -> Latn
+ {0xD9950000u, 44u}, // vmw -> Latn
+ {0x766F0000u, 44u}, // vo -> Latn
+ {0xCDD50000u, 44u}, // vot -> Latn
+ {0xBA350000u, 44u}, // vro -> Latn
+ {0xB6950000u, 44u}, // vun -> Latn
+ {0xCE950000u, 44u}, // vut -> Latn
+ {0x77610000u, 44u}, // wa -> Latn
+ {0x90160000u, 44u}, // wae -> Latn
+ {0xA4160000u, 44u}, // waj -> Latn
+ {0xAC160000u, 19u}, // wal -> Ethi
+ {0xB4160000u, 44u}, // wan -> Latn
+ {0xC4160000u, 44u}, // war -> Latn
+ {0xBC360000u, 44u}, // wbp -> Latn
+ {0xC0360000u, 84u}, // wbq -> Telu
+ {0xC4360000u, 17u}, // wbr -> Deva
+ {0xA0560000u, 44u}, // wci -> Latn
+ {0xC4960000u, 44u}, // wer -> Latn
+ {0xA0D60000u, 44u}, // wgi -> Latn
+ {0x98F60000u, 44u}, // whg -> Latn
+ {0x85160000u, 44u}, // wib -> Latn
+ {0xD1160000u, 44u}, // wiu -> Latn
+ {0xD5160000u, 44u}, // wiv -> Latn
+ {0x81360000u, 44u}, // wja -> Latn
+ {0xA1360000u, 44u}, // wji -> Latn
+ {0xC9760000u, 44u}, // wls -> Latn
+ {0xB9960000u, 44u}, // wmo -> Latn
+ {0x89B60000u, 44u}, // wnc -> Latn
{0xA1B60000u, 1u}, // wni -> Arab
- {0xD1B60000u, 40u}, // wnu -> Latn
- {0x776F0000u, 40u}, // wo -> Latn
- {0x85D60000u, 40u}, // wob -> Latn
- {0xC9D60000u, 40u}, // wos -> Latn
- {0xCA360000u, 40u}, // wrs -> Latn
- {0xAA560000u, 40u}, // wsk -> Latn
- {0xB2760000u, 16u}, // wtm -> Deva
- {0xD2960000u, 24u}, // wuu -> Hans
- {0xD6960000u, 40u}, // wuv -> Latn
- {0x82D60000u, 40u}, // wwa -> Latn
- {0xD4170000u, 40u}, // xav -> Latn
- {0xA0370000u, 40u}, // xbi -> Latn
- {0xC4570000u, 10u}, // xcr -> Cari
- {0xC8970000u, 40u}, // xes -> Latn
- {0x78680000u, 40u}, // xh -> Latn
- {0x81770000u, 40u}, // xla -> Latn
- {0x89770000u, 44u}, // xlc -> Lyci
- {0x8D770000u, 45u}, // xld -> Lydi
- {0x95970000u, 19u}, // xmf -> Geor
- {0xB5970000u, 47u}, // xmn -> Mani
- {0xC5970000u, 48u}, // xmr -> Merc
- {0x81B70000u, 53u}, // xna -> Narb
- {0xC5B70000u, 16u}, // xnr -> Deva
- {0x99D70000u, 40u}, // xog -> Latn
- {0xB5D70000u, 40u}, // xon -> Latn
- {0xC5F70000u, 63u}, // xpr -> Prti
- {0x86370000u, 40u}, // xrb -> Latn
- {0x82570000u, 66u}, // xsa -> Sarb
- {0xA2570000u, 40u}, // xsi -> Latn
- {0xB2570000u, 40u}, // xsm -> Latn
- {0xC6570000u, 16u}, // xsr -> Deva
- {0x92D70000u, 40u}, // xwe -> Latn
- {0xB0180000u, 40u}, // yam -> Latn
- {0xB8180000u, 40u}, // yao -> Latn
- {0xBC180000u, 40u}, // yap -> Latn
- {0xC8180000u, 40u}, // yas -> Latn
- {0xCC180000u, 40u}, // yat -> Latn
- {0xD4180000u, 40u}, // yav -> Latn
- {0xE0180000u, 40u}, // yay -> Latn
- {0xE4180000u, 40u}, // yaz -> Latn
- {0x80380000u, 40u}, // yba -> Latn
- {0x84380000u, 40u}, // ybb -> Latn
- {0xE0380000u, 40u}, // yby -> Latn
- {0xC4980000u, 40u}, // yer -> Latn
- {0xC4D80000u, 40u}, // ygr -> Latn
- {0xD8D80000u, 40u}, // ygw -> Latn
- {0x79690000u, 27u}, // yi -> Hebr
- {0xB9580000u, 40u}, // yko -> Latn
- {0x91780000u, 40u}, // yle -> Latn
- {0x99780000u, 40u}, // ylg -> Latn
- {0xAD780000u, 40u}, // yll -> Latn
- {0xAD980000u, 40u}, // yml -> Latn
- {0x796F0000u, 40u}, // yo -> Latn
- {0xB5D80000u, 40u}, // yon -> Latn
- {0x86380000u, 40u}, // yrb -> Latn
- {0x92380000u, 40u}, // yre -> Latn
- {0xAE380000u, 40u}, // yrl -> Latn
- {0xCA580000u, 40u}, // yss -> Latn
- {0x82980000u, 40u}, // yua -> Latn
- {0x92980000u, 25u}, // yue -> Hant
- {0x9298434Eu, 24u}, // yue-CN -> Hans
- {0xA6980000u, 40u}, // yuj -> Latn
- {0xCE980000u, 40u}, // yut -> Latn
- {0xDA980000u, 40u}, // yuw -> Latn
- {0x7A610000u, 40u}, // za -> Latn
- {0x98190000u, 40u}, // zag -> Latn
+ {0xD1B60000u, 44u}, // wnu -> Latn
+ {0x776F0000u, 44u}, // wo -> Latn
+ {0x85D60000u, 44u}, // wob -> Latn
+ {0xC9D60000u, 44u}, // wos -> Latn
+ {0xCA360000u, 44u}, // wrs -> Latn
+ {0x9A560000u, 21u}, // wsg -> Gong
+ {0xAA560000u, 44u}, // wsk -> Latn
+ {0xB2760000u, 17u}, // wtm -> Deva
+ {0xD2960000u, 27u}, // wuu -> Hans
+ {0xD6960000u, 44u}, // wuv -> Latn
+ {0x82D60000u, 44u}, // wwa -> Latn
+ {0xD4170000u, 44u}, // xav -> Latn
+ {0xA0370000u, 44u}, // xbi -> Latn
+ {0xC4570000u, 11u}, // xcr -> Cari
+ {0xC8970000u, 44u}, // xes -> Latn
+ {0x78680000u, 44u}, // xh -> Latn
+ {0x81770000u, 44u}, // xla -> Latn
+ {0x89770000u, 48u}, // xlc -> Lyci
+ {0x8D770000u, 49u}, // xld -> Lydi
+ {0x95970000u, 20u}, // xmf -> Geor
+ {0xB5970000u, 51u}, // xmn -> Mani
+ {0xC5970000u, 52u}, // xmr -> Merc
+ {0x81B70000u, 57u}, // xna -> Narb
+ {0xC5B70000u, 17u}, // xnr -> Deva
+ {0x99D70000u, 44u}, // xog -> Latn
+ {0xB5D70000u, 44u}, // xon -> Latn
+ {0xC5F70000u, 68u}, // xpr -> Prti
+ {0x86370000u, 44u}, // xrb -> Latn
+ {0x82570000u, 71u}, // xsa -> Sarb
+ {0xA2570000u, 44u}, // xsi -> Latn
+ {0xB2570000u, 44u}, // xsm -> Latn
+ {0xC6570000u, 17u}, // xsr -> Deva
+ {0x92D70000u, 44u}, // xwe -> Latn
+ {0xB0180000u, 44u}, // yam -> Latn
+ {0xB8180000u, 44u}, // yao -> Latn
+ {0xBC180000u, 44u}, // yap -> Latn
+ {0xC8180000u, 44u}, // yas -> Latn
+ {0xCC180000u, 44u}, // yat -> Latn
+ {0xD4180000u, 44u}, // yav -> Latn
+ {0xE0180000u, 44u}, // yay -> Latn
+ {0xE4180000u, 44u}, // yaz -> Latn
+ {0x80380000u, 44u}, // yba -> Latn
+ {0x84380000u, 44u}, // ybb -> Latn
+ {0xE0380000u, 44u}, // yby -> Latn
+ {0xC4980000u, 44u}, // yer -> Latn
+ {0xC4D80000u, 44u}, // ygr -> Latn
+ {0xD8D80000u, 44u}, // ygw -> Latn
+ {0x79690000u, 30u}, // yi -> Hebr
+ {0xB9580000u, 44u}, // yko -> Latn
+ {0x91780000u, 44u}, // yle -> Latn
+ {0x99780000u, 44u}, // ylg -> Latn
+ {0xAD780000u, 44u}, // yll -> Latn
+ {0xAD980000u, 44u}, // yml -> Latn
+ {0x796F0000u, 44u}, // yo -> Latn
+ {0xB5D80000u, 44u}, // yon -> Latn
+ {0x86380000u, 44u}, // yrb -> Latn
+ {0x92380000u, 44u}, // yre -> Latn
+ {0xAE380000u, 44u}, // yrl -> Latn
+ {0xCA580000u, 44u}, // yss -> Latn
+ {0x82980000u, 44u}, // yua -> Latn
+ {0x92980000u, 28u}, // yue -> Hant
+ {0x9298434Eu, 27u}, // yue-CN -> Hans
+ {0xA6980000u, 44u}, // yuj -> Latn
+ {0xCE980000u, 44u}, // yut -> Latn
+ {0xDA980000u, 44u}, // yuw -> Latn
+ {0x7A610000u, 44u}, // za -> Latn
+ {0x98190000u, 44u}, // zag -> Latn
{0xA4790000u, 1u}, // zdj -> Arab
- {0x80990000u, 40u}, // zea -> Latn
- {0x9CD90000u, 78u}, // zgh -> Tfng
- {0x7A680000u, 24u}, // zh -> Hans
- {0x7A684155u, 25u}, // zh-AU -> Hant
- {0x7A68424Eu, 25u}, // zh-BN -> Hant
- {0x7A684742u, 25u}, // zh-GB -> Hant
- {0x7A684746u, 25u}, // zh-GF -> Hant
- {0x7A68484Bu, 25u}, // zh-HK -> Hant
- {0x7A684944u, 25u}, // zh-ID -> Hant
- {0x7A684D4Fu, 25u}, // zh-MO -> Hant
- {0x7A684D59u, 25u}, // zh-MY -> Hant
- {0x7A685041u, 25u}, // zh-PA -> Hant
- {0x7A685046u, 25u}, // zh-PF -> Hant
- {0x7A685048u, 25u}, // zh-PH -> Hant
- {0x7A685352u, 25u}, // zh-SR -> Hant
- {0x7A685448u, 25u}, // zh-TH -> Hant
- {0x7A685457u, 25u}, // zh-TW -> Hant
- {0x7A685553u, 25u}, // zh-US -> Hant
- {0x7A68564Eu, 25u}, // zh-VN -> Hant
- {0x81190000u, 40u}, // zia -> Latn
- {0xB1790000u, 40u}, // zlm -> Latn
- {0xA1990000u, 40u}, // zmi -> Latn
- {0x91B90000u, 40u}, // zne -> Latn
- {0x7A750000u, 40u}, // zu -> Latn
- {0x83390000u, 40u}, // zza -> Latn
+ {0x80990000u, 44u}, // zea -> Latn
+ {0x9CD90000u, 85u}, // zgh -> Tfng
+ {0x7A680000u, 27u}, // zh -> Hans
+ {0x7A684155u, 28u}, // zh-AU -> Hant
+ {0x7A68424Eu, 28u}, // zh-BN -> Hant
+ {0x7A684742u, 28u}, // zh-GB -> Hant
+ {0x7A684746u, 28u}, // zh-GF -> Hant
+ {0x7A68484Bu, 28u}, // zh-HK -> Hant
+ {0x7A684944u, 28u}, // zh-ID -> Hant
+ {0x7A684D4Fu, 28u}, // zh-MO -> Hant
+ {0x7A684D59u, 28u}, // zh-MY -> Hant
+ {0x7A685041u, 28u}, // zh-PA -> Hant
+ {0x7A685046u, 28u}, // zh-PF -> Hant
+ {0x7A685048u, 28u}, // zh-PH -> Hant
+ {0x7A685352u, 28u}, // zh-SR -> Hant
+ {0x7A685448u, 28u}, // zh-TH -> Hant
+ {0x7A685457u, 28u}, // zh-TW -> Hant
+ {0x7A685553u, 28u}, // zh-US -> Hant
+ {0x7A68564Eu, 28u}, // zh-VN -> Hant
+ {0xDCF90000u, 59u}, // zhx -> Nshu
+ {0x81190000u, 44u}, // zia -> Latn
+ {0xB1790000u, 44u}, // zlm -> Latn
+ {0xA1990000u, 44u}, // zmi -> Latn
+ {0x91B90000u, 44u}, // zne -> Latn
+ {0x7A750000u, 44u}, // zu -> Latn
+ {0x83390000u, 44u}, // zza -> Latn
});
std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
@@ -1517,6 +1544,7 @@
0xB5014E474C61746ELLU, // bin_Latn_NG
0xA521494E44657661LLU, // bjj_Deva_IN
0xB52149444C61746ELLU, // bjn_Latn_ID
+ 0xCD21534E4C61746ELLU, // bjt_Latn_SN
0xB141434D4C61746ELLU, // bkm_Latn_CM
0xD14150484C61746ELLU, // bku_Latn_PH
0xCD61564E54617674LLU, // blt_Tavt_VN
@@ -1546,7 +1574,6 @@
0x93214D4C4C61746ELLU, // bze_Latn_ML
0x636145534C61746ELLU, // ca_Latn_ES
0x9C424E474C61746ELLU, // cch_Latn_NG
- 0xBC42494E42656E67LLU, // ccp_Beng_IN
0xBC42424443616B6DLLU, // ccp_Cakm_BD
0x636552554379726CLLU, // ce_Cyrl_RU
0x848250484C61746ELLU, // ceb_Latn_PH
@@ -1560,10 +1587,12 @@
0x81224B4841726162LLU, // cja_Arab_KH
0xB122564E4368616DLLU, // cjm_Cham_VN
0x8542495141726162LLU, // ckb_Arab_IQ
+ 0x99824D4E536F796FLLU, // cmg_Soyo_MN
0x636F46524C61746ELLU, // co_Latn_FR
0xBDC24547436F7074LLU, // cop_Copt_EG
0xC9E250484C61746ELLU, // cps_Latn_PH
0x6372434143616E73LLU, // cr_Cans_CA
+ 0x9E2255414379726CLLU, // crh_Cyrl_UA
0xA622434143616E73LLU, // crj_Cans_CA
0xAA22434143616E73LLU, // crk_Cans_CA
0xAE22434143616E73LLU, // crl_Cans_CA
@@ -1610,6 +1639,7 @@
0x657345534C61746ELLU, // es_Latn_ES
0x65734D584C61746ELLU, // es_Latn_MX
0x657355534C61746ELLU, // es_Latn_US
+ 0x9A44494E476F6E6DLLU, // esg_Gonm_IN
0xD24455534C61746ELLU, // esu_Latn_US
0x657445454C61746ELLU, // et_Latn_EE
0xCE6449544974616CLLU, // ett_Ital_IT
@@ -1700,10 +1730,10 @@
0x687548554C61746ELLU, // hu_Latn_HU
0x6879414D41726D6ELLU, // hy_Armn_AM
0x687A4E414C61746ELLU, // hz_Latn_NA
- 0x696146524C61746ELLU, // ia_Latn_FR
0x80284D594C61746ELLU, // iba_Latn_MY
0x84284E474C61746ELLU, // ibb_Latn_NG
0x696449444C61746ELLU, // id_Latn_ID
+ 0x90A854474C61746ELLU, // ife_Latn_TG
0x69674E474C61746ELLU, // ig_Latn_NG
0x6969434E59696969LLU, // ii_Yiii_CN
0x696B55534C61746ELLU, // ik_Latn_US
@@ -1764,6 +1794,7 @@
0x6B6D4B484B686D72LLU, // km_Khmr_KH
0x858A414F4C61746ELLU, // kmb_Latn_AO
0x6B6E494E4B6E6461LLU, // kn_Knda_IN
+ 0x95AA47574C61746ELLU, // knf_Latn_GW
0x6B6F4B524B6F7265LLU, // ko_Kore_KR
0xA1CA52554379726CLLU, // koi_Cyrl_RU
0xA9CA494E44657661LLU, // kok_Deva_IN
@@ -1854,6 +1885,7 @@
0x6D694E5A4C61746ELLU, // mi_Latn_NZ
0xB50C49444C61746ELLU, // min_Latn_ID
0xC90C495148617472LLU, // mis_Hatr_IQ
+ 0xC90C4E474D656466LLU, // mis_Medf_NG
0x6D6B4D4B4379726CLLU, // mk_Cyrl_MK
0x6D6C494E4D6C796DLLU, // ml_Mlym_IN
0xC96C53444C61746ELLU, // mls_Latn_SD
@@ -1877,6 +1909,7 @@
0xAACC4D4C4C61746ELLU, // mwk_Latn_ML
0xC6CC494E44657661LLU, // mwr_Deva_IN
0xD6CC49444C61746ELLU, // mwv_Latn_ID
+ 0xDACC5553486D6E70LLU, // mww_Hmnp_US
0x8AEC5A574C61746ELLU, // mxc_Latn_ZW
0x6D794D4D4D796D72LLU, // my_Mymr_MM
0xD70C52554379726CLLU, // myv_Cyrl_RU
@@ -1905,6 +1938,7 @@
0x998D434D4C61746ELLU, // nmg_Latn_CM
0x6E6E4E4F4C61746ELLU, // nn_Latn_NO
0x9DAD434D4C61746ELLU, // nnh_Latn_CM
+ 0xBDAD494E5763686FLLU, // nnp_Wcho_IN
0x6E6F4E4F4C61746ELLU, // no_Latn_NO
0x8DCD54484C616E61LLU, // nod_Lana_TH
0x91CD494E44657661LLU, // noe_Deva_IN
@@ -1959,6 +1993,7 @@
0x945152454C61746ELLU, // rcf_Latn_RE
0xA49149444C61746ELLU, // rej_Latn_ID
0xB4D149544C61746ELLU, // rgn_Latn_IT
+ 0x98F14D4D41726162LLU, // rhg_Arab_MM
0x8111494E4C61746ELLU, // ria_Latn_IN
0x95114D4154666E67LLU, // rif_Tfng_MA
0xC9314E5044657661LLU, // rjs_Deva_NP
@@ -1986,6 +2021,7 @@
0xC0124B454C61746ELLU, // saq_Latn_KE
0xC81249444C61746ELLU, // sas_Latn_ID
0xCC12494E4C61746ELLU, // sat_Latn_IN
+ 0xD412534E4C61746ELLU, // sav_Latn_SN
0xE412494E53617572LLU, // saz_Saur_IN
0xBC32545A4C61746ELLU, // sbp_Latn_TZ
0x736349544C61746ELLU, // sc_Latn_IT
@@ -2025,6 +2061,7 @@
0x736E5A574C61746ELLU, // sn_Latn_ZW
0xA9B24D4C4C61746ELLU, // snk_Latn_ML
0x736F534F4C61746ELLU, // so_Latn_SO
+ 0x99D2555A536F6764LLU, // sog_Sogd_UZ
0xD1D2544854686169LLU, // sou_Thai_TH
0x7371414C4C61746ELLU, // sq_Latn_AL
0x737252534379726CLLU, // sr_Cyrl_RS
@@ -2135,6 +2172,7 @@
0xC97657464C61746ELLU, // wls_Latn_WF
0xA1B64B4D41726162LLU, // wni_Arab_KM
0x776F534E4C61746ELLU, // wo_Latn_SN
+ 0x9A56494E476F6E67LLU, // wsg_Gong_IN
0xB276494E44657661LLU, // wtm_Deva_IN
0xD296434E48616E73LLU, // wuu_Hans_CN
0xD41742524C61746ELLU, // xav_Latn_BR
@@ -2169,6 +2207,7 @@
0x7A68545748616E62LLU, // zh_Hanb_TW
0x7A68434E48616E73LLU, // zh_Hans_CN
0x7A68545748616E74LLU, // zh_Hant_TW
+ 0xDCF9434E4E736875LLU, // zhx_Nshu_CN
0xB17954474C61746ELLU, // zlm_Latn_TG
0xA1994D594C61746ELLU, // zmi_Latn_MY
0x7A755A414C61746ELLU, // zu_Latn_ZA
@@ -2194,7 +2233,7 @@
{0x656E4154u, 0x656E80A1u}, // en-AT -> en-150
{0x656E4155u, 0x656E8400u}, // en-AU -> en-001
{0x656E4242u, 0x656E8400u}, // en-BB -> en-001
- {0x656E4245u, 0x656E8400u}, // en-BE -> en-001
+ {0x656E4245u, 0x656E80A1u}, // en-BE -> en-150
{0x656E424Du, 0x656E8400u}, // en-BM -> en-001
{0x656E4253u, 0x656E8400u}, // en-BS -> en-001
{0x656E4257u, 0x656E8400u}, // en-BW -> en-001
@@ -2285,6 +2324,7 @@
{0x65734152u, 0x6573A424u}, // es-AR -> es-419
{0x6573424Fu, 0x6573A424u}, // es-BO -> es-419
{0x65734252u, 0x6573A424u}, // es-BR -> es-419
+ {0x6573425Au, 0x6573A424u}, // es-BZ -> es-419
{0x6573434Cu, 0x6573A424u}, // es-CL -> es-419
{0x6573434Fu, 0x6573A424u}, // es-CO -> es-419
{0x65734352u, 0x6573A424u}, // es-CR -> es-419
@@ -2315,6 +2355,10 @@
{0x7074544Cu, 0x70745054u}, // pt-TL -> pt-PT
});
+const std::unordered_map<uint32_t, uint32_t> ___B_PARENTS({
+ {0x61725842u, 0x61729420u}, // ar-XB -> ar-015
+});
+
const struct {
const char script[4];
const std::unordered_map<uint32_t, uint32_t>* map;
@@ -2322,6 +2366,7 @@
{{'A', 'r', 'a', 'b'}, &ARAB_PARENTS},
{{'H', 'a', 'n', 't'}, &HANT_PARENTS},
{{'L', 'a', 't', 'n'}, &LATN_PARENTS},
+ {{'~', '~', '~', 'B'}, &___B_PARENTS},
};
const size_t MAX_PARENT_DEPTH = 3;
diff --git a/media/Android.bp b/media/Android.bp
index e2bdad1..75ccb22 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -77,20 +77,13 @@
path: "apex/java"
}
-metalava_updatable_media_args = " --error UnhiddenSystemApi " +
- "--hide RequiresPermission " +
- "--hide MissingPermission --hide BroadcastBehavior " +
- "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
- "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
- "--hide HiddenTypedefConstant --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) "
-
droidstubs {
name: "updatable-media-stubs",
srcs: [
":updatable-media-srcs",
":framework-media-annotation-srcs",
],
- args: metalava_updatable_media_args,
+ defaults: [ "framework-module-stubs-defaults-systemapi" ],
aidl: {
// TODO(b/135922046) remove this
include_dirs: ["frameworks/base/core/java"],
@@ -109,3 +102,67 @@
srcs: [":framework-media-annotation-srcs"],
installable: false,
}
+
+aidl_interface {
+ name: "audio_common-aidl",
+ local_include_dir: "java",
+ srcs: [
+ "java/android/media/audio/common/AudioChannelMask.aidl",
+ "java/android/media/audio/common/AudioConfig.aidl",
+ "java/android/media/audio/common/AudioFormat.aidl",
+ "java/android/media/audio/common/AudioOffloadInfo.aidl",
+ "java/android/media/audio/common/AudioStreamType.aidl",
+ "java/android/media/audio/common/AudioUsage.aidl",
+ ],
+ backend:
+ {
+ cpp: {
+ enabled: true,
+ },
+ java: {
+ // Already generated as part of the entire media java library.
+ enabled: false,
+ },
+ },
+}
+
+aidl_interface {
+ name: "soundtrigger_middleware-aidl",
+ local_include_dir: "java",
+ srcs: [
+ "java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl",
+ "java/android/media/soundtrigger_middleware/ModelParameter.aidl",
+ "java/android/media/soundtrigger_middleware/ModelParameterRange.aidl",
+ "java/android/media/soundtrigger_middleware/Phrase.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionConfig.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionEvent.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionMode.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionStatus.aidl",
+ "java/android/media/soundtrigger_middleware/SoundModel.aidl",
+ "java/android/media/soundtrigger_middleware/SoundModelType.aidl",
+ "java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
+ "java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl",
+ "java/android/media/soundtrigger_middleware/Status.aidl",
+ ],
+ backend:
+ {
+ cpp: {
+ enabled: true,
+ },
+ java: {
+ // Already generated as part of the entire media java library.
+ enabled: false,
+ },
+ ndk: {
+ // Not currently needed, and disabled because of b/146172425
+ enabled: false,
+ },
+ },
+ imports: [ "audio_common-aidl" ],
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index df799fd..33ec46a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -903,6 +903,18 @@
}
}
+ /**
+ * Returns a human readable name for a given device type
+ * @param device a native device type, NOT an AudioDeviceInfo type
+ * @return a string describing the device type
+ */
+ public static @NonNull String getDeviceName(int device) {
+ if ((device & DEVICE_BIT_IN) != 0) {
+ return getInputDeviceName(device);
+ }
+ return getOutputDeviceName(device);
+ }
+
// phone state, match audio_mode???
public static final int PHONE_STATE_OFFCALL = 0;
public static final int PHONE_STATE_RINGING = 1;
diff --git a/media/java/android/media/audio/common/AudioChannelMask.aidl b/media/java/android/media/audio/common/AudioChannelMask.aidl
new file mode 100644
index 0000000..b9b08e6
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioChannelMask.aidl
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * A channel mask per se only defines the presence or absence of a channel, not
+ * the order.
+ *
+ * The channel order convention is that channels are interleaved in order from
+ * least significant channel mask bit to most significant channel mask bit,
+ * with unused bits skipped. For example for stereo, LEFT would be first,
+ * followed by RIGHT.
+ * Any exceptions to this convention are noted at the appropriate API.
+ *
+ * AudioChannelMask is an opaque type and its internal layout should not be
+ * assumed as it may change in the future. Instead, always use functions
+ * to examine it.
+ *
+ * These are the current representations:
+ *
+ * REPRESENTATION_POSITION
+ * is a channel mask representation for position assignment. Each low-order
+ * bit corresponds to the spatial position of a transducer (output), or
+ * interpretation of channel (input). The user of a channel mask needs to
+ * know the context of whether it is for output or input. The constants
+ * OUT_* or IN_* apply to the bits portion. It is not permitted for no bits
+ * to be set.
+ *
+ * REPRESENTATION_INDEX
+ * is a channel mask representation for index assignment. Each low-order
+ * bit corresponds to a selected channel. There is no platform
+ * interpretation of the various bits. There is no concept of output or
+ * input. It is not permitted for no bits to be set.
+ *
+ * All other representations are reserved for future use.
+ *
+ * Warning: current representation distinguishes between input and output, but
+ * this will not the be case in future revisions of the platform. Wherever there
+ * is an ambiguity between input and output that is currently resolved by
+ * checking the channel mask, the implementer should look for ways to fix it
+ * with additional information outside of the mask.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioChannelMask {
+ /**
+ * must be 0 for compatibility
+ */
+ REPRESENTATION_POSITION = 0,
+ /**
+ * 1 is reserved for future use
+ */
+ REPRESENTATION_INDEX = 2,
+ /**
+ * 3 is reserved for future use
+ *
+ *
+ * These can be a complete value of AudioChannelMask
+ */
+ NONE = 0x0,
+ INVALID = 0xC0000000,
+ /**
+ * These can be the bits portion of an AudioChannelMask
+ * with representation REPRESENTATION_POSITION.
+ *
+ *
+ * output channels
+ */
+ OUT_FRONT_LEFT = 0x1,
+ OUT_FRONT_RIGHT = 0x2,
+ OUT_FRONT_CENTER = 0x4,
+ OUT_LOW_FREQUENCY = 0x8,
+ OUT_BACK_LEFT = 0x10,
+ OUT_BACK_RIGHT = 0x20,
+ OUT_FRONT_LEFT_OF_CENTER = 0x40,
+ OUT_FRONT_RIGHT_OF_CENTER = 0x80,
+ OUT_BACK_CENTER = 0x100,
+ OUT_SIDE_LEFT = 0x200,
+ OUT_SIDE_RIGHT = 0x400,
+ OUT_TOP_CENTER = 0x800,
+ OUT_TOP_FRONT_LEFT = 0x1000,
+ OUT_TOP_FRONT_CENTER = 0x2000,
+ OUT_TOP_FRONT_RIGHT = 0x4000,
+ OUT_TOP_BACK_LEFT = 0x8000,
+ OUT_TOP_BACK_CENTER = 0x10000,
+ OUT_TOP_BACK_RIGHT = 0x20000,
+ OUT_TOP_SIDE_LEFT = 0x40000,
+ OUT_TOP_SIDE_RIGHT = 0x80000,
+ /**
+ * Haptic channel characteristics are specific to a device and
+ * only used to play device specific resources (eg: ringtones).
+ * The HAL can freely map A and B to haptic controllers, the
+ * framework shall not interpret those values and forward them
+ * from the device audio assets.
+ */
+ OUT_HAPTIC_A = 0x20000000,
+ OUT_HAPTIC_B = 0x10000000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// OUT_MONO = OUT_FRONT_LEFT,
+// OUT_STEREO = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT),
+// OUT_2POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_2POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_2POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_3POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_3POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_QUAD = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+// OUT_QUAD_BACK = OUT_QUAD,
+// /**
+// * like OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_*
+// */
+// OUT_QUAD_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_SURROUND = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_BACK_CENTER),
+// OUT_PENTA = (OUT_QUAD | OUT_FRONT_CENTER),
+// OUT_5POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+// OUT_5POINT1_BACK = OUT_5POINT1,
+// /**
+// * like OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_*
+// */
+// OUT_5POINT1_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_5POINT1POINT2 = (OUT_5POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_5POINT1POINT4 = (OUT_5POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+// OUT_6POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_BACK_CENTER),
+// /**
+// * matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND
+// */
+// OUT_7POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_7POINT1POINT2 = (OUT_7POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_7POINT1POINT4 = (OUT_7POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+// OUT_MONO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_HAPTIC_A),
+// OUT_STEREO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A),
+// OUT_HAPTIC_AB = (OUT_HAPTIC_A | OUT_HAPTIC_B),
+// OUT_MONO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+// OUT_STEREO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+ /**
+ * These are bits only, not complete values
+ *
+ *
+ * input channels
+ */
+ IN_LEFT = 0x4,
+ IN_RIGHT = 0x8,
+ IN_FRONT = 0x10,
+ IN_BACK = 0x20,
+ IN_LEFT_PROCESSED = 0x40,
+ IN_RIGHT_PROCESSED = 0x80,
+ IN_FRONT_PROCESSED = 0x100,
+ IN_BACK_PROCESSED = 0x200,
+ IN_PRESSURE = 0x400,
+ IN_X_AXIS = 0x800,
+ IN_Y_AXIS = 0x1000,
+ IN_Z_AXIS = 0x2000,
+ IN_BACK_LEFT = 0x10000,
+ IN_BACK_RIGHT = 0x20000,
+ IN_CENTER = 0x40000,
+ IN_LOW_FREQUENCY = 0x100000,
+ IN_TOP_LEFT = 0x200000,
+ IN_TOP_RIGHT = 0x400000,
+ IN_VOICE_UPLINK = 0x4000,
+ IN_VOICE_DNLINK = 0x8000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// IN_MONO = IN_FRONT,
+// IN_STEREO = (IN_LEFT | IN_RIGHT),
+// IN_FRONT_BACK = (IN_FRONT | IN_BACK),
+// IN_6 = (IN_LEFT | IN_RIGHT | IN_FRONT | IN_BACK | IN_LEFT_PROCESSED | IN_RIGHT_PROCESSED),
+// IN_2POINT0POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+// IN_2POINT1POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+// IN_3POINT0POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+// IN_3POINT1POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+// IN_5POINT1 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_BACK_LEFT | IN_BACK_RIGHT | IN_LOW_FREQUENCY),
+// IN_VOICE_UPLINK_MONO = (IN_VOICE_UPLINK | IN_MONO),
+// IN_VOICE_DNLINK_MONO = (IN_VOICE_DNLINK | IN_MONO),
+// IN_VOICE_CALL_MONO = (IN_VOICE_UPLINK_MONO | IN_VOICE_DNLINK_MONO),
+// COUNT_MAX = 30,
+// INDEX_HDR = REPRESENTATION_INDEX << COUNT_MAX,
+// INDEX_MASK_1 = INDEX_HDR | ((1 << 1) - 1),
+// INDEX_MASK_2 = INDEX_HDR | ((1 << 2) - 1),
+// INDEX_MASK_3 = INDEX_HDR | ((1 << 3) - 1),
+// INDEX_MASK_4 = INDEX_HDR | ((1 << 4) - 1),
+// INDEX_MASK_5 = INDEX_HDR | ((1 << 5) - 1),
+// INDEX_MASK_6 = INDEX_HDR | ((1 << 6) - 1),
+// INDEX_MASK_7 = INDEX_HDR | ((1 << 7) - 1),
+// INDEX_MASK_8 = INDEX_HDR | ((1 << 8) - 1),
+// INDEX_MASK_9 = INDEX_HDR | ((1 << 9) - 1),
+// INDEX_MASK_10 = INDEX_HDR | ((1 << 10) - 1),
+// INDEX_MASK_11 = INDEX_HDR | ((1 << 11) - 1),
+// INDEX_MASK_12 = INDEX_HDR | ((1 << 12) - 1),
+// INDEX_MASK_13 = INDEX_HDR | ((1 << 13) - 1),
+// INDEX_MASK_14 = INDEX_HDR | ((1 << 14) - 1),
+// INDEX_MASK_15 = INDEX_HDR | ((1 << 15) - 1),
+// INDEX_MASK_16 = INDEX_HDR | ((1 << 16) - 1),
+// INDEX_MASK_17 = INDEX_HDR | ((1 << 17) - 1),
+// INDEX_MASK_18 = INDEX_HDR | ((1 << 18) - 1),
+// INDEX_MASK_19 = INDEX_HDR | ((1 << 19) - 1),
+// INDEX_MASK_20 = INDEX_HDR | ((1 << 20) - 1),
+// INDEX_MASK_21 = INDEX_HDR | ((1 << 21) - 1),
+// INDEX_MASK_22 = INDEX_HDR | ((1 << 22) - 1),
+// INDEX_MASK_23 = INDEX_HDR | ((1 << 23) - 1),
+// INDEX_MASK_24 = INDEX_HDR | ((1 << 24) - 1),
+}
diff --git a/media/java/android/media/audio/common/AudioConfig.aidl b/media/java/android/media/audio/common/AudioConfig.aidl
new file mode 100644
index 0000000..50dd796
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioConfig.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioOffloadInfo;
+
+/**
+ * Commonly used audio stream configuration parameters.
+ *
+ * {@hide}
+ */
+parcelable AudioConfig {
+ int sampleRateHz;
+ int channelMask;
+ AudioFormat format;
+ AudioOffloadInfo offloadInfo;
+ long frameCount;
+}
diff --git a/media/java/android/media/audio/common/AudioFormat.aidl b/media/java/android/media/audio/common/AudioFormat.aidl
new file mode 100644
index 0000000..aadc8e2
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioFormat.aidl
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio format is a 32-bit word that consists of:
+ * main format field (upper 8 bits)
+ * sub format field (lower 24 bits).
+ *
+ * The main format indicates the main codec type. The sub format field indicates
+ * options and parameters for each format. The sub format is mainly used for
+ * record to indicate for instance the requested bitrate or profile. It can
+ * also be used for certain formats to give informations not present in the
+ * encoded audio stream (e.g. octet alignement for AMR).
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioFormat {
+ INVALID = 0xFFFFFFFF,
+ DEFAULT = 0,
+ PCM = 0x00000000,
+ MP3 = 0x01000000,
+ AMR_NB = 0x02000000,
+ AMR_WB = 0x03000000,
+ AAC = 0x04000000,
+ /**
+ * Deprecated, Use AAC_HE_V1
+ */
+ HE_AAC_V1 = 0x05000000,
+ /**
+ * Deprecated, Use AAC_HE_V2
+ */
+ HE_AAC_V2 = 0x06000000,
+ VORBIS = 0x07000000,
+ OPUS = 0x08000000,
+ AC3 = 0x09000000,
+ E_AC3 = 0x0A000000,
+ DTS = 0x0B000000,
+ DTS_HD = 0x0C000000,
+ /**
+ * IEC61937 is encoded audio wrapped in 16-bit PCM.
+ */
+ IEC61937 = 0x0D000000,
+ DOLBY_TRUEHD = 0x0E000000,
+ EVRC = 0x10000000,
+ EVRCB = 0x11000000,
+ EVRCWB = 0x12000000,
+ EVRCNW = 0x13000000,
+ AAC_ADIF = 0x14000000,
+ WMA = 0x15000000,
+ WMA_PRO = 0x16000000,
+ AMR_WB_PLUS = 0x17000000,
+ MP2 = 0x18000000,
+ QCELP = 0x19000000,
+ DSD = 0x1A000000,
+ FLAC = 0x1B000000,
+ ALAC = 0x1C000000,
+ APE = 0x1D000000,
+ AAC_ADTS = 0x1E000000,
+ SBC = 0x1F000000,
+ APTX = 0x20000000,
+ APTX_HD = 0x21000000,
+ AC4 = 0x22000000,
+ LDAC = 0x23000000,
+ /**
+ * Dolby Metadata-enhanced Audio Transmission
+ */
+ MAT = 0x24000000,
+ AAC_LATM = 0x25000000,
+ CELT = 0x26000000,
+ APTX_ADAPTIVE = 0x27000000,
+ LHDC = 0x28000000,
+ LHDC_LL = 0x29000000,
+ APTX_TWSP = 0x2A000000,
+ /**
+ * Deprecated
+ */
+ MAIN_MASK = 0xFF000000,
+ SUB_MASK = 0x00FFFFFF,
+ /**
+ * Subformats
+ */
+ PCM_SUB_16_BIT = 0x1,
+ PCM_SUB_8_BIT = 0x2,
+ PCM_SUB_32_BIT = 0x3,
+ PCM_SUB_8_24_BIT = 0x4,
+ PCM_SUB_FLOAT = 0x5,
+ PCM_SUB_24_BIT_PACKED = 0x6,
+ MP3_SUB_NONE = 0x0,
+ AMR_SUB_NONE = 0x0,
+ AAC_SUB_MAIN = 0x1,
+ AAC_SUB_LC = 0x2,
+ AAC_SUB_SSR = 0x4,
+ AAC_SUB_LTP = 0x8,
+ AAC_SUB_HE_V1 = 0x10,
+ AAC_SUB_SCALABLE = 0x20,
+ AAC_SUB_ERLC = 0x40,
+ AAC_SUB_LD = 0x80,
+ AAC_SUB_HE_V2 = 0x100,
+ AAC_SUB_ELD = 0x200,
+ AAC_SUB_XHE = 0x300,
+ VORBIS_SUB_NONE = 0x0,
+ E_AC3_SUB_JOC = 0x1,
+ MAT_SUB_1_0 = 0x1,
+ MAT_SUB_2_0 = 0x2,
+ MAT_SUB_2_1 = 0x3,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// /**
+// * Aliases
+// *
+// *
+// * note != AudioFormat.ENCODING_PCM_16BIT
+// */
+// PCM_16_BIT = (PCM | PCM_SUB_16_BIT),
+// /**
+// * note != AudioFormat.ENCODING_PCM_8BIT
+// */
+// PCM_8_BIT = (PCM | PCM_SUB_8_BIT),
+// PCM_32_BIT = (PCM | PCM_SUB_32_BIT),
+// PCM_8_24_BIT = (PCM | PCM_SUB_8_24_BIT),
+// PCM_FLOAT = (PCM | PCM_SUB_FLOAT),
+// PCM_24_BIT_PACKED = (PCM | PCM_SUB_24_BIT_PACKED),
+// AAC_MAIN = (AAC | AAC_SUB_MAIN),
+// AAC_LC = (AAC | AAC_SUB_LC),
+// AAC_SSR = (AAC | AAC_SUB_SSR),
+// AAC_LTP = (AAC | AAC_SUB_LTP),
+// AAC_HE_V1 = (AAC | AAC_SUB_HE_V1),
+// AAC_SCALABLE = (AAC | AAC_SUB_SCALABLE),
+// AAC_ERLC = (AAC | AAC_SUB_ERLC),
+// AAC_LD = (AAC | AAC_SUB_LD),
+// AAC_HE_V2 = (AAC | AAC_SUB_HE_V2),
+// AAC_ELD = (AAC | AAC_SUB_ELD),
+// AAC_XHE = (AAC | AAC_SUB_XHE),
+// AAC_ADTS_MAIN = (AAC_ADTS | AAC_SUB_MAIN),
+// AAC_ADTS_LC = (AAC_ADTS | AAC_SUB_LC),
+// AAC_ADTS_SSR = (AAC_ADTS | AAC_SUB_SSR),
+// AAC_ADTS_LTP = (AAC_ADTS | AAC_SUB_LTP),
+// AAC_ADTS_HE_V1 = (AAC_ADTS | AAC_SUB_HE_V1),
+// AAC_ADTS_SCALABLE = (AAC_ADTS | AAC_SUB_SCALABLE),
+// AAC_ADTS_ERLC = (AAC_ADTS | AAC_SUB_ERLC),
+// AAC_ADTS_LD = (AAC_ADTS | AAC_SUB_LD),
+// AAC_ADTS_HE_V2 = (AAC_ADTS | AAC_SUB_HE_V2),
+// AAC_ADTS_ELD = (AAC_ADTS | AAC_SUB_ELD),
+// AAC_ADTS_XHE = (AAC_ADTS | AAC_SUB_XHE),
+// E_AC3_JOC = (E_AC3 | E_AC3_SUB_JOC),
+// MAT_1_0 = (MAT | MAT_SUB_1_0),
+// MAT_2_0 = (MAT | MAT_SUB_2_0),
+// MAT_2_1 = (MAT | MAT_SUB_2_1),
+// AAC_LATM_LC = (AAC_LATM | AAC_SUB_LC),
+// AAC_LATM_HE_V1 = (AAC_LATM | AAC_SUB_HE_V1),
+// AAC_LATM_HE_V2 = (AAC_LATM | AAC_SUB_HE_V2),
+}
diff --git a/media/java/android/media/audio/common/AudioOffloadInfo.aidl b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
new file mode 100644
index 0000000..ec10d71
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioStreamType;
+import android.media.audio.common.AudioUsage;
+
+/**
+ * Additional information about the stream passed to hardware decoders.
+ *
+ * {@hide}
+ */
+parcelable AudioOffloadInfo {
+ int sampleRateHz;
+ int channelMask;
+ AudioFormat format;
+ AudioStreamType streamType;
+ int bitRatePerSecond;
+ long durationMicroseconds;
+ boolean hasVideo;
+ boolean isStreaming;
+ int bitWidth;
+ int bufferSize;
+ AudioUsage usage;
+}
+
diff --git a/media/java/android/media/audio/common/AudioStreamType.aidl b/media/java/android/media/audio/common/AudioStreamType.aidl
new file mode 100644
index 0000000..c545667
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioStreamType.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio streams
+ *
+ * Audio stream type describing the intended use case of a stream.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioStreamType {
+ DEFAULT = -1,
+ MIN = 0,
+ VOICE_CALL = 0,
+ SYSTEM = 1,
+ RING = 2,
+ MUSIC = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ BLUETOOTH_SCO = 6,
+ ENFORCED_AUDIBLE = 7,
+ DTMF = 8,
+ TTS = 9,
+ ACCESSIBILITY = 10,
+}
diff --git a/media/java/android/media/audio/common/AudioUsage.aidl b/media/java/android/media/audio/common/AudioUsage.aidl
new file mode 100644
index 0000000..ef34816
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioUsage.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioUsage {
+ UNKNOWN = 0,
+ MEDIA = 1,
+ VOICE_COMMUNICATION = 2,
+ VOICE_COMMUNICATION_SIGNALLING = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ NOTIFICATION_TELEPHONY_RINGTONE = 6,
+ ASSISTANCE_ACCESSIBILITY = 11,
+ ASSISTANCE_NAVIGATION_GUIDANCE = 12,
+ ASSISTANCE_SONIFICATION = 13,
+ GAME = 14,
+ VIRTUAL_SOURCE = 15,
+ ASSISTANT = 16,
+}
diff --git a/media/java/android/media/session/ICallback.aidl b/media/java/android/media/session/ICallback.aidl
deleted file mode 100644
index 322bffa..0000000
--- a/media/java/android/media/session/ICallback.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/* Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.session;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.media.session.MediaSession;
-import android.view.KeyEvent;
-
-/**
- * @hide
- */
-oneway interface ICallback {
- void onMediaKeyEventDispatchedToMediaSession(in KeyEvent event,
- in MediaSession.Token sessionToken);
- void onMediaKeyEventDispatchedToMediaButtonReceiver(in KeyEvent event,
- in ComponentName mediaButtonReceiver);
-
- void onAddressedPlayerChangedToMediaSession(in MediaSession.Token sessionToken);
- void onAddressedPlayerChangedToMediaButtonReceiver(in ComponentName mediaButtonReceiver);
-}
-
diff --git a/media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl b/media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl
new file mode 100644
index 0000000..90d9134
--- /dev/null
+++ b/media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.media.session.MediaSession;
+import android.view.KeyEvent;
+
+/**
+ * @hide
+ */
+oneway interface IOnMediaKeyEventDispatchedListener {
+ void onMediaKeyEventDispatched(in KeyEvent event, in String packageName,
+ in MediaSession.Token sessionToken);
+}
diff --git a/media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl b/media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl
new file mode 100644
index 0000000..9566e75
--- /dev/null
+++ b/media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.media.session.MediaSession;
+
+/**
+ * @hide
+ */
+oneway interface IOnMediaKeyEventSessionChangedListener {
+ void onMediaKeyEventSessionChanged(in String packageName,
+ in MediaSession.Token mediaKeyEventSessionToken);
+}
+
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 01e6ed5..c8502a5 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -20,7 +20,8 @@
import android.media.IRemoteVolumeController;
import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
-import android.media.session.ICallback;
+import android.media.session.IOnMediaKeyEventDispatchedListener;
+import android.media.session.IOnMediaKeyEventSessionChangedListener;
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
@@ -62,8 +63,12 @@
// For PhoneWindowManager to precheck media keys
boolean isGlobalPriorityActive();
- void registerCallback(in ICallback callback);
- void unregisterCallback(in ICallback callback);
+ void addOnMediaKeyEventDispatchedListener(in IOnMediaKeyEventDispatchedListener listener);
+ void removeOnMediaKeyEventDispatchedListener(in IOnMediaKeyEventDispatchedListener listener);
+ void addOnMediaKeyEventSessionChangedListener(
+ in IOnMediaKeyEventSessionChangedListener listener);
+ void removeOnMediaKeyEventSessionChangedListener(
+ in IOnMediaKeyEventSessionChangedListener listener);
void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
void setOnMediaKeyListener(in IOnMediaKeyListener listener);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 92fb31b..a89dc5f 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -32,7 +32,6 @@
import android.media.Session2Token;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -78,29 +77,33 @@
*/
public static final int RESULT_MEDIA_KEY_HANDLED = 1;
private final ISessionManager mService;
+ private final OnMediaKeyEventDispatchedListenerStub mOnMediaKeyEventDispatchedListenerStub =
+ new OnMediaKeyEventDispatchedListenerStub();
+ private final OnMediaKeyEventSessionChangedListenerStub
+ mOnMediaKeyEventSessionChangedListenerStub =
+ new OnMediaKeyEventSessionChangedListenerStub();
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
- = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
+ private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners =
+ new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
@GuardedBy("mLock")
private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper>
mSession2TokensListeners = new ArrayMap<>();
@GuardedBy("mLock")
- private final CallbackStub mCbStub = new CallbackStub();
+ private final Map<OnMediaKeyEventDispatchedListener, Executor>
+ mOnMediaKeyEventDispatchedListeners = new HashMap<>();
@GuardedBy("mLock")
- private final Map<Callback, Executor> mCallbacks = new HashMap<>();
+ private final Map<OnMediaKeyEventSessionChangedListener, Executor>
+ mMediaKeyEventSessionChangedCallbacks = new HashMap<>();
@GuardedBy("mLock")
- private MediaSession.Token mCurMediaButtonSession;
+ private String mCurMediaKeyEventSessionPackage;
@GuardedBy("mLock")
- private ComponentName mCurMediaButtonReceiver;
+ private MediaSession.Token mCurMediaKeyEventSession;
private Context mContext;
private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
private OnMediaKeyListenerImpl mOnMediaKeyListener;
- // TODO: Remove mLegacyCallback once Bluetooth app stop calling setCallback() method.
- @GuardedBy("mLock")
- private Callback mLegacyCallback;
/**
* @hide
@@ -756,89 +759,118 @@
}
/**
- * Set a {@link Callback}.
- *
- * <p>System can only have a single callback, and the callback can only be set by
- * Bluetooth service process.
- *
- * @param callback A {@link Callback}. {@code null} to reset.
- * @param handler The handler on which the callback should be invoked, or {@code null}
- * if the callback should be invoked on the calling thread's looper.
- * @hide
- */
- // TODO: Remove this method once Bluetooth app stop calling it.
- public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
- if (handler == null) {
- handler = new Handler();
- }
- synchronized (mLock) {
- if (mLegacyCallback != null) {
- unregisterCallback(mLegacyCallback);
- }
- mLegacyCallback = callback;
- if (callback != null) {
- registerCallback(new HandlerExecutor(handler), callback);
- }
- }
- }
-
- /**
- * Register a {@link Callback}.
+ * Add a {@link OnMediaKeyEventDispatchedListener}.
*
* @param executor The executor on which the callback should be invoked
- * @param callback A {@link Callback}.
+ * @param listener A {@link OnMediaKeyEventDispatchedListener}.
* @hide
*/
@SystemApi
@RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
- public void registerCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull Callback callback) {
+ public void addOnMediaKeyEventDispatchedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnMediaKeyEventDispatchedListener listener) {
if (executor == null) {
throw new NullPointerException("executor shouldn't be null");
}
- if (callback == null) {
- throw new NullPointerException("callback shouldn't be null");
+ if (listener == null) {
+ throw new NullPointerException("listener shouldn't be null");
}
synchronized (mLock) {
try {
- mCallbacks.put(callback, executor);
- if (mCurMediaButtonSession != null) {
- executor.execute(
- () -> callback.onAddressedPlayerChanged(mCurMediaButtonSession));
- } else if (mCurMediaButtonReceiver != null) {
- executor.execute(
- () -> callback.onAddressedPlayerChanged(mCurMediaButtonReceiver));
- }
-
- if (mCallbacks.size() == 1) {
- mService.registerCallback(mCbStub);
+ mOnMediaKeyEventDispatchedListeners.put(listener, executor);
+ if (mOnMediaKeyEventDispatchedListeners.size() == 1) {
+ mService.addOnMediaKeyEventDispatchedListener(
+ mOnMediaKeyEventDispatchedListenerStub);
}
} catch (RemoteException e) {
- Log.e(TAG, "Failed to set media key callback", e);
+ Log.e(TAG, "Failed to set media key listener", e);
}
}
}
/**
- * Unregister a {@link Callback}.
+ * Remove a {@link OnMediaKeyEventDispatchedListener}.
*
- * @param callback A {@link Callback}.
+ * @param listener A {@link OnMediaKeyEventDispatchedListener}.
* @hide
*/
@SystemApi
@RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
- public void unregisterCallback(@NonNull Callback callback) {
- if (callback == null) {
- throw new NullPointerException("callback shouldn't be null");
+ public void removeOnMediaKeyEventDispatchedListener(
+ @NonNull OnMediaKeyEventDispatchedListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener shouldn't be null");
}
synchronized (mLock) {
try {
- mCallbacks.remove(callback);
- if (mCallbacks.size() == 0) {
- mService.unregisterCallback(mCbStub);
+ mOnMediaKeyEventDispatchedListeners.remove(listener);
+ if (mOnMediaKeyEventDispatchedListeners.size() == 0) {
+ mService.removeOnMediaKeyEventDispatchedListener(
+ mOnMediaKeyEventDispatchedListenerStub);
}
} catch (RemoteException e) {
- Log.e(TAG, "Failed to set media key callback", e);
+ Log.e(TAG, "Failed to set media key event dispatched listener", e);
+ }
+ }
+ }
+
+ /**
+ * Add a {@link OnMediaKeyEventDispatchedListener}.
+ *
+ * @param executor The executor on which the callback should be invoked
+ * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void addOnMediaKeyEventSessionChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnMediaKeyEventSessionChangedListener listener) {
+ if (executor == null) {
+ throw new NullPointerException("executor shouldn't be null");
+ }
+ if (listener == null) {
+ throw new NullPointerException("listener shouldn't be null");
+ }
+ synchronized (mLock) {
+ try {
+ mMediaKeyEventSessionChangedCallbacks.put(listener, executor);
+ executor.execute(
+ () -> listener.onMediaKeyEventSessionChanged(
+ mCurMediaKeyEventSessionPackage, mCurMediaKeyEventSession));
+ if (mMediaKeyEventSessionChangedCallbacks.size() == 1) {
+ mService.addOnMediaKeyEventSessionChangedListener(
+ mOnMediaKeyEventSessionChangedListenerStub);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set media key listener", e);
+ }
+ }
+ }
+
+ /**
+ * Remove a {@link OnMediaKeyEventSessionChangedListener}.
+ *
+ * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void removeOnMediaKeyEventSessionChangedListener(
+ @NonNull OnMediaKeyEventSessionChangedListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener shouldn't be null");
+ }
+ synchronized (mLock) {
+ try {
+ mMediaKeyEventSessionChangedCallbacks.remove(listener);
+ if (mMediaKeyEventSessionChangedCallbacks.size() == 0) {
+ mService.removeOnMediaKeyEventSessionChangedListener(
+ mOnMediaKeyEventSessionChangedListenerStub);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set media key listener", e);
}
}
}
@@ -900,54 +932,46 @@
}
/**
- * Callbacks for the media session service.
- *
- * <p>Called when a media key event is dispatched or the addressed player is changed.
- * The addressed player is either the media session or the media button receiver that will
- * receive media key events.
+ * Listener to receive when the media session service
* @hide
*/
@SystemApi
- public static abstract class Callback {
+ public interface OnMediaKeyEventDispatchedListener {
/**
- * Called when a media key event is dispatched to the media session
- * through the media session service.
+ * Called when a media key event is dispatched through the media session service. The
+ * session token can be {@link null} if the framework has sent the media key event to the
+ * media button receiver to revive the media app's playback.
+ *
+ * the session is dead when , but the framework sent
*
* @param event Dispatched media key event.
- * @param sessionToken The media session's token.
+ * @param packageName Package
+ * @param sessionToken The media session's token. Can be {@code null}.
*/
- public abstract void onMediaKeyEventDispatched(KeyEvent event,
- MediaSession.Token sessionToken);
+ default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
+ @NonNull MediaSession.Token sessionToken) { }
+ }
+ /**
+ * Listener to receive changes in the media key event session, which would receive the media key
+ * event unless specified.
+ * @hide
+ */
+ @SystemApi
+ public interface OnMediaKeyEventSessionChangedListener {
/**
- * Called when a media key event is dispatched to the media button receiver
- * through the media session service.
- * <p>MediaSessionService may broadcast key events to the media button receiver
- * when reviving playback after the media session is released.
+ * Called when the media key session is changed to the given media session. The key event
+ * session is the media session which would receive key event by default, unless the caller
+ * has specified the target.
+ * <p>
+ * The session token can be {@link null} if the media button session is unset. In that case,
+ * framework would dispatch to the last sessions's media button receiver.
*
- * @param event Dispatched media key event.
- * @param mediaButtonReceiver The media button receiver.
+ * @param packageName The package name who would receive the media key event. Can be empty.
+ * @param sessionToken The media session's token. Can be {@code null.}
*/
- public abstract void onMediaKeyEventDispatched(KeyEvent event,
- ComponentName mediaButtonReceiver);
-
- /**
- * Called when the addressed player is changed to a media session.
- * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
- * {@link #registerCallback} if the addressed player exists.
- *
- * @param sessionToken The media session's token.
- */
- public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken);
-
- /**
- * Called when the addressed player is changed to the media button receiver.
- * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
- * {@link #registerCallback} if the addressed player exists.
- *
- * @param mediaButtonReceiver The media button receiver.
- */
- public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver);
+ default void onMediaKeyEventSessionChanged(@NonNull String packageName,
+ @Nullable MediaSession.Token sessionToken) { }
}
/**
@@ -1149,50 +1173,35 @@
}
}
- private final class CallbackStub extends ICallback.Stub {
+ private final class OnMediaKeyEventDispatchedListenerStub
+ extends IOnMediaKeyEventDispatchedListener.Stub {
@Override
- public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
+ public void onMediaKeyEventDispatched(KeyEvent event, String packageName,
MediaSession.Token sessionToken) {
synchronized (mLock) {
- for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
+ for (Map.Entry<OnMediaKeyEventDispatchedListener, Executor> e
+ : mOnMediaKeyEventDispatchedListeners.entrySet()) {
e.getValue().execute(
- () -> e.getKey().onMediaKeyEventDispatched(event, sessionToken));
+ () -> e.getKey().onMediaKeyEventDispatched(event, packageName,
+ sessionToken));
}
}
}
+ }
+ private final class OnMediaKeyEventSessionChangedListenerStub
+ extends IOnMediaKeyEventSessionChangedListener.Stub {
@Override
- public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
- ComponentName mediaButtonReceiver) {
+ public void onMediaKeyEventSessionChanged(String packageName,
+ MediaSession.Token sessionToken) {
synchronized (mLock) {
- for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
- e.getValue().execute(
- () -> e.getKey().onMediaKeyEventDispatched(event, mediaButtonReceiver));
- }
- }
- }
-
- @Override
- public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
- synchronized (mLock) {
- mCurMediaButtonSession = sessionToken;
- mCurMediaButtonReceiver = null;
- for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
- e.getValue().execute(() -> e.getKey().onAddressedPlayerChanged(sessionToken));
- }
- }
- }
-
- @Override
- public void onAddressedPlayerChangedToMediaButtonReceiver(
- ComponentName mediaButtonReceiver) {
- synchronized (mLock) {
- mCurMediaButtonSession = null;
- mCurMediaButtonReceiver = mediaButtonReceiver;
- for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
- e.getValue().execute(() -> e.getKey().onAddressedPlayerChanged(
- mediaButtonReceiver));
+ mCurMediaKeyEventSessionPackage = packageName;
+ mCurMediaKeyEventSession = sessionToken;
+ for (Map.Entry<OnMediaKeyEventSessionChangedListener, Executor> e
+ : mMediaKeyEventSessionChangedCallbacks.entrySet()) {
+ e.getValue().execute(() -> e.getKey().onMediaKeyEventSessionChanged(packageName,
+ sessionToken));
}
}
}
diff --git a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
new file mode 100644
index 0000000..3dbc705
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * A recognition confidence level.
+ * This type is used to represent either a threshold or an actual detection confidence level.
+ *
+ * {@hide}
+ */
+parcelable ConfidenceLevel {
+ /** user ID. */
+ int userId;
+ /**
+ * Confidence level in percent (0 - 100).
+ * <ul>
+ * <li>Min level for recognition configuration
+ * <li>Detected level for recognition event.
+ * </ul>
+ */
+ int levelPercent;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
new file mode 100644
index 0000000..149c1cd
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+
+/**
+ * Main interface for a client to get notifications of events coming from this module.
+ *
+ * {@hide}
+ */
+oneway interface ISoundTriggerCallback {
+ /**
+ * Invoked whenever a recognition event is triggered (typically, on recognition, but also in
+ * case of external aborting of a recognition or a forced recognition event - see the status
+ * code in the event for determining).
+ */
+ void onRecognition(int modelHandle, in RecognitionEvent event);
+ /**
+ * Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
+ * also in case of external aborting of a recognition or a forced recognition event - see the
+ * status code in the event for determining).
+ */
+ void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event);
+ /**
+ * Notifies the client the recognition has become available after previously having been
+ * unavailable, or vice versa. This method will always be invoked once immediately after
+ * attachment, and then every time there is a change in availability.
+ * When availability changes from available to unavailable, all active recognitions are aborted,
+ * and this event will be sent in addition to the abort event.
+ */
+ void onRecognitionAvailabilityChange(boolean available);
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
new file mode 100644
index 0000000..8033307
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+
+/**
+ * Main entry point into this module.
+ *
+ * Allows the client to enumerate the available soundtrigger devices and their capabilities, then
+ * attach to either one of them in order to use it.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerMiddlewareService {
+ /**
+ * Query the available modules and their capabilities.
+ */
+ SoundTriggerModuleDescriptor[] listModules();
+
+ /**
+ * Attach to one of the available modules.
+ * listModules() must be called prior to calling this method and the provided handle must be
+ * one of the handles from the returned list.
+ */
+ ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);
+
+ /**
+ * Notify the service that external input capture is taking place. This may cause some of the
+ * active recognitions to be aborted.
+ */
+ void setExternalCaptureState(boolean active);
+}
\ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
new file mode 100644
index 0000000..c4a5785
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+
+/**
+ * A sound-trigger module.
+ *
+ * This interface allows a client to operate a sound-trigger device, intended for low-power
+ * detection of various sound patterns, represented by a "sound model".
+ *
+ * Basic operation is to load a sound model (either a generic one or a "phrase" model), then
+ * initiate recognition on this model. A trigger will be delivered asynchronously via a callback
+ * provided by the caller earlier, when attaching to this interface.
+ *
+ * In additon to recognition events, this module will also produce abort events in cases where
+ * recognition has been externally preempted.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerModule {
+ /**
+ * Load a sound model. Will return a handle to the model on success or will throw a
+ * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+ * (for example, lack of resources of loading a model at the time of call.
+ * Model must eventually be unloaded using {@link #unloadModel(int)} prior to detaching.
+ *
+ * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+ * resources required for loading the model are currently consumed by other clients.
+ */
+ int loadModel(in SoundModel model);
+
+ /**
+ * Load a phrase sound model. Will return a handle to the model on success or will throw a
+ * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+ * (for example, lack of resources of loading a model at the time of call.
+ * Model must eventually be unloaded using unloadModel prior to detaching.
+ *
+ * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+ * resources required for loading the model are currently consumed by other clients.
+ */
+ int loadPhraseModel(in PhraseSoundModel model);
+
+ /**
+ * Unload a model, previously loaded with loadModel or loadPhraseModel. After unloading, model
+ * can no longer be used for recognition and the resources occupied by it are released.
+ * Model must not be active at the time of unloading. Cient may call stopRecognition to ensure
+ * that.
+ */
+ void unloadModel(int modelHandle);
+
+ /**
+ * Initiate recognition on a previously loaded model.
+ * Recognition event would eventually be delivered via the client-provided callback, typically
+ * supplied during attachment to this interface.
+ *
+ * Once a recognition event is passed to the client, the recognition automatically become
+ * inactive, unless the event is of the RecognitionStatus.FORCED kind. Client can also shut down
+ * the recognition explicitly, via stopRecognition.
+ */
+ void startRecognition(int modelHandle, in RecognitionConfig config);
+
+ /**
+ * Stop a recognition of a previously active recognition. Will NOT generate a recognition event.
+ * This call is idempotent - calling it on an inactive model has no effect. However, it must
+ * only be used with a loaded model handle.
+ */
+ void stopRecognition(int modelHandle);
+
+ /**
+ * Force generation of a recognition event. Handle must be that of a loaded model. If
+ * recognition is inactive, will do nothing. If recognition is active, will asynchronously
+ * deliever an event with RecognitionStatus.FORCED status and leave recognition in active state.
+ * To avoid any race conditions, once an event signalling the automatic stopping of recognition
+ * is sent, no more forced events will get sent (even if previously requested) until recognition
+ * is explicitly started again.
+ *
+ * Since not all module implementations support this feature, may throw a
+ * ServiceSpecificException with an OPERATION_NOT_SUPPORTED status.
+ */
+ void forceRecognitionEvent(int modelHandle);
+
+ /**
+ * Set a model specific parameter with the given value. This parameter
+ * will keep its value for the duration the model is loaded regardless of starting and stopping
+ * recognition. Once the model is unloaded, the value will be lost.
+ * It is expected to check if the handle supports the parameter via the
+ * queryModelParameterSupport API prior to calling this method.
+ *
+ * @param modelHandle The sound model handle indicating which model to modify parameters
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @param value The value to set for the given model parameter
+ */
+ void setModelParameter(int modelHandle, ModelParameter modelParam, int value);
+
+ /**
+ * Get a model specific parameter. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParameter for parameter default values.
+ * It is expected to check if the handle supports the parameter via the
+ * queryModelParameterSupport API prior to calling this method.
+ *
+ * @param modelHandle The sound model associated with given modelParam
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @return Value set to the requested parameter.
+ */
+ int getModelParameter(int modelHandle, ModelParameter modelParam);
+
+ /**
+ * Determine if parameter control is supported for the given model handle, and its valid value
+ * range if it is.
+ *
+ * @param modelHandle The sound model handle indicating which model to query
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @return If parameter is supported, the return value is its valid range, otherwise null.
+ */
+ @nullable ModelParameterRange queryModelParameterSupport(int modelHandle,
+ ModelParameter modelParam);
+
+ /**
+ * Detach from the module, releasing any active resources.
+ * This will ensure the client callback is no longer called after this call returns.
+ * All models must have been unloaded prior to calling this method.
+ */
+ void detach();
+}
\ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
new file mode 100644
index 0000000..0993627
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Model specific parameters to be used with parameter set and get APIs.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum ModelParameter {
+ /**
+ * Placeholder for invalid model parameter used for returning error or
+ * passing an invalid value.
+ */
+ INVALID = -1,
+
+ /**
+ * Controls the sensitivity threshold adjustment factor for a given model.
+ * Negative value corresponds to less sensitive model (high threshold) and
+ * a positive value corresponds to a more sensitive model (low threshold).
+ * Default value is 0.
+ */
+ THRESHOLD_FACTOR = 0,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
new file mode 100644
index 0000000..d6948a8
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Value range for a model parameter.
+ *
+ * {@hide}
+ */
+parcelable ModelParameterRange {
+ /** Minimum (inclusive) */
+ int minInclusive;
+ /** Maximum (inclusive) */
+ int maxInclusive;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Phrase.aidl b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
new file mode 100644
index 0000000..98a489f8
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Key phrase descriptor.
+ *
+ * {@hide}
+ */
+parcelable Phrase {
+ /** Unique keyphrase ID assigned at enrollment time. */
+ int id;
+ /** Recognition modes supported by this key phrase (bitfield of RecognitionMode enum). */
+ int recognitionModes;
+ /** List of users IDs associated with this key phrase. */
+ int[] users;
+ /** Locale - Java Locale style (e.g. en_US). */
+ String locale;
+ /** Phrase text. */
+ String text;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
new file mode 100644
index 0000000..6a3ec61
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+
+/**
+ * An event that gets sent to indicate a phrase recognition (or aborting of the recognition
+ process).
+ * {@hide}
+ */
+parcelable PhraseRecognitionEvent {
+ /** Common recognition event. */
+ RecognitionEvent common;
+ /** List of descriptors for each recognized key phrase */
+ PhraseRecognitionExtra[] phraseExtras;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
new file mode 100644
index 0000000..cb96bf3
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+
+/**
+ * Specialized recognition event for key phrase detection.
+ * {@hide}
+ */
+parcelable PhraseRecognitionExtra {
+ // TODO(ytai): Constants / enums.
+
+ /** keyphrase ID */
+ int id;
+ /** recognition modes used for this keyphrase */
+ int recognitionModes;
+ /** confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER */
+ int confidenceLevel;
+ /** number of user confidence levels */
+ ConfidenceLevel[] levels;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
new file mode 100644
index 0000000..81028c1
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.Phrase;
+
+/**
+ * Specialized sound model for key phrase detection.
+ * Proprietary representation of key phrases in binary data must match
+ * information indicated by phrases field.
+ * {@hide}
+ */
+parcelable PhraseSoundModel {
+ /** Common part of sound model descriptor */
+ SoundModel common;
+ /** List of descriptors for key phrases supported by this sound model */
+ Phrase[] phrases;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
new file mode 100644
index 0000000..c7642e8
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+
+/**
+ * Configuration for tuning behavior of an active recognition process.
+ * {@hide}
+ */
+parcelable RecognitionConfig {
+ /* Capture and buffer audio for this recognition instance. */
+ boolean captureRequested;
+
+ /* Configuration for each key phrase. */
+ PhraseRecognitionExtra[] phraseRecognitionExtras;
+
+ /** Opaque capture configuration data. */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
new file mode 100644
index 0000000..de4d060
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * An event that gets sent to indicate a recognition (or aborting of the recognition process).
+ * {@hide}
+ */
+parcelable RecognitionEvent {
+ /** Recognition status. */
+ RecognitionStatus status;
+ /** Event type, same as sound model type. */
+ SoundModelType type;
+ /** Is it possible to capture audio from this utterance buffered by the implementation. */
+ boolean captureAvailable;
+ /* Audio session ID. framework use. */
+ int captureSession;
+ /**
+ * Delay in ms between end of model detection and start of audio available for capture.
+ * A negative value is possible (e.g. if key phrase is also available for Capture.
+ */
+ int captureDelayMs;
+ /** Duration in ms of audio captured before the start of the trigger. 0 if none. */
+ int capturePreambleMs;
+ /** If true, the 'data' field below contains the capture of the trigger sound. */
+ boolean triggerInData;
+ /**
+ * Audio format of either the trigger in event data or to use for capture of the rest of the
+ * utterance.
+ */
+ AudioConfig audioConfig;
+ /** Additional data. */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
new file mode 100644
index 0000000..d8bfff4
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Recognition mode.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionMode {
+ /** Simple voice trigger. */
+ VOICE_TRIGGER = 0x1,
+ /** Trigger only if one user in model identified. */
+ USER_IDENTIFICATION = 0x2,
+ /** Trigger only if one user in model authenticated. */
+ USER_AUTHENTICATION = 0x4,
+ /** Generic sound trigger. */
+ GENERIC_TRIGGER = 0x8,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
new file mode 100644
index 0000000..d563edc
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * A status for indicating the type of a recognition event.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionStatus {
+ /** Recognition success. */
+ SUCCESS = 0,
+ /** Recognition aborted (e.g. capture preempted by another use-case. */
+ ABORTED = 1,
+ /** Recognition failure. */
+ FAILURE = 2,
+ /**
+ * Recognition event was triggered by a forceRecognitionEvent request, not by the DSP.
+ * Note that forced detections *do not* stop the active recognition, unlike the other types.
+ */
+ FORCED = 3
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
new file mode 100644
index 0000000..fba1ee50
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * Base sound model descriptor. This struct can be extended for various specific types by way of
+ * aggregation.
+ * {@hide}
+ */
+parcelable SoundModel {
+ /** Model type. */
+ SoundModelType type;
+ /** Unique sound model ID. */
+ String uuid;
+ /**
+ * Unique vendor ID. Identifies the engine the sound model
+ * was build for */
+ String vendorUuid;
+ /** Opaque data transparent to Android framework */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
new file mode 100644
index 0000000..f2abc9a
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Sound model type.
+ * {@hide}
+ */
+@Backing(type="int")
+enum SoundModelType {
+ /** Unspecified sound model type */
+ UNKNOWN = -1,
+ /** Key phrase sound models */
+ KEYPHRASE = 0,
+ /** All models other than keyphrase */
+ GENERIC = 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
new file mode 100644
index 0000000..667135f
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+/**
+ * A descriptor of an available sound trigger module, containing the handle used to reference the
+ * module, as well its capabilities.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleDescriptor {
+ /** Module handle to be used for attaching to it. */
+ int handle;
+ /** Module capabilities. */
+ SoundTriggerModuleProperties properties;
+}
+
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
new file mode 100644
index 0000000..1a3b402
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Capabilities of a sound trigger module.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleProperties {
+ /** Implementor name */
+ String implementor;
+ /** Implementation description */
+ String description;
+ /** Implementation version */
+ int version;
+ /**
+ * Unique implementation ID. The UUID must change with each version of
+ the engine implementation */
+ String uuid;
+ /** Maximum number of concurrent sound models loaded */
+ int maxSoundModels;
+ /** Maximum number of key phrases */
+ int maxKeyPhrases;
+ /** Maximum number of concurrent users detected */
+ int maxUsers;
+ /** All supported modes. e.g RecognitionMode.VOICE_TRIGGER */
+ int recognitionModes;
+ /** Supports seamless transition from detection to capture */
+ boolean captureTransition;
+ /** Maximum buffering capacity in ms if captureTransition is true */
+ int maxBufferMs;
+ /** Supports capture by other use cases while detection is active */
+ boolean concurrentCapture;
+ /** Returns the trigger capture in event */
+ boolean triggerInEvent;
+ /**
+ * Rated power consumption when detection is active with TDB
+ * silence/sound/speech ratio */
+ int powerConsumptionMw;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
new file mode 100644
index 0000000..d8f9d8f
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum Status {
+ /** Success. */
+ SUCCESS = 0,
+ /** Failure due to resource contention. This is typically a temporary condition. */
+ RESOURCE_CONTENTION = 1,
+ /** Operation is not supported in this implementation. This is a permanent condition. */
+ OPERATION_NOT_SUPPORTED = 2,
+}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 16ba63b..0f402eb 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -577,7 +577,7 @@
try {
// note - we are relying on a special case in MediaProvider.update() to update
// the paths for all children in the case where this is a directory.
- final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+ final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in mMediaProvider.update", e);
@@ -658,7 +658,7 @@
// Old parent exists in MediaProvider - perform a move
// note - we are relying on a special case in MediaProvider.update() to update
// the paths for all children in the case where this is a directory.
- final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+ final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
} else {
// Old parent doesn't exist - add the object
@@ -873,7 +873,7 @@
}
private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
- final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+ final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
int ret = -1;
Cursor c = null;
@@ -893,7 +893,7 @@
}
private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
- final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+ final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
try {
// Delete the object(s) from MediaProvider, but ignore errors.
if (isDir) {
@@ -921,71 +921,12 @@
@VisibleForNative
private int[] getObjectReferences(int handle) {
- MtpStorageManager.MtpObject obj = mManager.getObject(handle);
- if (obj == null)
- return null;
- // Translate this handle to the MediaProvider Handle
- handle = findInMedia(obj, obj.getPath());
- if (handle == -1)
- return null;
- Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
- Cursor c = null;
- try {
- c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
- if (c == null) {
- return null;
- }
- ArrayList<Integer> result = new ArrayList<>();
- while (c.moveToNext()) {
- // Translate result handles back into handles for this session.
- String refPath = c.getString(0);
- MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
- if (refObj != null) {
- result.add(refObj.getId());
- }
- }
- return result.stream().mapToInt(Integer::intValue).toArray();
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in getObjectList", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
return null;
}
@VisibleForNative
private int setObjectReferences(int handle, int[] references) {
- MtpStorageManager.MtpObject obj = mManager.getObject(handle);
- if (obj == null)
- return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
- // Translate this handle to the MediaProvider Handle
- handle = findInMedia(obj, obj.getPath());
- if (handle == -1)
- return MtpConstants.RESPONSE_GENERAL_ERROR;
- Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
- ArrayList<ContentValues> valuesList = new ArrayList<>();
- for (int id : references) {
- // Translate each reference id to the MediaProvider Id
- MtpStorageManager.MtpObject refObj = mManager.getObject(id);
- if (refObj == null)
- continue;
- int refHandle = findInMedia(refObj, refObj.getPath());
- if (refHandle == -1)
- continue;
- ContentValues values = new ContentValues();
- values.put(Files.FileColumns._ID, refHandle);
- valuesList.add(values);
- }
- try {
- if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
- return MtpConstants.RESPONSE_OK;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in setObjectReferences", e);
- }
- return MtpConstants.RESPONSE_GENERAL_ERROR;
+ return MtpConstants.RESPONSE_OPERATION_NOT_SUPPORTED;
}
@VisibleForNative
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 05aaa82..20980a9 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -742,11 +742,11 @@
return OK;
}
-status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const {
- mediametrics_handle_t reply2 = MediaAnalyticsItem::convert(reply);
+status_t JMediaCodec::getMetrics(JNIEnv *, mediametrics::Item * &reply) const {
+ mediametrics_handle_t reply2 = mediametrics::Item::convert(reply);
status_t status = mCodec->getMetrics(reply2);
// getMetrics() updates reply2, pass the converted update along to our caller.
- reply = MediaAnalyticsItem::convert(reply2);
+ reply = mediametrics::Item::convert(reply2);
return status;
}
@@ -1850,7 +1850,7 @@
}
// get what we have for the metrics from the codec
- MediaAnalyticsItem *item = 0;
+ mediametrics::Item *item = 0;
status_t err = codec->getMetrics(env, item);
if (err != OK) {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index dfe30a3..ce1c805 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -21,7 +21,7 @@
#include "jni.h"
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/hardware/CryptoAPI.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/foundation/AHandler.h>
@@ -124,7 +124,7 @@
status_t getCodecInfo(JNIEnv *env, jobject *codecInfo) const;
- status_t getMetrics(JNIEnv *env, MediaAnalyticsItem * &reply) const;
+ status_t getMetrics(JNIEnv *env, mediametrics::Item * &reply) const;
status_t setParameters(const sp<AMessage> ¶ms);
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index f5ae9d0..528dc62 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -913,7 +913,7 @@
}
// build and return the Bundle
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(reply);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index d315154..494c617 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,7 +18,7 @@
#include <binder/Parcel.h>
#include <jni.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <nativehelper/JNIHelp.h>
#include "android_media_MediaMetricsJNI.h"
@@ -52,7 +52,7 @@
const jmethodID constructID;
jobject const bundle;
- // We use templated put to access MediaAnalyticsItem based on data type not type enum.
+ // We use templated put to access mediametrics::Item based on data type not type enum.
// See std::variant and std::visit.
template<typename T>
void put(jstring keyName, const T& value) = delete;
@@ -97,7 +97,7 @@
// place the attributes into a java PersistableBundle object
jobject MediaMetricsJNI::writeMetricsToBundle(
- JNIEnv* env, MediaAnalyticsItem *item, jobject bundle)
+ JNIEnv* env, mediametrics::Item *item, jobject bundle)
{
BundleHelper bh(env, bundle);
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
index 63ec27a..bcad558 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -19,7 +19,7 @@
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <binder/PersistableBundle.h>
// Copeid from core/jni/ (libandroid_runtime.so)
@@ -27,7 +27,7 @@
class MediaMetricsJNI {
public:
- static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
+ static jobject writeMetricsToBundle(JNIEnv* env, mediametrics::Item *item, jobject mybundle);
static jobject nativeToJavaPersistableBundle(JNIEnv*, os::PersistableBundle*);
};
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index b4fa07b..963b650 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -23,7 +23,7 @@
#include <media/AudioResamplerPublic.h>
#include <media/IMediaHTTPService.h>
#include <media/MediaPlayerInterface.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition
#include <stdio.h>
#include <assert.h>
@@ -682,7 +682,7 @@
return (jobject) NULL;
}
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(p);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index f8ba36d..6eeccf0 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -29,7 +29,7 @@
#include <gui/Surface.h>
#include <camera/Camera.h>
#include <media/mediarecorder.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/MicrophoneInfo.h>
#include <media/stagefright/PersistentSurface.h>
#include <utils/threads.h>
@@ -694,7 +694,7 @@
}
// build and return the Bundle
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(reply);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 3258d57..2697a10 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -29,6 +29,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.internal.telephony.PhoneConstants;
/**
@@ -138,7 +139,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onRegisterDefaultNetworkAvail subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, true);
+ telephonyMgr.createForSubscriptionId(subId).reportDefaultNetworkStatus(true);
}
private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) {
@@ -146,7 +147,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDeregisterDefaultNetworkAvail subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, false);
+ telephonyMgr.createForSubscriptionId(subId).reportDefaultNetworkStatus(false);
}
private static void onDisableRadio(Intent intent, Context context) {
@@ -154,7 +155,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDisableRadio subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionSetRadioEnabled(subId, !ENABLE);
+ telephonyMgr.createForSubscriptionId(subId).setRadioEnabled(!ENABLE);
}
private static void onEnableRadio(Intent intent, Context context) {
@@ -162,7 +163,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onEnableRadio subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionSetRadioEnabled(subId, ENABLE);
+ telephonyMgr.createForSubscriptionId(subId).setRadioEnabled(ENABLE);
}
private static void onShowCaptivePortalNotification(Intent intent, Context context) {
@@ -205,7 +206,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onResetAllCarrierActions subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionResetAll(subId);
+ telephonyMgr.createForSubscriptionId(subId).resetAllCarrierActions();
}
private static Notification getNotification(Context context, int titleId, int textId,
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ade292f..f40105d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -598,6 +598,8 @@
<string name="bluetooth_show_devices_without_names">Show Bluetooth devices without names</string>
<!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
<string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
+ <!-- Setting Checkbox title for enabling Bluetooth Gabeldorsche. [CHAR LIMIT=40] -->
+ <string name="bluetooth_enable_gabeldorsche">Enable Gabeldorsche</string>
<!-- UI debug setting: Select Bluetooth AVRCP Version -->
<string name="bluetooth_select_avrcp_version_string">Bluetooth AVRCP Version</string>
@@ -696,6 +698,8 @@
<string name="bluetooth_show_devices_without_names_summary">Bluetooth devices without names (MAC addresses only) will be displayed</string>
<!-- Summary of checkbox for disabling Bluetooth absolute volume -->
<string name="bluetooth_disable_absolute_volume_summary">Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.</string>
+ <!-- Summary of checkbox for enabling Bluetooth Gabeldorche features [CHAR LIMIT=none] -->
+ <string name="bluetooth_enable_gabeldorsche_summary">Enables the Bluetooth Gabeldorche feature stack.</string>
<!-- Title of checkbox setting that enables the terminal app. [CHAR LIMIT=32] -->
<string name="enable_terminal_title">Local terminal</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index 9c896c8..bc03c34 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -164,13 +164,13 @@
}
}
- boolean isA2dpPlaying() {
+ boolean isAudioPlaying() {
if (mService == null) {
return false;
}
List<BluetoothDevice> srcs = mService.getConnectedDevices();
if (!srcs.isEmpty()) {
- if (mService.isA2dpPlaying(srcs.get(0))) {
+ if (mService.isAudioPlaying(srcs.get(0))) {
return true;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 8f40ab4..80b03a4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -175,7 +175,7 @@
return;
}
A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
- if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
+ if ((a2dpSink != null) && (a2dpSink.isAudioPlaying())) {
return;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index d91226e..3f920a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothPbap;
@@ -52,14 +53,16 @@
// These callbacks run on the main thread.
private final class PbapServiceListener
- implements BluetoothPbap.ServiceListener {
+ implements BluetoothProfile.ServiceListener {
- public void onServiceConnected(BluetoothPbap proxy) {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
mService = (BluetoothPbap) proxy;
mIsProfileReady=true;
}
- public void onServiceDisconnected() {
+ @Override
+ public void onServiceDisconnected(int profile) {
mIsProfileReady=false;
}
}
@@ -74,7 +77,8 @@
}
PbapServerProfile(Context context) {
- BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener());
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new PbapServiceListener(),
+ BluetoothProfile.PBAP);
}
public boolean accessProfileEnabled() {
@@ -97,13 +101,8 @@
}
public int getConnectionStatus(BluetoothDevice device) {
- if (mService == null) {
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- if (mService.isConnected(device))
- return BluetoothProfile.STATE_CONNECTED;
- else
- return BluetoothProfile.STATE_DISCONNECTED;
+ if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+ return mService.getConnectionState(device);
}
public boolean isPreferred(BluetoothDevice device) {
@@ -142,7 +141,8 @@
Log.d(TAG, "finalize()");
if (mService != null) {
try {
- mService.close();
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PBAP,
+ mService);
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up PBAP proxy", t);
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 104cc8f..d3315ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -235,7 +235,7 @@
public final CharSequence contentDescription;
public final long requestFinishTime;
- private Request(String packageName, UserHandle userHandle, Drawable icon,
+ public Request(String packageName, UserHandle userHandle, Drawable icon,
CharSequence label, boolean isHighBattery, CharSequence contentDescription,
long requestFinishTime) {
this.packageName = packageName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 22f47f1..440315f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -152,6 +152,14 @@
// TODO(b/70983952): Fill this method in
}
+ /**
+ * Result of the sign-in request indecated by the WifiEntry.SIGNIN_STATUS constants
+ */
+ public void onSignInResult(int status) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+
private void updateIcon(int level) {
if (level == -1) {
setIcon(null);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 76e7ab4..22d843b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -142,6 +142,8 @@
Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST,
Settings.Secure.SKIP_DIRECTION,
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
Settings.Secure.NAVIGATION_MODE,
Settings.Secure.SKIP_GESTURE_COUNT,
Settings.Secure.SKIP_TOUCH_COUNT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index ae07598..4b10557 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -216,6 +216,10 @@
VALIDATORS.put(Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, JSON_OBJECT_VALIDATOR);
VALIDATORS.put(
Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
+ VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
+ VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+ new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
VALIDATORS.put(Secure.AWARE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SKIP_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.SKIP_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 80077c8..016896f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2191,6 +2191,14 @@
Settings.Secure.NAVIGATION_MODE,
SecureSettingsProto.NAVIGATION_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_LEFT);
+
+ dumpSetting(s, p,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+ SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_RIGHT);
+
final long nfcPaymentToken = p.start(SecureSettingsProto.NFC_PAYMENT);
dumpSetting(s, p,
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 4a10e85..5d0db01 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -734,7 +734,8 @@
Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
Settings.Secure.FACE_UNLOCK_RE_ENROLL,
- Settings.Secure.TAP_GESTURE);
+ Settings.Secure.TAP_GESTURE,
+ Settings.Secure.WINDOW_MAGNIFICATION);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3514704..a8318d6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -28,11 +28,6 @@
android:glEsVersion="0x00020000"
android:required="true" />
- <!-- SysUI must be the one to define this permission; its name is
- referenced by the core OS. -->
- <permission android:name="android.permission.systemui.IDENTITY"
- android:protectionLevel="signature" />
-
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Used to read wallpaper -->
diff --git a/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml b/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml
new file mode 100644
index 0000000..80ce8c1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector android:height="96dp" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="96dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#757575" android:pathData="M15.54,5.54L13.77,7.3 12,5.54 10.23,7.3 8.46,5.54 12,2zM18.46,15.54l-1.76,-1.77L18.46,12l-1.76,-1.77 1.76,-1.77L22,12zM8.46,18.46l1.77,-1.76L12,18.46l1.77,-1.76 1.77,1.76L12,22zM5.54,8.46l1.76,1.77L5.54,12l1.76,1.77 -1.76,1.77L2,12z"/>
+ <path android:fillColor="#757575" android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/magnifier_controllers.xml b/packages/SystemUI/res/layout/magnifier_controllers.xml
new file mode 100644
index 0000000..0203cd4
--- /dev/null
+++ b/packages/SystemUI/res/layout/magnifier_controllers.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/magnification_controls_size"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_height="@dimen/magnification_controls_size"
+ android:gravity="center">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:focusable="true"
+ android:id="@+id/controller"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_control_magnification_grey" />
+
+ <View
+ android:id="@+id/left_control"
+ android:layout_width="@dimen/magnifier_left_right_controls_width"
+ android:layout_height="@dimen/magnifier_left_right_controls_height"
+ android:layout_alignLeft="@+id/controller"
+ android:layout_centerVertical="true" />
+
+ <View
+ android:id="@+id/up_control"
+ android:layout_width="@dimen/magnifier_up_down_controls_width"
+ android:layout_height="@dimen/magnifier_up_down_controls_height"
+ android:layout_alignTop="@+id/controller"
+ android:layout_centerHorizontal="true" />
+
+ <View
+ android:id="@+id/right_control"
+ android:layout_width="@dimen/magnifier_left_right_controls_width"
+ android:layout_height="@dimen/magnifier_left_right_controls_height"
+ android:layout_alignRight="@+id/controller"
+ android:layout_centerVertical="true" />
+
+ <View
+ android:id="@+id/down_control"
+ android:layout_width="@dimen/magnifier_up_down_controls_width"
+ android:layout_height="@dimen/magnifier_up_down_controls_height"
+ android:layout_alignBottom="@+id/controller"
+ android:layout_centerHorizontal="true" />
+ </RelativeLayout>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
new file mode 100644
index 0000000..f818612
--- /dev/null
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <SurfaceView
+ android:layout_marginStart="@dimen/magnification_border_size"
+ android:layout_marginTop="@dimen/magnification_border_size"
+ android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <View
+ android:id="@+id/left_handle"
+ android:layout_width="@dimen/magnification_border_size"
+ android:layout_height="match_parent"
+ android:layout_above="@+id/drag_handle"
+ android:background="@color/magnification_border_color" />
+
+ <View
+ android:id="@+id/top_handle"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/magnification_border_size"
+ android:background="@color/magnification_border_color" />
+
+ <View
+ android:id="@+id/right_handle"
+ android:layout_width="@dimen/magnification_border_size"
+ android:layout_height="match_parent"
+ android:layout_above="@+id/drag_handle"
+ android:layout_alignParentEnd="true"
+ android:background="@color/magnification_border_color" />
+
+ <View
+ android:id="@+id/bottom_handle"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/magnification_border_size"
+ android:layout_above="@+id/drag_handle"
+ android:background="@color/magnification_border_color" />
+
+ <View
+ android:id="@+id/drag_handle"
+ android:layout_width="@dimen/magnification_drag_view_width"
+ android:layout_height="@dimen/magnification_drag_view_height"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true"
+ android:background="@color/magnification_border_color" />
+
+ </RelativeLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 92c7477..c142465 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -211,4 +211,5 @@
<color name="GM2_green_500">#FF34A853</color>
<color name="GM2_blue_500">#FF4285F4</color>
+ <color name="magnification_border_color">#FF9900</color>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e896c16..640f31b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -295,6 +295,7 @@
<item>com.android.systemui.SizeCompatModeActivityController</item>
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
<item>com.android.systemui.theme.ThemeOverlayController</item>
+ <item>com.android.systemui.accessibility.WindowMagnification</item>
</string-array>
<!-- SystemUI vender service, used in config_systemUIServiceComponents. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c948116..da0323a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1139,4 +1139,16 @@
<dimen name="qs_media_width">350dp</dimen>
<dimen name="qs_media_padding">8dp</dimen>
<dimen name="qs_media_corner_radius">10dp</dimen>
+
+ <dimen name="magnification_border_size">5dp</dimen>
+ <dimen name="magnification_frame_move_short">5dp</dimen>
+ <dimen name="magnification_frame_move_long">25dp</dimen>
+ <dimen name="magnification_drag_view_width">100dp</dimen>
+ <dimen name="magnification_drag_view_height">35dp</dimen>
+ <dimen name="magnification_controls_size">90dp</dimen>
+ <dimen name="magnifier_left_right_controls_width">35dp</dimen>
+ <dimen name="magnifier_left_right_controls_height">45dp</dimen>
+ <dimen name="magnifier_up_down_controls_width">45dp</dimen>
+ <dimen name="magnifier_up_down_controls_height">40dp</dimen>
+
</resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index deae7e2..c1cf7b4 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -32,4 +32,6 @@
<!-- Ratio of "right" end of status bar that will swipe to QS. -->
<integer name="qs_split_fraction">2</integer>
+ <integer name="magnification_default_scale">2</integer>
+
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1053750..bc808b3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2492,4 +2492,12 @@
<!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] -->
<string name="inattentive_sleep_warning_title">Standby</string>
+
+ <!-- Window Magnification strings -->
+ <!-- Title for Magnification Overlay Window [CHAR LIMIT=NONE] -->
+ <string name="magnification_overlay_title">Magnification Overlay Window</string>
+ <!-- Title for Magnification Window [CHAR LIMIT=NONE] -->
+ <string name="magnification_window_title">Magnification Window</string>
+ <!-- Title for Magnification Controls Window [CHAR LIMIT=NONE] -->
+ <string name="magnification_controls_title">Magnification Window Controls</string>
</resources>
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 22d1675c..9f13718 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
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -215,4 +216,23 @@
public void removePinnedStackListener(PinnedStackListener listener) {
mPinnedStackListenerForwarder.removeListener(listener);
}
+
+ /**
+ * Mirrors a specified display. The SurfaceControl returned is the root of the mirrored
+ * hierarchy.
+ *
+ * @param displayId The id of the display to mirror
+ * @return The SurfaceControl for the root of the mirrored hierarchy.
+ */
+ public SurfaceControl mirrorDisplay(final int displayId) {
+ try {
+ SurfaceControl outSurfaceControl = new SurfaceControl();
+ WindowManagerGlobal.getWindowManagerService().mirrorDisplay(displayId,
+ outSurfaceControl);
+ return outSurfaceControl;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reach window manager", e);
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 5d084e7..c2cedad 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -30,6 +30,7 @@
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
@@ -114,11 +115,7 @@
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- takeEmergencyCallAction();
- }
- });
+ setOnClickListener(v -> takeEmergencyCallAction());
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
@@ -168,9 +165,9 @@
*/
public void takeEmergencyCallAction() {
MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
- // TODO: implement a shorter timeout once new PowerManager API is ready.
- // should be the equivalent to the old userActivity(EMERGENCY_CALL_TIMEOUT)
- mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ if (mPowerManager != null) {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ }
try {
ActivityTaskManager.getService().stopSystemLockTaskMode();
} catch (RemoteException e) {
@@ -182,10 +179,19 @@
mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
}
} else {
- Dependency.get(KeyguardUpdateMonitor.class).reportEmergencyCallAction(
- true /* bypassHandler */);
+ KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+ if (updateMonitor != null) {
+ updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+ } else {
+ Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
+ }
+ TelecomManager telecomManager = getTelecommManager();
+ if (telecomManager == null) {
+ Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+ return;
+ }
Intent emergencyDialIntent =
- getTelecommManager().createLaunchEmergencyDialerIntent(null /* number*/)
+ telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_CLEAR_TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
new file mode 100644
index 0000000..6178ff2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.MainHandler;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Class to handle changes to setting window_magnification value.
+ */
+@Singleton
+public class WindowMagnification extends SystemUI {
+ private WindowMagnificationController mWindowMagnificationController;
+ private final Handler mHandler;
+
+ @Inject
+ public WindowMagnification(Context context, @MainHandler Handler mainHandler) {
+ super(context);
+ mHandler = mainHandler;
+ }
+
+ @Override
+ public void start() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.WINDOW_MAGNIFICATION),
+ true, new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateWindowMagnification();
+ }
+ });
+ }
+
+ private void updateWindowMagnification() {
+ try {
+ boolean enable = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.WINDOW_MAGNIFICATION) != 0;
+ if (enable) {
+ enableMagnification();
+ } else {
+ disableMagnification();
+ }
+ } catch (Settings.SettingNotFoundException e) {
+ disableMagnification();
+ }
+ }
+
+ private void enableMagnification() {
+ if (mWindowMagnificationController == null) {
+ mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler);
+ }
+ mWindowMagnificationController.createWindowMagnification();
+ }
+
+ private void disableMagnification() {
+ if (mWindowMagnificationController != null) {
+ mWindowMagnificationController.deleteWindowMagnification();
+ }
+ mWindowMagnificationController = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
new file mode 100644
index 0000000..e3694ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+/**
+ * Class to handle adding and removing a window magnification.
+ */
+public class WindowMagnificationController implements View.OnClickListener,
+ View.OnLongClickListener, View.OnTouchListener, SurfaceHolder.Callback {
+ private final int mBorderSize;
+ private final int mMoveFrameAmountShort;
+ private final int mMoveFrameAmountLong;
+
+ private final Context mContext;
+ private final Point mDisplaySize = new Point();
+ private final int mDisplayId;
+ private final Handler mHandler;
+ private final Rect mMagnificationFrame = new Rect();
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ private final WindowManager mWm;
+
+ private float mScale;
+
+ private final Rect mTmpRect = new Rect();
+
+ // The root of the mirrored content
+ private SurfaceControl mMirrorSurface;
+
+ private boolean mIsPressedDown;
+
+ private View mLeftControl;
+ private View mUpControl;
+ private View mRightControl;
+ private View mBottomControl;
+
+ private View mDragView;
+ private View mLeftDrag;
+ private View mTopDrag;
+ private View mRightDrag;
+ private View mBottomDrag;
+
+ private final PointF mLastDrag = new PointF();
+ private final Point mMoveWindowOffset = new Point();
+
+ private View mMirrorView;
+ private SurfaceView mMirrorSurfaceView;
+ private View mControlsView;
+ private View mOverlayView;
+
+ private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable();
+
+ WindowMagnificationController(Context context, Handler handler) {
+ mContext = context;
+ mHandler = handler;
+ Display display = mContext.getDisplay();
+ display.getSize(mDisplaySize);
+ mDisplayId = mContext.getDisplayId();
+
+ mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+ Resources r = context.getResources();
+ mBorderSize = (int) r.getDimension(R.dimen.magnification_border_size);
+ mMoveFrameAmountShort = (int) r.getDimension(R.dimen.magnification_frame_move_short);
+ mMoveFrameAmountLong = (int) r.getDimension(R.dimen.magnification_frame_move_long);
+
+ mScale = r.getInteger(R.integer.magnification_default_scale);
+ }
+
+ /**
+ * Creates a magnification window if it doesn't already exist.
+ */
+ void createWindowMagnification() {
+ if (mMirrorView != null) {
+ return;
+ }
+ createOverlayWindow();
+ }
+
+ private void createOverlayWindow() {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.gravity = Gravity.TOP | Gravity.LEFT;
+ params.token = new Binder();
+ params.setTitle(mContext.getString(R.string.magnification_overlay_title));
+
+ mOverlayView = new View(mContext);
+ mOverlayView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mOverlayView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ createMirrorWindow();
+ createControls();
+ }
+
+ @Override
+ public void onWindowDetached() {
+
+ }
+ });
+
+ mOverlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+
+ mWm.addView(mOverlayView, params);
+ }
+
+ /**
+ * Deletes the magnification window.
+ */
+ void deleteWindowMagnification() {
+ if (mMirrorSurface != null) {
+ mTransaction.remove(mMirrorSurface).apply();
+ mMirrorSurface = null;
+ }
+
+ if (mOverlayView != null) {
+ mWm.removeView(mOverlayView);
+ mOverlayView = null;
+ }
+
+ if (mMirrorView != null) {
+ mWm.removeView(mMirrorView);
+ mMirrorView = null;
+ }
+
+ if (mControlsView != null) {
+ mWm.removeView(mControlsView);
+ mControlsView = null;
+ }
+ }
+
+ private void createMirrorWindow() {
+ setInitialStartBounds();
+
+ // The window should be the size the mirrored surface will be but also add room for the
+ // border and the drag handle.
+ int dragViewHeight = (int) mContext.getResources().getDimension(
+ R.dimen.magnification_drag_view_height);
+ int windowWidth = mMagnificationFrame.width() + 2 * mBorderSize;
+ int windowHeight = mMagnificationFrame.height() + dragViewHeight + 2 * mBorderSize;
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ windowWidth, windowHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.gravity = Gravity.TOP | Gravity.LEFT;
+ params.token = mOverlayView.getWindowToken();
+ params.x = mMagnificationFrame.left;
+ params.y = mMagnificationFrame.top;
+ params.setTitle(mContext.getString(R.string.magnification_window_title));
+
+ mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
+ mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
+ // This places the SurfaceView's SurfaceControl above the ViewRootImpl's SurfaceControl to
+ // ensure the mirrored area can get touch instead of going to the window
+ mMirrorSurfaceView.setZOrderOnTop(true);
+
+ mMirrorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+ mWm.addView(mMirrorView, params);
+
+ SurfaceHolder holder = mMirrorSurfaceView.getHolder();
+ holder.addCallback(this);
+ holder.setFormat(PixelFormat.RGBA_8888);
+
+ addDragTouchListeners();
+ }
+
+ private void createControls() {
+ int controlsSize = (int) mContext.getResources().getDimension(
+ R.dimen.magnification_controls_size);
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(controlsSize, controlsSize,
+ WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.RGBA_8888);
+ lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ lp.token = mOverlayView.getWindowToken();
+ lp.setTitle(mContext.getString(R.string.magnification_controls_title));
+
+ mControlsView = LayoutInflater.from(mContext).inflate(R.layout.magnifier_controllers, null);
+ mWm.addView(mControlsView, lp);
+
+ mLeftControl = mControlsView.findViewById(R.id.left_control);
+ mUpControl = mControlsView.findViewById(R.id.up_control);
+ mRightControl = mControlsView.findViewById(R.id.right_control);
+ mBottomControl = mControlsView.findViewById(R.id.down_control);
+
+ mLeftControl.setOnClickListener(this);
+ mUpControl.setOnClickListener(this);
+ mRightControl.setOnClickListener(this);
+ mBottomControl.setOnClickListener(this);
+
+ mLeftControl.setOnLongClickListener(this);
+ mUpControl.setOnLongClickListener(this);
+ mRightControl.setOnLongClickListener(this);
+ mBottomControl.setOnLongClickListener(this);
+
+ mLeftControl.setOnTouchListener(this);
+ mUpControl.setOnTouchListener(this);
+ mRightControl.setOnTouchListener(this);
+ mBottomControl.setOnTouchListener(this);
+ }
+
+ private void setInitialStartBounds() {
+ // Sets the initial frame area for the mirror and places it in the center of the display.
+ int initSize = Math.min(mDisplaySize.x, mDisplaySize.y) / 2;
+ int initX = mDisplaySize.x / 2 - initSize / 2;
+ int initY = mDisplaySize.y / 2 - initSize / 2;
+ mMagnificationFrame.set(initX, initY, initX + initSize, initY + initSize);
+ }
+
+ /**
+ * This is called once the surfaceView is created so the mirrored content can be placed as a
+ * child of the surfaceView.
+ */
+ private void createMirror() {
+ mMirrorSurface = WindowManagerWrapper.getInstance().mirrorDisplay(mDisplayId);
+ if (!mMirrorSurface.isValid()) {
+ return;
+ }
+ mTransaction.show(mMirrorSurface)
+ .reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl());
+
+ modifyWindowMagnification(mTransaction);
+ mTransaction.apply();
+ }
+
+ private void addDragTouchListeners() {
+ mDragView = mMirrorView.findViewById(R.id.drag_handle);
+ mLeftDrag = mMirrorView.findViewById(R.id.left_handle);
+ mTopDrag = mMirrorView.findViewById(R.id.top_handle);
+ mRightDrag = mMirrorView.findViewById(R.id.right_handle);
+ mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
+
+ mDragView.setOnTouchListener(this);
+ mLeftDrag.setOnTouchListener(this);
+ mTopDrag.setOnTouchListener(this);
+ mRightDrag.setOnTouchListener(this);
+ mBottomDrag.setOnTouchListener(this);
+ }
+
+ /**
+ * Modifies the placement of the mirrored content.
+ */
+ private void modifyWindowMagnification(SurfaceControl.Transaction t) {
+ Rect sourceBounds = getSourceBounds(mMagnificationFrame, mScale);
+ // The final destination for the magnification surface should be at 0,0 since the
+ // ViewRootImpl's position will change
+ mTmpRect.set(0, 0, mMagnificationFrame.width(), mMagnificationFrame.height());
+
+ WindowManager.LayoutParams params =
+ (WindowManager.LayoutParams) mMirrorView.getLayoutParams();
+ params.x = mMagnificationFrame.left;
+ params.y = mMagnificationFrame.top;
+ mWm.updateViewLayout(mMirrorView, params);
+
+ t.setGeometry(mMirrorSurface, sourceBounds, mTmpRect, Surface.ROTATION_0);
+ }
+
+ @Override
+ public void onClick(View v) {
+ setMoveOffset(v, mMoveFrameAmountShort);
+ moveMirrorFromControls();
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ mIsPressedDown = true;
+ setMoveOffset(v, mMoveFrameAmountLong);
+ mHandler.post(mMoveMirrorRunnable);
+ return true;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (v == mLeftControl || v == mUpControl || v == mRightControl || v == mBottomControl) {
+ return handleControlTouchEvent(event);
+ } else if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
+ || v == mBottomDrag) {
+ return handleDragTouchEvent(event);
+ }
+ return false;
+ }
+
+ private boolean handleControlTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mIsPressedDown = false;
+ break;
+ }
+ return false;
+ }
+
+ private boolean handleDragTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLastDrag.set(event.getRawX(), event.getRawY());
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ int xDiff = (int) (event.getRawX() - mLastDrag.x);
+ int yDiff = (int) (event.getRawY() - mLastDrag.y);
+ mMagnificationFrame.offset(xDiff, yDiff);
+ mLastDrag.set(event.getRawX(), event.getRawY());
+ modifyWindowMagnification(mTransaction);
+ mTransaction.apply();
+ return true;
+ }
+ return false;
+ }
+
+ private void setMoveOffset(View v, int moveFrameAmount) {
+ mMoveWindowOffset.set(0, 0);
+
+ if (v == mLeftControl) {
+ mMoveWindowOffset.x = -moveFrameAmount;
+ } else if (v == mUpControl) {
+ mMoveWindowOffset.y = -moveFrameAmount;
+ } else if (v == mRightControl) {
+ mMoveWindowOffset.x = moveFrameAmount;
+ } else if (v == mBottomControl) {
+ mMoveWindowOffset.y = moveFrameAmount;
+ }
+ }
+
+ private void moveMirrorFromControls() {
+ mMagnificationFrame.offset(mMoveWindowOffset.x, mMoveWindowOffset.y);
+
+ modifyWindowMagnification(mTransaction);
+ mTransaction.apply();
+ }
+
+ /**
+ * Calculates the desired source bounds. This will be the area under from the center of the
+ * displayFrame, factoring in scale.
+ */
+ private Rect getSourceBounds(Rect displayFrame, float scale) {
+ int halfWidth = displayFrame.width() / 2;
+ int halfHeight = displayFrame.height() / 2;
+ int left = displayFrame.left + (halfWidth - (int) (halfWidth / scale));
+ int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
+ int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
+ int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
+ return new Rect(left, top, right, bottom);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ createMirror();
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+
+ class MoveMirrorRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (mIsPressedDown) {
+ moveMirrorFromControls();
+ mHandler.postDelayed(mMoveMirrorRunnable, 100);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 1277736..c82bc30 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -103,7 +103,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -735,7 +734,7 @@
@SuppressWarnings("FieldCanBeLocal")
private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
@Override
- public void onPendingEntryAdded(NotificationEntry entry) {
+ public void onNotificationAdded(NotificationEntry entry) {
boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 21471ec..4c1cf49 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -32,6 +32,9 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcelable;
@@ -39,8 +42,11 @@
import android.provider.Settings;
import android.util.Log;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleHubNotificationListenerKt;
import java.util.ArrayList;
import java.util.Arrays;
@@ -227,15 +233,29 @@
List<Person> personList = getPeopleFromNotification(entry);
if (personList.size() > 0) {
final Person person = personList.get(0);
-
if (person != null) {
icon = person.getIcon();
+ if (icon == null) {
+ // Lets try and grab the icon constructed by the layout
+ Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
+ if (d instanceof BitmapDrawable) {
+ icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+ }
+ }
}
}
if (icon == null) {
- icon = notification.getLargeIcon() != null
- ? notification.getLargeIcon()
- : notification.getSmallIcon();
+ boolean shouldTint = notification.getLargeIcon() == null;
+ icon = shouldTint
+ ? notification.getSmallIcon()
+ : notification.getLargeIcon();
+ if (shouldTint) {
+ int notifColor = entry.getSbn().getNotification().color;
+ notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
+ notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
+ true /* findFg */, 3f);
+ icon.setTint(notifColor);
+ }
}
if (intent != null) {
return new Notification.BubbleMetadata.Builder()
@@ -285,7 +305,7 @@
}
static boolean isShortcutIntent(PendingIntent intent) {
- return intent.equals(sDummyShortcutIntent);
+ return intent != null && intent.equals(sDummyShortcutIntent);
}
static List<Person> getPeopleFromNotification(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 3cf14d6..99dd5e2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -21,6 +21,7 @@
import com.android.systemui.SizeCompatModeActivityController;
import com.android.systemui.SliceBroadcastRelayHandler;
import com.android.systemui.SystemUI;
+import com.android.systemui.accessibility.WindowMagnification;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -156,4 +157,10 @@
@IntoMap
@ClassKey(VolumeUI.class)
public abstract SystemUI bindVolumeUI(VolumeUI sysui);
+
+ /** Inject into WindowMagnification. */
+ @Binds
+ @IntoMap
+ @ClassKey(WindowMagnification.class)
+ public abstract SystemUI bindWindowMagnification(WindowMagnification sysui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 05a234f..d008e66 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -80,8 +80,7 @@
public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
- Callback callback, Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy,
- DozeLog dozeLog) {
+ Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) {
mContext = context;
mAlarmManager = alarmManager;
mSensorManager = sensorManager;
@@ -154,7 +153,7 @@
};
mProximitySensor = new ProximitySensor(context.getResources(), sensorManager);
-
+ setProxListening(false); // Don't immediately start listening when we register.
mProximitySensor.register(
proximityEvent -> {
if (proximityEvent != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 2d6b946..722dc03 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -103,8 +103,7 @@
mWakeLock = wakeLock;
mAllowPulseTriggers = allowPulseTriggers;
mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
- config, wakeLock, this::onSensor, this::onProximityFar,
- dozeParameters.getPolicy(), dozeLog);
+ config, wakeLock, this::onSensor, this::onProximityFar, dozeLog);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 9f79785..077d260 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -97,7 +98,7 @@
if (!mReceiverRegistered) {
mCurrentUserId = ActivityManager.getCurrentUser();
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, filter);
+ mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
mReceiverRegistered = true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index 9ae3882..ec1efa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -24,6 +24,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
/**
* Represents a set of grouped notifications. The final notification list is usually a mix of
@@ -57,15 +58,22 @@
@VisibleForTesting
public void setSummary(@Nullable NotificationEntry summary) {
- mSummary = summary;
+ if (!Objects.equals(mSummary, summary)) {
+ mSummary = summary;
+ onGroupingUpdated();
+ }
}
void clearChildren() {
- mChildren.clear();
+ if (mChildren.size() != 0) {
+ mChildren.clear();
+ onGroupingUpdated();
+ }
}
void addChild(NotificationEntry child) {
mChildren.add(child);
+ onGroupingUpdated();
}
void sortChildren(Comparator<? super NotificationEntry> c) {
@@ -77,4 +85,5 @@
}
public static final GroupEntry ROOT_ENTRY = new GroupEntry("<root>");
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 6ce7fd9..052473a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -19,6 +19,14 @@
import android.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.notification.collection.provider.DerivedMember;
+import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
/**
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
@@ -26,14 +34,23 @@
*/
public abstract class ListEntry {
private final String mKey;
+ private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider();
+ private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider);
@Nullable private GroupEntry mParent;
@Nullable private GroupEntry mPreviousParent;
private int mSection;
int mFirstAddedIteration = -1;
+ // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
+ // replaced in GroupEntry and NotifListBuilderImpl
+ private final NotificationGroupManager mGroupManager;
+
ListEntry(String key) {
mKey = key;
+
+ // TODO: (b/145659174) remove
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
}
public String getKey() {
@@ -53,7 +70,11 @@
@VisibleForTesting
public void setParent(@Nullable GroupEntry parent) {
- mParent = parent;
+ if (!Objects.equals(mParent, parent)) {
+ invalidateParent();
+ mParent = parent;
+ onGroupingUpdated();
+ }
}
@Nullable public GroupEntry getPreviousParent() {
@@ -72,4 +93,58 @@
void setSection(int section) {
mSection = section;
}
+
+ /**
+ * Resets the cached values of DerivedMembers.
+ */
+ void invalidateDerivedMembers() {
+ for (int i = 0; i < mDerivedMemberList.size(); i++) {
+ mDerivedMemberList.get(i).invalidate();
+ }
+ }
+
+ /**
+ * Whether this notification is shown to the user as a high priority notification: visible on
+ * the lock screen/status bar and in the top section in the shade.
+ */
+ public boolean isHighPriority() {
+ return mIsHighPriorityProvider.get(this);
+ }
+
+ private void invalidateParent() {
+ // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children
+ if (getParent() != null) {
+ getParent().invalidateDerivedMembers();
+ }
+
+ // TODO: (b/145659174) remove
+ final NotificationEntry notifEntry = getRepresentativeEntry();
+ if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) {
+ NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn());
+ if (summary != null) {
+ summary.invalidateDerivedMembers();
+ }
+ }
+ }
+
+ void onGroupingUpdated() {
+ for (int i = 0; i < mDerivedMemberList.size(); i++) {
+ mDerivedMemberList.get(i).onGroupingUpdated();
+ }
+ invalidateParent();
+ }
+
+ void onSbnUpdated() {
+ for (int i = 0; i < mDerivedMemberList.size(); i++) {
+ mDerivedMemberList.get(i).onSbnUpdated();
+ }
+ invalidateParent();
+ }
+
+ void onRankingUpdated() {
+ for (int i = 0; i < mDerivedMemberList.size(); i++) {
+ mDerivedMemberList.get(i).onRankingUpdated();
+ }
+ invalidateParent();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 232fb6d..de16ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -21,7 +21,6 @@
import static android.app.Notification.CATEGORY_EVENT;
import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.Notification.EXTRA_MESSAGES;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
@@ -42,7 +41,6 @@
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Bundle;
-import android.os.Parcelable;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
@@ -92,7 +90,6 @@
private StatusBarNotification mSbn;
private Ranking mRanking;
-
/*
* Bookkeeping members
*/
@@ -120,7 +117,6 @@
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
public CharSequence remoteInputText;
- private final List<Person> mAssociatedPeople = new ArrayList<>();
private Notification.BubbleMetadata mBubbleMetadata;
/**
@@ -157,12 +153,6 @@
*/
private boolean hasSentReply;
- /**
- * Whether this notification is shown to the user as a high priority notification: visible on
- * the lock screen/status bar and in the top section in the shade.
- */
- private boolean mHighPriority;
-
private boolean mSensitive = true;
private Runnable mOnSensitiveChangedListener;
private boolean mAutoHeadsUp;
@@ -212,9 +202,11 @@
+ " doesn't match existing key " + mKey);
}
- mSbn = sbn;
- mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
- updatePeopleList();
+ if (!Objects.equals(mSbn, sbn)) {
+ mSbn = sbn;
+ mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
+ onSbnUpdated();
+ }
}
/**
@@ -239,10 +231,12 @@
+ " doesn't match existing key " + mKey);
}
- mRanking = ranking;
+ if (!Objects.equals(mRanking, ranking)) {
+ mRanking = ranking;
+ onRankingUpdated();
+ }
}
-
/*
* Convenience getters for SBN and Ranking members
*/
@@ -304,49 +298,10 @@
return interruption;
}
- public boolean isHighPriority() {
- return mHighPriority;
- }
-
- public void setIsHighPriority(boolean highPriority) {
- this.mHighPriority = highPriority;
- }
-
public boolean isBubble() {
return (mSbn.getNotification().flags & FLAG_BUBBLE) != 0;
}
- private void updatePeopleList() {
- mAssociatedPeople.clear();
-
- Bundle extras = mSbn.getNotification().extras;
- if (extras == null) {
- return;
- }
-
- List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
-
- if (p != null) {
- mAssociatedPeople.addAll(p);
- }
-
- if (Notification.MessagingStyle.class.equals(
- mSbn.getNotification().getNotificationStyle())) {
- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (!ArrayUtils.isEmpty(messages)) {
- for (Notification.MessagingStyle.Message message :
- Notification.MessagingStyle.Message
- .getMessagesFromBundleArray(messages)) {
- mAssociatedPeople.add(message.getSenderPerson());
- }
- }
- }
- }
-
- boolean hasAssociatedPeople() {
- return mAssociatedPeople.size() > 0;
- }
-
/**
* Returns the data needed for a bubble for this notification, if it exists.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 48a4882..7010943 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -16,14 +16,11 @@
package com.android.systemui.statusbar.notification.collection
-import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
-import android.app.NotificationManager.IMPORTANCE_LOW
import android.app.NotificationManager.IMPORTANCE_MIN
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.NotificationListenerService.RankingMap
import android.service.notification.StatusBarNotification
-import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationFilter
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
@@ -105,44 +102,6 @@
return entry.key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN
}
- @VisibleForTesting
- protected fun isHighPriority(entry: NotificationEntry): Boolean {
- if (entry.importance >= IMPORTANCE_DEFAULT ||
- hasHighPriorityCharacteristics(entry)) {
- return true
- }
-
- if (groupManager.isSummaryOfGroup(entry.sbn)) {
- val logicalChildren = groupManager.getLogicalChildren(entry.sbn)
- for (child in logicalChildren) {
- if (isHighPriority(child)) {
- return true
- }
- }
- }
-
- return false
- }
-
- private fun hasHighPriorityCharacteristics(entry: NotificationEntry): Boolean {
- val c = entry.channel
- val n = entry.sbn.notification
-
- if ((n.isForegroundService && entry.ranking.importance >= IMPORTANCE_LOW) ||
- n.hasMediaSession() ||
- entry.isPeopleNotification()) {
- // Users who have long pressed and demoted to silent should not see the notification
- // in the top section
- if (c != null && c.hasUserSetImportance()) {
- return false
- }
-
- return true
- }
-
- return false
- }
-
fun updateRanking(
newRankingMap: RankingMap?,
entries: Collection<NotificationEntry>,
@@ -219,7 +178,10 @@
// TODO: notify group manager here?
groupManager.onEntryUpdated(entry, oldSbn)
}
- entry.setIsHighPriority(isHighPriority(entry))
+
+ // TODO: (b/145659174) remove after moving to new NotifPipeline
+ // (should be able to remove all groupManager code post-migration)
+ entry.invalidateDerivedMembers()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 4413dc4..232246e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -37,6 +37,8 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
@@ -133,8 +135,8 @@
// to be shown on the lockscreen
// TODO: grouping hasn't happened yet (b/145134683)
if (entry.getParent() != null) {
- final NotificationEntry summary = entry.getParent().getRepresentativeEntry();
- if (priorityExceedsLockscreenShowingThreshold(summary)) {
+ final GroupEntry parent = entry.getParent();
+ if (priorityExceedsLockscreenShowingThreshold(parent)) {
return false;
}
}
@@ -144,7 +146,7 @@
}
};
- private boolean priorityExceedsLockscreenShowingThreshold(NotificationEntry entry) {
+ private boolean priorityExceedsLockscreenShowingThreshold(ListEntry entry) {
if (entry == null) {
return false;
}
@@ -154,7 +156,7 @@
// correctly updated before reaching this point (b/145134683)
return entry.isHighPriority();
} else {
- return !entry.getRanking().isAmbient();
+ return !entry.getRepresentativeEntry().getRanking().isAmbient();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
new file mode 100644
index 0000000..815e6f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider;
+/**
+ * Caches a computed value until invalidate() is called
+ * @param <Parent> Object used to computeValue
+ * @param <Value> type of value to cache until invalidate is called
+ */
+public abstract class DerivedMember<Parent, Value> {
+ private Value mValue;
+ protected abstract Value computeValue(Parent parent);
+
+ /**
+ * Gets the last cached value, else recomputes the value.
+ */
+ public Value get(Parent parent) {
+ if (mValue == null) {
+ mValue = computeValue(parent);
+ }
+ return mValue;
+ }
+
+ /**
+ * Resets the cached value.
+ * Next time "get" is called, the value is recomputed.
+ */
+ public void invalidate() {
+ mValue = null;
+ }
+
+ /**
+ * Called when a NotificationEntry's status bar notification has updated.
+ * Derived members can invalidate here.
+ */
+ public void onSbnUpdated() {}
+
+ /**
+ * Called when a NotificationEntry's Ranking has updated.
+ * Derived members can invalidate here.
+ */
+ public void onRankingUpdated() {}
+
+ /**
+ * Called when a ListEntry's grouping information (parent or children) has changed.
+ * Derived members can invalidate here.
+ */
+ public void onGroupingUpdated() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
new file mode 100644
index 0000000..76e256b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Person;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Whether the ListEntry is shown to the user as a high priority notification: visible on
+ * the lock screen/status bar and in the top section in the shade.
+ *
+ * A NotificationEntry is considered high priority if it:
+ * - has importance greater than or equal to IMPORTANCE_DEFAULT
+ * OR
+ * - their importance has NOT been set to a low priority option by the user AND the notification
+ * fulfills one of the following:
+ * - has a person associated with it
+ * - has a media session associated with it
+ * - has messaging style
+ *
+ * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
+ * high priority
+ */
+public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> {
+ // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
+ // replaced in GroupEntry and NotifListBuilderImpl
+ private final NotificationGroupManager mGroupManager;
+
+
+ public IsHighPriorityProvider() {
+ // TODO: (b/145659174) remove
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
+ }
+
+ @Override
+ protected Boolean computeValue(ListEntry entry) {
+ if (entry == null) {
+ return false;
+ }
+
+ return isHighPriority(entry);
+ }
+
+ private boolean isHighPriority(ListEntry listEntry) {
+ // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING)
+ final NotificationEntry notifEntry = listEntry.getRepresentativeEntry();
+ return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
+ || hasHighPriorityCharacteristics(notifEntry)
+ || hasHighPriorityChild(listEntry);
+
+ }
+
+ private boolean hasHighPriorityChild(ListEntry entry) {
+ // TODO: (b/145659174) remove
+ if (entry instanceof NotificationEntry) {
+ NotificationEntry notifEntry = (NotificationEntry) entry;
+ if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) {
+ List<NotificationEntry> logicalChildren =
+ mGroupManager.getLogicalChildren(notifEntry.getSbn());
+ for (NotificationEntry child : logicalChildren) {
+ if (child.isHighPriority()) {
+ return true;
+ }
+ }
+ }
+ }
+
+ if (entry instanceof GroupEntry) {
+ for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
+ if (child.isHighPriority()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
+ return !hasUserSetImportance(entry)
+ && (isImportantOngoing(entry)
+ || entry.getSbn().getNotification().hasMediaSession()
+ || hasPerson(entry)
+ || isMessagingStyle(entry));
+ }
+
+ private boolean isImportantOngoing(NotificationEntry entry) {
+ return entry.getSbn().getNotification().isForegroundService()
+ && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
+ }
+
+ private boolean isMessagingStyle(NotificationEntry entry) {
+ return Notification.MessagingStyle.class.equals(
+ entry.getSbn().getNotification().getNotificationStyle());
+ }
+
+ private boolean hasPerson(NotificationEntry entry) {
+ // TODO: cache favorite and recent contacts to check contact affinity
+ Notification notification = entry.getSbn().getNotification();
+ ArrayList<Person> people = notification.extras != null
+ ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
+ : new ArrayList<>();
+ return people != null && !people.isEmpty();
+ }
+
+ private boolean hasUserSetImportance(NotificationEntry entry) {
+ return entry.getRanking().getChannel() != null
+ && entry.getRanking().getChannel().hasUserSetImportance();
+ }
+
+ @Override
+ public void onSbnUpdated() {
+ invalidate();
+ }
+
+ @Override
+ public void onRankingUpdated() {
+ invalidate();
+ }
+
+ @Override
+ public void onGroupingUpdated() {
+ invalidate();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 987b52db..784673e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -249,7 +249,7 @@
}
}
-private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
+fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
entry.row
?.childrenWithId(R.id.expanded)
?.mapNotNull { it as? ViewGroup }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index f3e9b6b..183adeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -37,12 +37,19 @@
import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.IndentingPrintWriter;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.statusbar.NotificationMediaManager;
import libcore.io.IoUtils;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Objects;
import javax.inject.Inject;
@@ -52,16 +59,15 @@
* Manages the lockscreen wallpaper.
*/
@Singleton
-public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable {
+public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
+ Dumpable {
private static final String TAG = "LockscreenWallpaper";
- private final NotificationMediaManager mMediaManager =
- Dependency.get(NotificationMediaManager.class);
-
+ private final NotificationMediaManager mMediaManager;
private final WallpaperManager mWallpaperManager;
- private Handler mH;
private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final Handler mH;
private boolean mCached;
private Bitmap mCache;
@@ -74,10 +80,16 @@
@Inject
public LockscreenWallpaper(WallpaperManager wallpaperManager,
@Nullable IWallpaperManager iWallpaperManager,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DumpController dumpController,
+ NotificationMediaManager mediaManager,
+ @MainHandler Handler mainHandler) {
+ dumpController.registerDumpable(getClass().getSimpleName(), this);
mWallpaperManager = wallpaperManager;
mCurrentUserId = ActivityManager.getCurrentUser();
mUpdateMonitor = keyguardUpdateMonitor;
+ mMediaManager = mediaManager;
+ mH = mainHandler;
if (iWallpaperManager != null) {
// Service is disabled on some devices like Automotive
@@ -89,14 +101,6 @@
}
}
- void setHandler(Handler handler) {
- if (mH != null) {
- Log.wtfStack(TAG, "Handler has already been set. Trying to double initialize?");
- return;
- }
- mH = handler;
- }
-
public Bitmap getBitmap() {
if (mCached) {
return mCache;
@@ -227,6 +231,16 @@
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println(getClass().getSimpleName() + ":");
+ IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " ").increaseIndent();
+ iPw.println("mCached=" + mCached);
+ iPw.println("mCache=" + mCache);
+ iPw.println("mCurrentUserId=" + mCurrentUserId);
+ iPw.println("mSelectedUser=" + mSelectedUser);
+ }
+
private static class LoaderResult {
public final boolean success;
public final Bitmap bitmap;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 1454e25..4c5bbce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -360,7 +360,8 @@
return false;
}
- if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
+ if (mState == ScrimState.AOD
+ && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
return true;
}
@@ -560,7 +561,7 @@
}
protected void scheduleUpdate() {
- if (mUpdatePending) return;
+ if (mUpdatePending || mScrimBehind == null) return;
// Make sure that a frame gets scheduled.
mScrimBehind.invalidate();
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 a9d7601..7e32581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1061,7 +1061,6 @@
if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
- mLockscreenWallpaper.setHandler(mHandler);
}
mKeyguardIndicationController =
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
new file mode 100644
index 0000000..62ae7b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.animation
+
+import android.os.Looper
+import android.util.ArrayMap
+import android.util.Log
+import android.view.View
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FlingAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
+import java.util.WeakHashMap
+
+/**
+ * Extension function for all objects which will return a PhysicsAnimator instance for that object.
+ */
+val <T : View> T.physicsAnimator: PhysicsAnimator<T> get() { return getInstance(this) }
+
+private const val TAG = "PhysicsAnimator"
+
+typealias EndAction = () -> Unit
+
+/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
+typealias UpdateMap<T> =
+ ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+/**
+ * Map of the animators associated with a given object. This ensures that only one animator
+ * per object exists.
+ */
+internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
+
+/**
+ * Default spring configuration to use for animations where stiffness and/or damping ratio
+ * were not provided.
+ */
+private val defaultSpring = PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM,
+ SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+
+/** Default fling configuration to use for animations where friction was not provided. */
+private val defaultFling = PhysicsAnimator.FlingConfig(
+ friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
+
+/** Whether to log helpful debug information about animations. */
+private var verboseLogging = false
+
+/**
+ * Animator that uses physics-based animations to animate properties on views and objects. Physics
+ * animations use real-world physical concepts, such as momentum and mass, to realistically simulate
+ * motion. PhysicsAnimator is heavily inspired by [android.view.ViewPropertyAnimator], and
+ * also uses the builder pattern to configure and start animations.
+ *
+ * The physics animations are backed by [DynamicAnimation].
+ *
+ * @param T The type of the object being animated.
+ */
+class PhysicsAnimator<T> private constructor (val target: T) {
+
+ /** Data class for representing animation frame updates. */
+ data class AnimationUpdate(val value: Float, val velocity: Float)
+
+ /** [DynamicAnimation] instances for the given properties. */
+ private val springAnimations = ArrayMap<FloatPropertyCompat<in T>, SpringAnimation>()
+ private val flingAnimations = ArrayMap<FloatPropertyCompat<in T>, FlingAnimation>()
+
+ /**
+ * Spring and fling configurations for the properties to be animated on the target. We'll
+ * configure and start the DynamicAnimations for these properties according to the provided
+ * configurations.
+ */
+ private val springConfigs = ArrayMap<FloatPropertyCompat<in T>, SpringConfig>()
+ private val flingConfigs = ArrayMap<FloatPropertyCompat<in T>, FlingConfig>()
+
+ /**
+ * Animation listeners for the animation. These will be notified when each property animation
+ * updates or ends.
+ */
+ private val updateListeners = ArrayList<UpdateListener<T>>()
+ private val endListeners = ArrayList<EndListener<T>>()
+
+ /** End actions to run when all animations have completed. */
+ private val endActions = ArrayList<EndAction>()
+
+ /**
+ * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
+ * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
+ * just one permanent update and end listener to the DynamicAnimations.
+ */
+ internal var internalListeners = ArrayList<InternalListener>()
+
+ /**
+ * Action to run when [start] is called. This can be changed by
+ * [PhysicsAnimatorTestUtils.prepareForTest] to enable animators to run under test and provide
+ * helpful test utilities.
+ */
+ internal var startAction: () -> Unit = ::startInternal
+
+ /**
+ * Springs a property to the given value, using the provided configuration settings.
+ *
+ * Springs are used when you know the exact value to which you want to animate. They can be
+ * configured with a start velocity (typically used when the spring is initiated by a touch
+ * event), but this velocity will be realistically attenuated as forces are applied to move the
+ * property towards the end value.
+ *
+ * If you find yourself repeating the same stiffness and damping ratios many times, consider
+ * storing a single [SpringConfig] instance and passing that in instead of individual values.
+ *
+ * @param property The property to spring to the given value. The property must be an instance
+ * of FloatPropertyCompat<? super T>. For example, if this is a
+ * PhysicsAnimator<FrameLayout>, you can use a FloatPropertyCompat<FrameLayout>, as
+ * well as a FloatPropertyCompat<ViewGroup>, and so on.
+ * @param toPosition The value to spring the given property to.
+ * @param startVelocity The initial velocity to use for the animation.
+ * @param stiffness The stiffness to use for the spring. Higher stiffness values result in
+ * faster animations, while lower stiffness means a slower animation. Reasonable values for
+ * low, medium, and high stiffness can be found as constants in [SpringForce].
+ * @param dampingRatio The damping ratio (bounciness) to use for the spring. Higher values
+ * result in a less 'springy' animation, while lower values allow the animation to bounce
+ * back and forth for a longer time after reaching the final position. Reasonable values for
+ * low, medium, and high damping can be found in [SpringForce].
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ startVelocity: Float = 0f,
+ stiffness: Float = defaultSpring.stiffness,
+ dampingRatio: Float = defaultSpring.dampingRatio
+ ): PhysicsAnimator<T> {
+ if (verboseLogging) {
+ Log.d(TAG, "Springing ${getReadablePropertyName(property)} to $toPosition.")
+ }
+
+ springConfigs[property] =
+ SpringConfig(stiffness, dampingRatio, startVelocity, toPosition)
+ return this
+ }
+
+ /**
+ * Springs a property to a given value using the provided start velocity and configuration
+ * options.
+ *
+ * @see spring
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ startVelocity: Float,
+ config: SpringConfig = defaultSpring
+ ): PhysicsAnimator<T> {
+ return spring(
+ property, toPosition, startVelocity, config.stiffness, config.dampingRatio)
+ }
+
+ /**
+ * Springs a property to a given value using the provided configuration options, and a start
+ * velocity of 0f.
+ *
+ * @see spring
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ config: SpringConfig = defaultSpring
+ ): PhysicsAnimator<T> {
+ return spring(property, toPosition, 0f, config)
+ }
+
+ /**
+ * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+ * the provided configuration settings.
+ *
+ * Flings are used when you have a start velocity, and want the property value to realistically
+ * decrease as friction is applied until the velocity reaches zero. Flings do not have a
+ * deterministic end value. If you are attempting to animate to a specific end value, use
+ * [spring].
+ *
+ * If you find yourself repeating the same friction/min/max values, consider storing a single
+ * [FlingConfig] and passing that in instead.
+ *
+ * @param property The property to fling using the given start velocity.
+ * @param startVelocity The start velocity (in pixels per second) with which to start the fling.
+ * @param friction Friction value applied to slow down the animation over time. Higher values
+ * will more quickly slow the animation. Typical friction values range from 1f to 10f.
+ * @param min The minimum value allowed for the animation. If this value is reached, the
+ * animation will end abruptly.
+ * @param max The maximum value allowed for the animation. If this value is reached, the
+ * animation will end abruptly.
+ */
+ fun fling(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ friction: Float = defaultFling.friction,
+ min: Float = defaultFling.min,
+ max: Float = defaultFling.max
+ ): PhysicsAnimator<T> {
+ if (verboseLogging) {
+ Log.d(TAG, "Flinging ${getReadablePropertyName(property)} " +
+ "with velocity $startVelocity.")
+ }
+
+ flingConfigs[property] = FlingConfig(friction, min, max, startVelocity)
+ return this
+ }
+
+ /**
+ * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+ * the provided configuration settings.
+ *
+ * @see fling
+ */
+ fun fling(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ config: FlingConfig = defaultFling
+ ): PhysicsAnimator<T> {
+ return fling(property, startVelocity, config.friction, config.min, config.max)
+ }
+
+ /**
+ * Adds a listener that will be called whenever any property on the animated object is updated.
+ * This will be called on every animation frame, with the current value of the animated object
+ * and the new property values.
+ */
+ fun addUpdateListener(listener: UpdateListener<T>): PhysicsAnimator<T> {
+ updateListeners.add(listener)
+ return this
+ }
+
+ /**
+ * Adds a listener that will be called whenever a property's animation ends. This is useful if
+ * you care about a specific property ending, or want to use the end value/end velocity from a
+ * particular property's animation. If you just want to run an action when all property
+ * animations have ended, use [withEndActions].
+ */
+ fun addEndListener(listener: EndListener<T>): PhysicsAnimator<T> {
+ endListeners.add(listener)
+ return this
+ }
+
+ /**
+ * Adds end actions that will be run sequentially when animations for every property involved in
+ * this specific animation have ended (unless they were explicitly canceled). For example, if
+ * you call:
+ *
+ * animator
+ * .spring(TRANSLATION_X, ...)
+ * .spring(TRANSLATION_Y, ...)
+ * .withEndAction(action)
+ * .start()
+ *
+ * 'action' will be run when both TRANSLATION_X and TRANSLATION_Y end.
+ *
+ * Other properties may still be animating, if those animations were not started in the same
+ * call. For example:
+ *
+ * animator
+ * .spring(ALPHA, ...)
+ * .start()
+ *
+ * animator
+ * .spring(TRANSLATION_X, ...)
+ * .spring(TRANSLATION_Y, ...)
+ * .withEndAction(action)
+ * .start()
+ *
+ * 'action' will still be run as soon as TRANSLATION_X and TRANSLATION_Y end, even if ALPHA is
+ * still animating.
+ *
+ * If you want to run actions as soon as a subset of property animations have ended, you want
+ * access to the animation's end value/velocity, or you want to run these actions even if the
+ * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
+ * which indicates that all relevant animations have ended.
+ */
+ fun withEndActions(vararg endActions: EndAction): PhysicsAnimator<T> {
+ this.endActions.addAll(endActions)
+ return this
+ }
+
+ /** Starts the animations! */
+ fun start() {
+ startAction()
+ }
+
+ /**
+ * Starts the animations for real! This is typically called immediately by [start] unless this
+ * animator is under test.
+ */
+ internal fun startInternal() {
+ if (!Looper.getMainLooper().isCurrentThread) {
+ Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
+ "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
+ "your test setup.")
+ }
+
+ // Add an internal listener that will dispatch animation events to the provided listeners.
+ internalListeners.add(InternalListener(
+ getAnimatedProperties(),
+ ArrayList(updateListeners),
+ ArrayList(endListeners),
+ ArrayList(endActions)))
+
+ for ((property, config) in flingConfigs) {
+ val currentValue = property.getValue(target)
+
+ // If the fling is already out of bounds, don't start it.
+ if (currentValue <= config.min || currentValue >= config.max) {
+ continue
+ }
+
+ val flingAnim = getFlingAnimation(property)
+ config.applyToAnimation(flingAnim)
+ flingAnim.start()
+ }
+
+ for ((property, config) in springConfigs) {
+ val springAnim = getSpringAnimation(property)
+ config.applyToAnimation(springAnim)
+ springAnim.start()
+ }
+
+ clearAnimator()
+ }
+
+ /** Clear the animator's builder variables. */
+ private fun clearAnimator() {
+ springConfigs.clear()
+ flingConfigs.clear()
+
+ updateListeners.clear()
+ endListeners.clear()
+ endActions.clear()
+ }
+
+ /** Retrieves a spring animation for the given property, building one if needed. */
+ private fun getSpringAnimation(property: FloatPropertyCompat<in T>): SpringAnimation {
+ return springAnimations.getOrPut(
+ property,
+ { configureDynamicAnimation(SpringAnimation(target, property), property)
+ as SpringAnimation })
+ }
+
+ /** Retrieves a fling animation for the given property, building one if needed. */
+ private fun getFlingAnimation(property: FloatPropertyCompat<in T>): FlingAnimation {
+ return flingAnimations.getOrPut(
+ property,
+ { configureDynamicAnimation(FlingAnimation(target, property), property)
+ as FlingAnimation })
+ }
+
+ /**
+ * Adds update and end listeners to the DynamicAnimation which will dispatch to the internal
+ * listeners.
+ */
+ private fun configureDynamicAnimation(
+ anim: DynamicAnimation<*>,
+ property: FloatPropertyCompat<in T>
+ ): DynamicAnimation<*> {
+ anim.addUpdateListener { _, value, velocity ->
+ for (i in 0 until internalListeners.size) {
+ internalListeners[i].onInternalAnimationUpdate(property, value, velocity)
+ }
+ }
+ anim.addEndListener { _, canceled, value, velocity ->
+ internalListeners.removeAll {
+ it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+ return anim
+ }
+
+ /**
+ * Internal listener class that receives updates from DynamicAnimation listeners, and dispatches
+ * them to the appropriate update/end listeners. This class is also aware of which properties
+ * were being animated when the end listeners were passed in, so that we can provide the
+ * appropriate value for allEnded to [EndListener.onAnimationEnd].
+ */
+ internal inner class InternalListener constructor(
+ private var properties: Set<FloatPropertyCompat<in T>>,
+ private var updateListeners: List<UpdateListener<T>>,
+ private var endListeners: List<EndListener<T>>,
+ private var endActions: List<EndAction>
+ ) {
+
+ /** The number of properties whose animations haven't ended. */
+ private var numPropertiesAnimating = properties.size
+
+ /**
+ * Update values that haven't yet been dispatched because not all property animations have
+ * updated yet.
+ */
+ private val undispatchedUpdates =
+ ArrayMap<FloatPropertyCompat<in T>, AnimationUpdate>()
+
+ /** Called when a DynamicAnimation updates. */
+ internal fun onInternalAnimationUpdate(
+ property: FloatPropertyCompat<in T>,
+ value: Float,
+ velocity: Float
+ ) {
+
+ // If this property animation isn't relevant to this listener, ignore it.
+ if (!properties.contains(property)) {
+ return
+ }
+
+ undispatchedUpdates[property] = AnimationUpdate(value, velocity)
+ maybeDispatchUpdates()
+ }
+
+ /**
+ * Called when a DynamicAnimation ends.
+ *
+ * @return True if this listener should be removed from the list of internal listeners, so
+ * it no longer receives updates from DynamicAnimations.
+ */
+ internal fun onInternalAnimationEnd(
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float
+ ): Boolean {
+
+ // If this property animation isn't relevant to this listener, ignore it.
+ if (!properties.contains(property)) {
+ return false
+ }
+
+ // Dispatch updates if we have one for each property.
+ numPropertiesAnimating--
+ maybeDispatchUpdates()
+
+ // If we didn't have an update for each property, dispatch the update for the ending
+ // property. This guarantees that an update isn't sent for this property *after* we call
+ // onAnimationEnd for that property.
+ if (undispatchedUpdates.contains(property)) {
+ updateListeners.forEach { updateListener ->
+ updateListener.onAnimationUpdateForProperty(
+ target,
+ UpdateMap<T>().also { it[property] = undispatchedUpdates[property] })
+ }
+
+ undispatchedUpdates.remove(property)
+ }
+
+ val allEnded = !arePropertiesAnimating(properties)
+ endListeners.forEach {
+ it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+
+ // If all of the animations that this listener cares about have ended, run the end
+ // actions unless the animation was canceled.
+ if (allEnded && !canceled) {
+ endActions.forEach { it() }
+ }
+
+ return allEnded
+ }
+
+ /**
+ * Dispatch undispatched values if we've received an update from each of the animating
+ * properties.
+ */
+ private fun maybeDispatchUpdates() {
+ if (undispatchedUpdates.size >= numPropertiesAnimating &&
+ undispatchedUpdates.size > 0) {
+ updateListeners.forEach {
+ it.onAnimationUpdateForProperty(target, ArrayMap(undispatchedUpdates))
+ }
+
+ undispatchedUpdates.clear()
+ }
+ }
+ }
+
+ /** Return true if any animations are running on the object. */
+ fun isRunning(): Boolean {
+ return arePropertiesAnimating(springAnimations.keys.union(flingAnimations.keys))
+ }
+
+ /** Returns whether the given property is animating. */
+ fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
+ return springAnimations[property]?.isRunning ?: false
+ }
+
+ /** Returns whether any of the given properties are animating. */
+ fun arePropertiesAnimating(properties: Set<FloatPropertyCompat<in T>>): Boolean {
+ return properties.any { isPropertyAnimating(it) }
+ }
+
+ /** Return the set of properties that will begin animating upon calling [start]. */
+ internal fun getAnimatedProperties(): Set<FloatPropertyCompat<in T>> {
+ return springConfigs.keys.union(flingConfigs.keys)
+ }
+
+ /** Cancels all in progress animations on all properties. */
+ fun cancel() {
+ for (dynamicAnim in flingAnimations.values.union(springAnimations.values)) {
+ dynamicAnim.cancel()
+ }
+ }
+
+ /**
+ * Container object for spring animation configuration settings. This allows you to store
+ * default stiffness and damping ratio values in a single configuration object, which you can
+ * pass to [spring].
+ */
+ data class SpringConfig internal constructor(
+ internal var stiffness: Float,
+ internal var dampingRatio: Float,
+ internal var startVel: Float = 0f,
+ internal var finalPosition: Float = -Float.MAX_VALUE
+ ) {
+
+ constructor() :
+ this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+
+ constructor(stiffness: Float, dampingRatio: Float) :
+ this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+
+ /** Apply these configuration settings to the given SpringAnimation. */
+ internal fun applyToAnimation(anim: SpringAnimation) {
+ val springForce = anim.spring ?: SpringForce()
+ anim.spring = springForce.apply {
+ stiffness = this@SpringConfig.stiffness
+ dampingRatio = this@SpringConfig.dampingRatio
+ finalPosition = this@SpringConfig.finalPosition
+ }
+
+ if (startVel != 0f) anim.setStartVelocity(startVel)
+ }
+ }
+
+ /**
+ * Container object for fling animation configuration settings. This allows you to store default
+ * friction values (as well as optional min/max values) in a single configuration object, which
+ * you can pass to [fling] and related methods.
+ */
+ data class FlingConfig internal constructor(
+ internal var friction: Float,
+ internal var min: Float,
+ internal var max: Float,
+ internal var startVel: Float
+ ) {
+
+ constructor() : this(defaultFling.friction)
+
+ constructor(friction: Float) :
+ this(friction, defaultFling.min, defaultFling.max)
+
+ constructor(friction: Float, min: Float, max: Float) :
+ this(friction, min, max, startVel = 0f)
+
+ /** Apply these configuration settings to the given FlingAnimation. */
+ internal fun applyToAnimation(anim: FlingAnimation) {
+ anim.apply {
+ friction = this@FlingConfig.friction
+ setMinValue(min)
+ setMaxValue(max)
+ setStartVelocity(startVel)
+ }
+ }
+ }
+
+ /**
+ * Listener for receiving values from in progress animations. Used with
+ * [PhysicsAnimator.addUpdateListener].
+ *
+ * @param <T> The type of the object being animated.
+ </T> */
+ interface UpdateListener<T> {
+
+ /**
+ * Called on each animation frame with the target object, and a map of FloatPropertyCompat
+ * -> AnimationUpdate, containing the latest value and velocity for that property. When
+ * multiple properties are animating together, the map will typically contain one entry for
+ * each property. However, you should never assume that this is the case - when a property
+ * animation ends earlier than the others, you'll receive an UpdateMap containing only that
+ * property's final update. Subsequently, you'll only receive updates for the properties
+ * that are still animating.
+ *
+ * Always check that the map contains an update for the property you're interested in before
+ * accessing it.
+ *
+ * @param target The animated object itself.
+ * @param values Map of property to AnimationUpdate, which contains that property
+ * animation's latest value and velocity. You should never assume that a particular property
+ * is present in this map.
+ */
+ fun onAnimationUpdateForProperty(
+ target: T,
+ values: UpdateMap<T>
+ )
+ }
+
+ /**
+ * Listener for receiving callbacks when animations end.
+ *
+ * @param <T> The type of the object being animated.
+ </T> */
+ interface EndListener<T> {
+
+ /**
+ * Called with the final animation values as each property animation ends. This can be used
+ * to respond to specific property animations concluding (such as hiding a view when ALPHA
+ * ends, even if the corresponding TRANSLATION animations have not ended).
+ *
+ * If you just want to run an action when all of the property animations have ended, you can
+ * use [PhysicsAnimator.withEndActions].
+ *
+ * @param target The animated object itself.
+ * @param property The property whose animation has just ended.
+ * @param canceled Whether the animation was explicitly canceled before it naturally ended.
+ * @param finalValue The final value of the animated property.
+ * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
+ * This is typically zero, unless this was a fling animation which ended abruptly due to
+ * reaching its configured min/max values.
+ * @param allRelevantPropertyAnimsEnded Whether all properties relevant to this end listener
+ * have ended. Relevant properties are those which were animated alongside the
+ * [addEndListener] call where this animator was passed in. For example:
+ *
+ * animator
+ * .spring(TRANSLATION_X, 100f)
+ * .spring(TRANSLATION_Y, 200f)
+ * .withEndListener(firstEndListener)
+ * .start()
+ *
+ * firstEndListener will be called first for TRANSLATION_X, with allEnded = false,
+ * because TRANSLATION_Y is still running. When TRANSLATION_Y ends, it'll be called with
+ * allEnded = true.
+ *
+ * If a subsequent call to start() is made with other properties, those properties are not
+ * considered relevant and allEnded will still equal true when only TRANSLATION_X and
+ * TRANSLATION_Y end. For example, if immediately after the prior example, while
+ * TRANSLATION_X and TRANSLATION_Y are still animating, we called:
+ *
+ * animator.
+ * .spring(SCALE_X, 2f, stiffness = 10f) // That will take awhile...
+ * .withEndListener(secondEndListener)
+ * .start()
+ *
+ * firstEndListener will still be called with allEnded = true when TRANSLATION_X/Y end, even
+ * though SCALE_X is still animating. Similarly, secondEndListener will be called with
+ * allEnded = true as soon as SCALE_X ends, even if the translation animations are still
+ * running.
+ */
+ fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ )
+ }
+
+ companion object {
+
+ /**
+ * Constructor to use to for new physics animator instances in [getInstance]. This is
+ * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
+ * all code using the physics animator is given testable instances instead.
+ */
+ internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
+
+ @JvmStatic
+ fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
+ if (!animators.containsKey(target)) {
+ animators[target] = instanceConstructor(target)
+ }
+
+ return animators[target] as PhysicsAnimator<T>
+ }
+
+ /**
+ * Set whether all physics animators should log a lot of information about animations.
+ * Useful for debugging!
+ */
+ @JvmStatic
+ fun setVerboseLogging(debug: Boolean) {
+ verboseLogging = debug
+ }
+
+ @JvmStatic
+ fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
+ return when (property) {
+ DynamicAnimation.TRANSLATION_X -> "translationX"
+ DynamicAnimation.TRANSLATION_Y -> "translationY"
+ DynamicAnimation.TRANSLATION_Z -> "translationZ"
+ DynamicAnimation.SCALE_X -> "scaleX"
+ DynamicAnimation.SCALE_Y -> "scaleY"
+ DynamicAnimation.ROTATION -> "rotation"
+ DynamicAnimation.ROTATION_X -> "rotationX"
+ DynamicAnimation.ROTATION_Y -> "rotationY"
+ DynamicAnimation.SCROLL_X -> "scrollX"
+ DynamicAnimation.SCROLL_Y -> "scrollY"
+ DynamicAnimation.ALPHA -> "alpha"
+ else -> "Custom FloatPropertyCompat instance"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
new file mode 100644
index 0000000..a1f74eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.animation
+
+import android.os.Handler
+import android.os.Looper
+import android.util.ArrayMap
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import java.util.ArrayDeque
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean
+typealias UpdateFramesPerProperty<T> =
+ ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>
+
+/**
+ * Utilities for testing code that uses [PhysicsAnimator].
+ *
+ * Start by calling [prepareForTest] at the beginning of each test - this will modify the behavior
+ * of all PhysicsAnimator instances so that they post animations to the main thread (so they don't
+ * crash). It'll also enable the use of the other static helper methods in this class, which you can
+ * use to do things like block the test until animations complete (so you can test end states), or
+ * verify keyframes.
+ */
+object PhysicsAnimatorTestUtils {
+ var timeoutMs: Long = 2000
+ private var startBlocksUntilAnimationsEnd = false
+ private val animationThreadHandler = Handler(Looper.getMainLooper())
+ private val allAnimatedObjects = HashSet<Any>()
+ private val animatorTestHelpers = HashMap<PhysicsAnimator<*>, AnimatorTestHelper<*>>()
+
+ /**
+ * Modifies the behavior of all [PhysicsAnimator] instances so that they post animations to the
+ * main thread, and report all of their
+ */
+ @JvmStatic
+ fun prepareForTest() {
+ val defaultConstructor = PhysicsAnimator.instanceConstructor
+ PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
+ val animator = defaultConstructor(target)
+ allAnimatedObjects.add(target)
+ animatorTestHelpers[animator] = AnimatorTestHelper(animator)
+ return animator
+ }
+
+ timeoutMs = 2000
+ startBlocksUntilAnimationsEnd = false
+ allAnimatedObjects.clear()
+ }
+
+ @JvmStatic
+ fun tearDown() {
+ val latch = CountDownLatch(1)
+ animationThreadHandler.post {
+ animatorTestHelpers.keys.forEach { it.cancel() }
+ latch.countDown()
+ }
+
+ latch.await()
+
+ animatorTestHelpers.clear()
+ animators.clear()
+ allAnimatedObjects.clear()
+ }
+
+ /**
+ * Sets the maximum time (in milliseconds) to block the test thread while waiting for animations
+ * before throwing an exception.
+ */
+ @JvmStatic
+ fun setBlockTimeout(timeoutMs: Long) {
+ this.timeoutMs = timeoutMs
+ }
+
+ /**
+ * Sets whether all animations should block the test thread until they end. This is typically
+ * the desired behavior, since you can invoke code that runs an animation and then assert things
+ * about its end state.
+ */
+ @JvmStatic
+ fun setAllAnimationsBlock(block: Boolean) {
+ startBlocksUntilAnimationsEnd = block
+ }
+
+ /**
+ * Blocks the calling thread until animations of the given property on the target object end.
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ fun <T : Any> blockUntilAnimationsEnd(
+ animator: PhysicsAnimator<T>,
+ vararg properties: FloatPropertyCompat<in T>
+ ) {
+ val animatingProperties = HashSet<FloatPropertyCompat<in T>>()
+ for (property in properties) {
+ if (animator.isPropertyAnimating(property)) {
+ animatingProperties.add(property)
+ }
+ }
+
+ if (animatingProperties.size > 0) {
+ val latch = CountDownLatch(animatingProperties.size)
+ getAnimationTestHelper(animator).addTestEndListener(
+ object : PhysicsAnimator.EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ if (animatingProperties.contains(property)) {
+ latch.countDown()
+ }
+ }
+ })
+
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ /**
+ * Blocks the calling thread until all animations of the given property (on all target objects)
+ * have ended. Useful when you don't have access to the objects being animated, but still need
+ * to wait for them to end so that other testable side effects occur (such as update/end
+ * listeners).
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ fun <T : Any> blockUntilAnimationsEnd(
+ properties: FloatPropertyCompat<in T>
+ ) {
+ for (target in allAnimatedObjects) {
+ try {
+ blockUntilAnimationsEnd(
+ PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
+ } catch (e: ClassCastException) {
+ // Keep checking the other objects for ones whose types match the provided
+ // properties.
+ }
+ }
+ }
+
+ /**
+ * Blocks the calling thread until the first animation frame in which predicate returns true. If
+ * the given object isn't animating, returns without blocking.
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ fun <T : Any> blockUntilFirstAnimationFrameWhereTrue(
+ animator: PhysicsAnimator<T>,
+ predicate: (T) -> Boolean
+ ) {
+ if (animator.isRunning()) {
+ val latch = CountDownLatch(1)
+ getAnimationTestHelper(animator).addTestUpdateListener(object : PhysicsAnimator
+ .UpdateListener<T> {
+ override fun onAnimationUpdateForProperty(
+ target: T,
+ values: UpdateMap<T>
+ ) {
+ if (predicate(target)) {
+ latch.countDown()
+ }
+ }
+ })
+
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ /**
+ * Verifies that the animator reported animation frame values to update listeners that satisfy
+ * the given matchers, in order. Not all frames need to satisfy a matcher - we'll run through
+ * all animation frames, and check them against the current predicate. If it returns false, we
+ * continue through the frames until it returns true, and then move on to the next matcher.
+ * Verification fails if we run out of frames while unsatisfied matchers remain.
+ *
+ * If verification is successful, all frames to this point are considered 'verified' and will be
+ * cleared. Subsequent calls to this method will start verification at the next animation frame.
+ *
+ * Example: Verify that an animation surpassed x = 50f before going negative.
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 50f },
+ * { u -> u.value < 0f })
+ *
+ * Example: verify that an animation went backwards at some point while still being on-screen.
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.velocity < 0f && u.value >= 0f })
+ *
+ * This method is intended to help you test longer, more complicated animations where it's
+ * critical that certain values were reached. Using this method to test short animations can
+ * fail due to the animation having fewer frames than provided matchers. For example, an
+ * animation from x = 1f to x = 5f might only have two frames, at x = 3f and x = 5f. The
+ * following would then fail despite it seeming logically sound:
+ *
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 1f },
+ * { u -> u.value > 2f },
+ * { u -> u.value > 3f })
+ *
+ * Tests might also fail if your matchers are too granular, such as this example test after an
+ * animation from x = 0f to x = 100f. It's unlikely there was a frame specifically between 2f
+ * and 3f.
+ *
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 2f && u.value < 3f },
+ * { u -> u.value >= 50f })
+ *
+ * Failures will print a helpful log of all animation frames so you can see what caused the test
+ * to fail.
+ */
+ fun <T : Any> verifyAnimationUpdateFrames(
+ animator: PhysicsAnimator<T>,
+ property: FloatPropertyCompat<in T>,
+ firstUpdateMatcher: UpdateMatcher,
+ vararg additionalUpdateMatchers: UpdateMatcher
+ ) {
+ val updateFrames: UpdateFramesPerProperty<T> = getAnimationUpdateFrames(animator)
+ val matchers = ArrayDeque<UpdateMatcher>(
+ additionalUpdateMatchers.toList())
+ val frameTraceMessage = StringBuilder()
+
+ var curMatcher = firstUpdateMatcher
+
+ // Loop through the updates from the testable animator.
+ for (update in updateFrames[property]
+ ?: error("No frames for given target object and property.")) {
+
+ // Check whether this frame satisfies the current matcher.
+ if (curMatcher(update)) {
+
+ // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
+ // frames and return without failing.
+ if (matchers.size == 0) {
+ getAnimationUpdateFrames(animator).remove(property)
+ return
+ }
+
+ frameTraceMessage.append("$update\t(satisfied matcher)\n")
+ curMatcher = matchers.pop() // Get the next matcher and keep going.
+ } else {
+ frameTraceMessage.append("${update}\n")
+ }
+ }
+
+ val readablePropertyName = PhysicsAnimator.getReadablePropertyName(property)
+ getAnimationUpdateFrames(animator).remove(property)
+
+ throw RuntimeException(
+ "Failed to verify animation frames for property $readablePropertyName: " +
+ "Provided ${additionalUpdateMatchers.size + 1} matchers, " +
+ "however ${matchers.size + 1} remained unsatisfied.\n\n" +
+ "All frames:\n$frameTraceMessage")
+ }
+
+ /**
+ * Overload of [verifyAnimationUpdateFrames] that builds matchers for you, from given float
+ * values. For example, to verify that an animations passed from 0f to 50f to 100f back to 50f:
+ *
+ * verifyAnimationUpdateFrames(animator, TRANSLATION_X, 0f, 50f, 100f, 50f)
+ *
+ * This verifies that update frames were received with values of >= 0f, >= 50f, >= 100f, and
+ * <= 50f.
+ *
+ * The same caveats apply: short animations might not have enough frames to satisfy all of the
+ * matchers, and overly specific calls (such as 0f, 1f, 2f, 3f, etc. for an animation from
+ * x = 0f to x = 100f) might fail as the animation only had frames at 0f, 25f, 50f, 75f, and
+ * 100f. As with [verifyAnimationUpdateFrames], failures will print a helpful log of all frames
+ * so you can see what caused the test to fail.
+ */
+ fun <T : Any> verifyAnimationUpdateFrames(
+ animator: PhysicsAnimator<T>,
+ property: FloatPropertyCompat<in T>,
+ startValue: Float,
+ firstTargetValue: Float,
+ vararg additionalTargetValues: Float
+ ) {
+ val matchers = ArrayList<UpdateMatcher>()
+
+ val values = ArrayList<Float>().also {
+ it.add(firstTargetValue)
+ it.addAll(additionalTargetValues.toList())
+ }
+
+ var prevVal = startValue
+ for (value in values) {
+ if (value > prevVal) {
+ matchers.add { update -> update.value >= value }
+ } else {
+ matchers.add { update -> update.value <= value }
+ }
+
+ prevVal = value
+ }
+
+ verifyAnimationUpdateFrames(
+ animator, property, matchers[0], *matchers.drop(0).toTypedArray())
+ }
+
+ /**
+ * Returns all of the values that have ever been reported to update listeners, per property.
+ */
+ fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
+ UpdateFramesPerProperty<T> {
+ return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
+ }
+
+ /**
+ * Clears animation frame updates from the given animator so they aren't used the next time its
+ * passed to [verifyAnimationUpdateFrames].
+ */
+ fun <T : Any> clearAnimationUpdateFrames(animator: PhysicsAnimator<T>) {
+ animatorTestHelpers[animator]?.clearUpdates()
+ }
+
+ private fun <T> getAnimationTestHelper(animator: PhysicsAnimator<T>): AnimatorTestHelper<T> {
+ return animatorTestHelpers[animator] as AnimatorTestHelper<T>
+ }
+
+ /**
+ * Helper class for testing an animator. This replaces the animator's start action with
+ * [startForTest] and adds test listeners to enable other test utility behaviors. We build one
+ * these for each Animator and keep them around so we can access the updates.
+ */
+ class AnimatorTestHelper<T> (private val animator: PhysicsAnimator<T>) {
+
+ /** All updates received for each property animation. */
+ private val allUpdates =
+ ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>()
+
+ private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
+ private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
+
+ init {
+ animator.startAction = ::startForTest
+ }
+
+ internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
+ testEndListeners.add(listener)
+ }
+
+ internal fun addTestUpdateListener(listener: PhysicsAnimator.UpdateListener<T>) {
+ testUpdateListeners.add(listener)
+ }
+
+ internal fun getUpdates(): UpdateFramesPerProperty<T> {
+ return allUpdates
+ }
+
+ internal fun clearUpdates() {
+ allUpdates.clear()
+ }
+
+ private fun startForTest() {
+ // The testable animator needs to block the main thread until super.start() has been
+ // called, since callers expect .start() to be synchronous but we're posting it to a
+ // handler here. We may also continue blocking until all animations end, if
+ // startBlocksUntilAnimationsEnd = true.
+ val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
+
+ animationThreadHandler.post {
+ val animatedProperties = animator.getAnimatedProperties()
+
+ // Add an update listener that dispatches to any test update listeners added by
+ // tests.
+ animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
+ override fun onAnimationUpdateForProperty(
+ target: T,
+ values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+ ) {
+ for (listener in testUpdateListeners) {
+ listener.onAnimationUpdateForProperty(target, values)
+ }
+ }
+ })
+
+ // Add an end listener that dispatches to any test end listeners added by tests, and
+ // unblocks the main thread if required.
+ animator.addEndListener(object : PhysicsAnimator.EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ for (listener in testEndListeners) {
+ listener.onAnimationEnd(
+ target, property, canceled, finalValue, finalVelocity,
+ allRelevantPropertyAnimsEnded)
+ }
+
+ if (allRelevantPropertyAnimsEnded) {
+ testEndListeners.clear()
+ testUpdateListeners.clear()
+
+ if (startBlocksUntilAnimationsEnd) {
+ unblockLatch.countDown()
+ }
+ }
+ }
+ })
+
+ val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
+ it.add(object : PhysicsAnimator.UpdateListener<T> {
+ override fun onAnimationUpdateForProperty(
+ target: T,
+ values: ArrayMap<FloatPropertyCompat<in T>,
+ PhysicsAnimator.AnimationUpdate>
+ ) {
+ values.forEach { (property, value) ->
+ allUpdates.getOrPut(property, { ArrayList() }).add(value)
+ }
+ }
+ })
+ }
+
+ /**
+ * Add an internal listener at the head of the list that captures update values
+ * directly from DynamicAnimation. We use this to build a list of all updates so we
+ * can verify that InternalListener dispatches to the real listeners properly.
+ */
+ animator.internalListeners.add(0, animator.InternalListener(
+ animatedProperties,
+ updateListeners,
+ ArrayList(),
+ ArrayList()))
+
+ animator.startInternal()
+ unblockLatch.countDown()
+ }
+
+ unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 2ccecec..2bf855a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -261,7 +261,7 @@
@Test
public void testRemoveBubble_withDismissedNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -304,7 +304,7 @@
assertFalse(mBubbleController.isStackExpanded());
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -334,8 +334,8 @@
@Test
public void testCollapseAfterChangingExpandedBubble() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
@@ -377,7 +377,7 @@
@Test
public void testExpansionRemovesShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -403,7 +403,7 @@
@Test
public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -439,8 +439,8 @@
@Test
public void testRemoveLastExpandedCollapses() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -483,7 +483,7 @@
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Expansion shouldn't change
@@ -501,7 +501,7 @@
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Expansion should change
@@ -519,7 +519,7 @@
Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
// Add the suppress notif bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Notif should be suppressed because we were foreground
@@ -564,7 +564,7 @@
public void testExpandStackAndSelectBubble_removedFirst() {
final String key = mRow.getEntry().getKey();
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Simulate notification cancellation.
@@ -576,7 +576,7 @@
@Test
public void testMarkNewNotificationAsShowInShade() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow.getEntry().getKey()));
@@ -586,7 +586,7 @@
@Test
public void testAddNotif_notBubble() {
- mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
+ mEntryListener.onNotificationAdded(mNonBubbleNotifRow.getEntry());
mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
@@ -631,7 +631,7 @@
@Test
public void testRemoveBubble_succeeds_appCancel() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -646,7 +646,7 @@
@Test
public void removeBubble_fails_clearAll() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -669,7 +669,7 @@
@Test
public void removeBubble_fails_userDismissNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -692,7 +692,7 @@
@Test
public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index f2665ef..775acdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -33,6 +33,7 @@
import android.app.AlarmManager;
import android.database.ContentObserver;
+import android.hardware.Sensor;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -80,6 +81,8 @@
private TriggerSensor mTriggerSensor;
@Mock
private DozeLog mDozeLog;
+ @Mock
+ private Sensor mProximitySensor;
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
private DozeSensors mDozeSensors;
@@ -90,6 +93,7 @@
mTestableLooper = TestableLooper.get(this);
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProximitySensor);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -98,6 +102,14 @@
}
@Test
+ public void testRegisterProx() {
+ // We should not register with the sensor manager initially.
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
+ mDozeSensors.setProxListening(true);
+ verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt());
+ }
+
+ @Test
public void testSensorDebounce() {
mDozeSensors.setListening(true);
@@ -116,6 +128,7 @@
@Test
public void testSetListening_firstTrue_registerSettingsObserver() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
verify(mTriggerSensor).registerSettingsObserver(any(ContentObserver.class));
@@ -123,6 +136,7 @@
@Test
public void testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
mDozeSensors.setListening(true);
@@ -131,6 +145,7 @@
@Test
public void testSetPaused_doesntPause_sensors() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
verify(mTriggerSensor).setListening(eq(true));
@@ -147,8 +162,7 @@
TestableDozeSensors() {
super(getContext(), mAlarmManager, mSensorManager, mDozeParameters,
- mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback,
- mAlwaysOnDisplayPolicy, mDozeLog);
+ mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog);
for (TriggerSensor sensor : mSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index ba7b2e2..cef210c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -26,12 +26,14 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+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.app.ActivityManager;
import android.app.KeyguardManager;
+import android.app.Notification;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -198,11 +200,13 @@
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+ final Notification notification = mock(Notification.class);
+ when(notification.isForegroundService()).thenReturn(true);
NotificationEntry entry = new NotificationEntryBuilder()
.setImportance(IMPORTANCE_LOW)
+ .setNotification(notification)
.build();
entry.setBucket(BUCKET_SILENT);
- entry.setIsHighPriority(true);
assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index d003b99..5310dd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -76,6 +76,7 @@
mSmartActions = copyList(ranking.getSmartActions());
mSmartReplies = copyList(ranking.getSmartReplies());
mCanBubble = ranking.canBubble();
+ mIsVisuallyInterruptive = ranking.visuallyInterruptive();
}
public Ranking build() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
new file mode 100644
index 0000000..721bbdc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.RankingBuilder;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class GroupEntryTest extends SysuiTestCase {
+ @Test
+ public void testIsHighPriority_addChild() {
+ // GIVEN a GroupEntry with a lowPrioritySummary and no children
+ final GroupEntry parentEntry = new GroupEntry("test_group_key");
+ final NotificationEntry lowPrioritySummary = createNotifEntry(false);
+ setSummary(parentEntry, lowPrioritySummary);
+ assertFalse(parentEntry.isHighPriority());
+
+ // WHEN we add a high priority child and invalidate derived members
+ addChild(parentEntry, createNotifEntry(true));
+ parentEntry.invalidateDerivedMembers();
+
+ // THEN the GroupEntry's priority is updated to high even though the summary is still low
+ // priority
+ assertTrue(parentEntry.isHighPriority());
+ assertFalse(lowPrioritySummary.isHighPriority());
+ }
+
+ @Test
+ public void testIsHighPriority_clearChildren() {
+ // GIVEN a GroupEntry with a lowPrioritySummary and high priority children
+ final GroupEntry parentEntry = new GroupEntry("test_group_key");
+ setSummary(parentEntry, createNotifEntry(false));
+ addChild(parentEntry, createNotifEntry(true));
+ addChild(parentEntry, createNotifEntry(true));
+ addChild(parentEntry, createNotifEntry(true));
+ assertTrue(parentEntry.isHighPriority());
+
+ // WHEN we clear the children and invalidate derived members
+ parentEntry.clearChildren();
+ parentEntry.invalidateDerivedMembers();
+
+ // THEN the parentEntry isn't high priority anymore
+ assertFalse(parentEntry.isHighPriority());
+ }
+
+ @Test
+ public void testIsHighPriority_summaryUpdated() {
+ // GIVEN a GroupEntry with a lowPrioritySummary and no children
+ final GroupEntry parentEntry = new GroupEntry("test_group_key");
+ final NotificationEntry lowPrioritySummary = createNotifEntry(false);
+ setSummary(parentEntry, lowPrioritySummary);
+ assertFalse(parentEntry.isHighPriority());
+
+ // WHEN the summary changes to high priority and invalidates its derived members
+ lowPrioritySummary.setRanking(
+ new RankingBuilder()
+ .setKey(lowPrioritySummary.getKey())
+ .setImportance(IMPORTANCE_HIGH)
+ .build());
+ lowPrioritySummary.invalidateDerivedMembers();
+ assertTrue(lowPrioritySummary.isHighPriority());
+
+ // THEN the GroupEntry's priority is updated to high
+ assertTrue(parentEntry.isHighPriority());
+ }
+
+ @Test
+ public void testIsHighPriority_checkChildrenToCalculatePriority() {
+ // GIVEN:
+ // GroupEntry = parentEntry, summary = lowPrioritySummary
+ // NotificationEntry = lowPriorityChild
+ // NotificationEntry = highPriorityChild
+ final GroupEntry parentEntry = new GroupEntry("test_group_key");
+ setSummary(parentEntry, createNotifEntry(false));
+ addChild(parentEntry, createNotifEntry(false));
+ addChild(parentEntry, createNotifEntry(true));
+
+ // THEN the GroupEntry parentEntry is high priority since it has a high priority child
+ assertTrue(parentEntry.isHighPriority());
+ }
+
+ @Test
+ public void testIsHighPriority_childEntryRankingUpdated() {
+ // GIVEN:
+ // GroupEntry = parentEntry, summary = lowPrioritySummary
+ // NotificationEntry = lowPriorityChild
+ final GroupEntry parentEntry = new GroupEntry("test_group_key");
+ final NotificationEntry lowPriorityChild = createNotifEntry(false);
+ setSummary(parentEntry, createNotifEntry(false));
+ addChild(parentEntry, lowPriorityChild);
+
+ // WHEN the child entry ranking changes to high priority and invalidates its derived members
+ lowPriorityChild.setRanking(
+ new RankingBuilder()
+ .setKey(lowPriorityChild.getKey())
+ .setImportance(IMPORTANCE_HIGH)
+ .build());
+ lowPriorityChild.invalidateDerivedMembers();
+
+ // THEN the parent entry's high priority value is updated - but not the parent's summary
+ assertTrue(parentEntry.isHighPriority());
+ assertFalse(parentEntry.getSummary().isHighPriority());
+ }
+
+ private NotificationEntry createNotifEntry(boolean highPriority) {
+ return new NotificationEntryBuilder()
+ .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
+ .build();
+ }
+
+ private void setSummary(GroupEntry parent, NotificationEntry summary) {
+ parent.setSummary(summary);
+ summary.setParent(parent);
+ }
+
+ private void addChild(GroupEntry parent, NotificationEntry child) {
+ parent.addChild(child);
+ child.setParent(parent);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 536aeb4..17d556d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -21,6 +21,8 @@
import static android.app.Notification.CATEGORY_EVENT;
import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -91,6 +93,44 @@
}
@Test
+ public void testIsHighPriority_notificationUpdates() {
+ // GIVEN a notification with high importance
+ final NotificationEntry entryHigh = new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH)
+ .build();
+
+ // WHEN we get the value for the high priority entry, we're caching the high priority value
+ assertTrue(entryHigh.isHighPriority());
+
+ // WHEN we change the ranking and derived members (high priority) are invalidated
+ entryHigh.setRanking(
+ new RankingBuilder()
+ .setKey(entryHigh.getKey())
+ .setImportance(IMPORTANCE_MIN)
+ .build());
+ entryHigh.invalidateDerivedMembers();
+
+ // THEN the priority is recalculated and is now low
+ assertFalse(entryHigh.isHighPriority());
+
+ // WHEN the sbn is updated to have messaging style (high priority characteristic)
+ // AND the entry invalidates its derived members
+ final Notification notification =
+ new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle(""))
+ .build();
+ final StatusBarNotification sbn = entryHigh.getSbn();
+ entryHigh.setSbn(new StatusBarNotification(
+ sbn.getPackageName(), sbn.getPackageName(),
+ sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+ notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0));
+ entryHigh.invalidateDerivedMembers();
+
+ // THEN the priority is recalculated and is now high
+ assertTrue(entryHigh.isHighPriority());
+ }
+
+ @Test
public void testIsExemptFromDndVisualSuppression_foreground() {
mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index cda1538e..1764bef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -17,17 +17,12 @@
package com.android.systemui.statusbar.notification.collection
import android.app.Notification
-import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
import android.service.notification.NotificationListenerService.RankingMap
-import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
-
-import org.junit.runner.RunWith
-
import androidx.test.filters.SmallTest
-
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationEntryBuilder
import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
@@ -42,12 +37,9 @@
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Lazy
import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
-
import org.junit.Before
import org.junit.Test
-import org.mockito.Mockito.`when`
+import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@SmallTest
@@ -76,106 +68,34 @@
}
@Test
- fun testPeopleNotification_isHighPriority() {
- val notification = Notification.Builder(mContext, "test")
- .build()
-
- val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
- notification, mContext.user, "", 0)
-
- `when`(personNotificationIdentifier.isPeopleNotification(sbn)).thenReturn(true)
-
- val e = NotificationEntryBuilder()
- .setNotification(notification)
- .setSbn(sbn)
- .build()
-
- assertTrue(rankingManager.isHighPriority2(e))
- }
-
- @Test
- fun lowForegroundHighPriority() {
- val notification = mock(Notification::class.java)
- `when`(notification.isForegroundService).thenReturn(true)
-
- val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
- notification, mContext.user, "", 0)
-
- val e = NotificationEntryBuilder()
- .setNotification(notification)
- .setSbn(sbn)
- .build()
-
- modifyRanking(e)
- .setImportance(IMPORTANCE_LOW)
- .build()
-
- assertTrue(rankingManager.isHighPriority2(e))
- }
-
- @Test
- fun userChangeTrumpsHighPriorityCharacteristics() {
- val notification = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
- .build()
-
- val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
- notification, mContext.user, "", 0)
-
- `when`(personNotificationIdentifier.isPeopleNotification(sbn)).thenReturn(true)
-
- val channel = NotificationChannel("a", "a", IMPORTANCE_LOW)
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE)
-
- val e = NotificationEntryBuilder()
- .setSbn(sbn)
- .setChannel(channel)
- .build()
-
- assertFalse(rankingManager.isHighPriority2(e))
- }
-
- @Test
fun testSort_highPriorityTrumpsNMSRank() {
// NMS rank says A and then B. But A is not high priority and B is, so B should sort in
// front
- val aN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
val a = NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_LOW) // low priority
+ .setRank(1) // NMS says rank first
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
- .setNotification(aN)
+ .setNotification(
+ Notification.Builder(mContext, "test")
+ .build())
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
- a.setIsHighPriority(false)
-
- modifyRanking(a)
- .setImportance(IMPORTANCE_LOW)
- .setRank(1)
- .build()
-
- val bN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
val b = NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH) // high priority
+ .setRank(2) // NMS says rank second
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
- .setNotification(bN)
+ .setNotification(
+ Notification.Builder(mContext, "test")
+ .build())
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
- b.setIsHighPriority(true)
-
- modifyRanking(b)
- .setImportance(IMPORTANCE_LOW)
- .setRank(2)
- .build()
assertEquals(
listOf(b, a),
@@ -189,6 +109,8 @@
.setStyle(Notification.MessagingStyle(""))
.build()
val a = NotificationEntryBuilder()
+ .setRank(1)
+ .setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
@@ -196,17 +118,13 @@
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
- a.setIsHighPriority(false)
-
- modifyRanking(a)
- .setImportance(IMPORTANCE_LOW)
- .setRank(1)
- .build()
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val b = NotificationEntryBuilder()
+ .setRank(2)
+ .setImportance(IMPORTANCE_HIGH)
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
@@ -214,12 +132,6 @@
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
- b.setIsHighPriority(false)
-
- modifyRanking(b)
- .setImportance(IMPORTANCE_LOW)
- .setRank(2)
- .build()
assertEquals(
listOf(a, b),
@@ -239,7 +151,7 @@
.setOverrideGroupKey("")
.build()
- modifyRanking(e).setImportance(IMPORTANCE_DEFAULT) .build()
+ modifyRanking(e).setImportance(IMPORTANCE_DEFAULT).build()
rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
assertEquals(e.bucket, BUCKET_ALERTING)
@@ -281,11 +193,6 @@
sectionsFeatureManager,
peopleNotificationIdentifier
) {
-
- fun isHighPriority2(e: NotificationEntry): Boolean {
- return isHighPriority(e)
- }
-
fun applyTestRankingMap(r: RankingMap) {
rankingMap = r
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
new file mode 100644
index 0000000..11488a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.Person;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class IsHighPriorityProviderTest extends SysuiTestCase {
+ private IsHighPriorityProvider mIsHighPriorityProvider;
+
+ @Before
+ public void setup() {
+ mIsHighPriorityProvider = new IsHighPriorityProvider();
+ }
+
+ @Test
+ public void testCache() {
+ // GIVEN a notification with high importance
+ final NotificationEntry entryHigh = new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH)
+ .build();
+
+ // GIVEN notification with min importance
+ final NotificationEntry entryMin = new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_MIN)
+ .build();
+
+ // WHEN we get the value for the high priority entry
+ assertTrue(mIsHighPriorityProvider.get(entryHigh));
+
+ // THEN the value is cached, so even when passed an entryMin, we still get high priority
+ assertTrue(mIsHighPriorityProvider.get(entryMin));
+
+ // UNTIL the provider is invalidated
+ mIsHighPriorityProvider.invalidate();
+
+ // THEN the priority is recalculated
+ assertFalse(mIsHighPriorityProvider.get(entryMin));
+ }
+
+ @Test
+ public void highImportance() {
+ // GIVEN notification has high importance
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH)
+ .build();
+
+ // THEN it has high priority
+ assertTrue(mIsHighPriorityProvider.get(entry));
+ }
+
+ @Test
+ public void peopleNotification() {
+ // GIVEN notification is low importance but has a person associated with it
+ final Notification notification = new Notification.Builder(mContext, "test")
+ .addPerson(
+ new Person.Builder()
+ .setName("name")
+ .setKey("abc")
+ .setUri("uri")
+ .setBot(true)
+ .build())
+ .build();
+
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+
+ // THEN it has high priority
+ assertTrue(mIsHighPriorityProvider.get(entry));
+ }
+
+ @Test
+ public void messagingStyle() {
+ // GIVEN notification is low importance but has messaging style
+ final Notification notification = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle(""))
+ .build();
+
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .build();
+
+ // THEN it has high priority
+ assertTrue(mIsHighPriorityProvider.get(entry));
+ }
+
+ @Test
+ public void lowImportanceForeground() {
+ // GIVEN notification is low importance and is associated with a foreground service
+ final Notification notification = mock(Notification.class);
+ when(notification.isForegroundService()).thenReturn(true);
+
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+
+ // THEN it has high priority
+ assertTrue(mIsHighPriorityProvider.get(entry));
+ }
+
+ @Test
+ public void minImportanceForeground() {
+ // GIVEN notification is low importance and is associated with a foreground service
+ final Notification notification = mock(Notification.class);
+ when(notification.isForegroundService()).thenReturn(true);
+
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_MIN)
+ .build();
+
+ // THEN it does NOT have high priority
+ assertFalse(mIsHighPriorityProvider.get(entry));
+ }
+
+ @Test
+ public void userChangeTrumpsHighPriorityCharacteristics() {
+ // GIVEN notification has high priority characteristics but the user changed the importance
+ // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
+ final Notification notification = new Notification.Builder(mContext, "test")
+ .addPerson(
+ new Person.Builder()
+ .setName("name")
+ .setKey("abc")
+ .setUri("uri")
+ .setBot(true)
+ .build())
+ .setStyle(new Notification.MessagingStyle(""))
+ .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+ .build();
+
+ final NotificationChannel channel = new NotificationChannel("a", "a",
+ IMPORTANCE_LOW);
+ channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setChannel(channel)
+ .build();
+
+ // THEN it does NOT have high priority
+ assertFalse(mIsHighPriorityProvider.get(entry));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 43d39a2..ccc9496 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -20,6 +20,7 @@
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -383,15 +384,14 @@
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
row.setBlockingHelperShowing(true);
- modifyRanking(row.getEntry())
+ final NotificationEntry entry = row.getEntry();
+ modifyRanking(entry)
.setUserSentiment(USER_SENTIMENT_NEGATIVE)
- .setImportance(IMPORTANCE_DEFAULT)
+ .setImportance(IMPORTANCE_HIGH)
.build();
- row.getEntry().setIsHighPriority(true);
- when(row.getIsNonblockable()).thenReturn(false);
- StatusBarNotification statusBarNotification = row.getEntry().getSbn();
- NotificationEntry entry = row.getEntry();
+ when(row.getIsNonblockable()).thenReturn(false);
+ StatusBarNotification statusBarNotification = entry.getSbn();
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
verify(notificationInfoView).bindNotification(
@@ -408,7 +408,7 @@
eq(false),
eq(false),
eq(true) /* isForBlockingHelper */,
- eq(IMPORTANCE_DEFAULT),
+ eq(IMPORTANCE_HIGH),
eq(true) /* wasShownHighPriority */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4d6ff1f..008a349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -760,6 +760,17 @@
}
@Test
+ public void testWillHideDockedWallpaper() {
+ mAlwaysOnEnabled = false;
+ when(mDockManager.isDocked()).thenReturn(true);
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+
+ mScrimController.transitionTo(ScrimState.AOD);
+
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+ }
+
+ @Test
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.setPanelExpansion(0.5f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
new file mode 100644
index 0000000..a39fbc4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -0,0 +1,436 @@
+package com.android.systemui.util.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.util.ArrayMap
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringForce
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.animation.PhysicsAnimator.EndListener
+import com.android.systemui.util.animation.PhysicsAnimator.UpdateListener
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PhysicsAnimatorTest : SysuiTestCase() {
+ private lateinit var viewGroup: ViewGroup
+ private lateinit var testView: View
+ private lateinit var testView2: View
+
+ private lateinit var animator: PhysicsAnimator<View>
+
+ private val springConfig = PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ private val flingConfig = PhysicsAnimator.FlingConfig(2f)
+
+ private lateinit var mockUpdateListener: UpdateListener<View>
+ private lateinit var mockEndListener: EndListener<View>
+ private lateinit var mockEndAction: Runnable
+
+ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ mockUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+ mockEndListener = mock(EndListener::class.java) as EndListener<View>
+ mockEndAction = mock(Runnable::class.java)
+
+ viewGroup = FrameLayout(context)
+ testView = View(context)
+ testView2 = View(context)
+ viewGroup.addView(testView)
+ viewGroup.addView(testView2)
+
+ PhysicsAnimatorTestUtils.prepareForTest()
+
+ // Most of our tests involve checking the end state of animations, so we want calls that
+ // start animations to block the test thread until the animations have ended.
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+ animator = PhysicsAnimator.getInstance(testView)
+ }
+
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ }
+
+ @Test
+ fun testOneAnimatorPerView() {
+ assertEquals(animator, PhysicsAnimator.getInstance(testView))
+ assertEquals(PhysicsAnimator.getInstance(testView), PhysicsAnimator.getInstance(testView))
+ assertNotEquals(animator, PhysicsAnimator.getInstance(testView2))
+ }
+
+ @Test
+ fun testSpringOneProperty() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 50f, springConfig)
+ .start()
+
+ assertEquals(testView.translationX, 50f, 1f)
+ }
+
+ @Test
+ fun testSpringMultipleProperties() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+ .spring(DynamicAnimation.SCALE_Y, 1.1f, springConfig)
+ .start()
+
+ assertEquals(10f, testView.translationX, 1f)
+ assertEquals(50f, testView.translationY, 1f)
+ assertEquals(1.1f, testView.scaleY, 0.01f)
+ }
+
+ @Test
+ fun testFling() {
+ val startTime = System.currentTimeMillis()
+
+ animator
+ .fling(DynamicAnimation.TRANSLATION_X, 1000f /* startVelocity */, flingConfig)
+ .fling(DynamicAnimation.TRANSLATION_Y, 500f, flingConfig)
+ .start()
+
+ val elapsedTimeSeconds = (System.currentTimeMillis() - startTime) / 1000f
+
+ // If the fling worked, the view should be somewhere between its starting position and the
+ // and the theoretical no-friction maximum of startVelocity (in pixels per second)
+ // multiplied by elapsedTimeSeconds. We can't calculate an exact expected location for a
+ // fling, so this is close enough.
+ assertTrue(testView.translationX > 0f)
+ assertTrue(testView.translationX < 1000f * elapsedTimeSeconds)
+ assertTrue(testView.translationY > 0f)
+ assertTrue(testView.translationY < 500f * elapsedTimeSeconds)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testEndListenersAndActions() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 500f, springConfig)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // Once TRANSLATION_X is done, the view should be at x = 10...
+ assertEquals(10f, testView.translationX, 1f)
+
+ // / ...TRANSLATION_Y should still be running...
+ assertTrue(animator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+
+ // ...and our end listener should have been called with x = 10, velocity = 0, and allEnded =
+ // false since TRANSLATION_Y is still running.
+ verify(mockEndListener).onAnimationEnd(
+ testView,
+ DynamicAnimation.TRANSLATION_X,
+ canceled = false,
+ finalValue = 10f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = false)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The end action should not have been run yet.
+ verify(mockEndAction, times(0)).run()
+
+ // Block until TRANSLATION_Y finishes.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+ // The view should have been moved.
+ assertEquals(10f, testView.translationX, 1f)
+ assertEquals(500f, testView.translationY, 1f)
+
+ // The end listener should have been called, this time with TRANSLATION_Y, y = 50, and
+ // allEnded = true.
+ verify(mockEndListener).onAnimationEnd(
+ testView,
+ DynamicAnimation.TRANSLATION_Y,
+ canceled = false,
+ finalValue = 500f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // Now that all properties are done animating, the end action should have been called.
+ verify(mockEndAction, times(1)).run()
+ }
+
+ @Test
+ fun testUpdateListeners() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+ .addUpdateListener(object : UpdateListener<View> {
+ override fun onAnimationUpdateForProperty(
+ target: View,
+ values: UpdateMap<View>
+ ) {
+ mockUpdateListener.onAnimationUpdateForProperty(target, values)
+ }
+ })
+ .start()
+
+ verifyUpdateListenerCalls(animator, mockUpdateListener)
+ }
+
+ @Test
+ fun testListenersNotCalledOnSubsequentAnimations() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ verifyUpdateListenerCalls(animator, mockUpdateListener)
+ verify(mockEndListener, times(1)).onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
+ eq(true))
+ verify(mockEndAction, times(1)).run()
+
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 0f, springConfig)
+ .start()
+
+ // We didn't pass any of the listeners/actions to the subsequent animation, so they should
+ // never have been called.
+ verifyNoMoreInteractions(mockUpdateListener)
+ verifyNoMoreInteractions(mockEndListener)
+ verifyNoMoreInteractions(mockEndAction)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testAnimationsUpdatedWhileInMotion() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Spring towards x = 100f.
+ animator
+ .spring(
+ DynamicAnimation.TRANSLATION_X,
+ 100f,
+ springConfig)
+ .start()
+
+ // Block until it reaches x = 50f.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { view -> view.translationX > 50f }
+
+ // Translation X value at the time of reversing the animation to spring to x = 0f.
+ val reversalTranslationX = testView.translationX
+
+ // Spring back towards 0f.
+ animator
+ .spring(
+ DynamicAnimation.TRANSLATION_X,
+ 0f,
+ // Lower the stiffness to ensure the update listener receives at least one
+ // update frame where the view has continued to move to the right.
+ springConfig.apply { stiffness = SpringForce.STIFFNESS_LOW })
+ .start()
+
+ // Wait for TRANSLATION_X.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // Verify that the animation continued past the X value at the time of reversal, before
+ // springing back. This ensures the change in direction was not abrupt.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > reversalTranslationX },
+ { u -> u.value < reversalTranslationX })
+
+ // Verify that the view is where it should be.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testAnimationsUpdatedWhileInMotion_originalListenersStillCalled() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Spring TRANSLATION_X to 100f, with an update and end listener provided.
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Wait until the animation is halfway there.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { view -> view.translationX > 50f }
+
+ // The end listener shouldn't have been called since the animation hasn't ended.
+ verifyNoMoreInteractions(mockEndListener)
+
+ // Make sure we called the update listener with appropriate values.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > 0f },
+ { u -> u.value >= 50f })
+
+ // Mock a second end listener.
+ val secondEndListener = mock(EndListener::class.java) as EndListener<View>
+ val secondUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+
+ // Start a new animation that springs both TRANSLATION_X and TRANSLATION_Y, and provide it
+ // the second end listener. This new end listener should be called for the end of
+ // TRANSLATION_X and TRANSLATION_Y, with allEnded = true when both have ended.
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 200f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 4000f, springConfig)
+ .addUpdateListener(secondUpdateListener)
+ .addEndListener(secondEndListener)
+ .start()
+
+ // Wait for TRANSLATION_X to end.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // The update listener provided to the initial animation call (the one that only animated
+ // TRANSLATION_X) should have been called with values on the way to x = 200f. This is
+ // because the second animation call updated the original TRANSLATION_X animation.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > 100f }, { u -> u.value >= 200f })
+
+ // The original end listener should also have been called, with allEnded = true since it was
+ // provided to an animator that animated only TRANSLATION_X.
+ verify(mockEndListener, times(1))
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The second end listener should have been called, but with allEnded = false since it was
+ // provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
+ verify(secondEndListener, times(1))
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+ verifyNoMoreInteractions(secondEndListener)
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+ // The original end listener shouldn't receive any callbacks because it was not provided to
+ // an animator that animated TRANSLATION_Y.
+ verifyNoMoreInteractions(mockEndListener)
+
+ verify(secondEndListener, times(1))
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+ verifyNoMoreInteractions(secondEndListener)
+ }
+
+ @Test
+ fun testFlingRespectsMinMax() {
+ animator
+ .fling(DynamicAnimation.TRANSLATION_X,
+ startVelocity = 1000f,
+ friction = 1.1f,
+ max = 10f)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Ensure that the view stopped at x = 10f, and the end listener was called once with that
+ // value.
+ assertEquals(10f, testView.translationX, 1f)
+ verify(mockEndListener, times(1))
+ .onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
+ anyFloat(), eq(true))
+
+ animator
+ .fling(
+ DynamicAnimation.TRANSLATION_X,
+ startVelocity = -1000f,
+ friction = 1.1f,
+ min = -5f)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Ensure that the view stopped at x = -5f, and the end listener was called once with that
+ // value.
+ assertEquals(-5f, testView.translationX, 1f)
+ verify(mockEndListener, times(1))
+ .onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
+ anyFloat(), eq(true))
+ }
+
+ @Test
+ fun testExtensionProperty() {
+ testView
+ .physicsAnimator
+ .spring(DynamicAnimation.TRANSLATION_X, 200f)
+ .start()
+
+ assertEquals(200f, testView.translationX, 1f)
+ }
+
+ /**
+ * Verifies that the calls to the mock update listener match the animation update frames
+ * reported by the test internal listener, in order.
+ */
+ private fun <T : Any> verifyUpdateListenerCalls(
+ animator: PhysicsAnimator<T>,
+ mockUpdateListener: UpdateListener<T>
+ ) {
+ val updates = getAnimationUpdateFrames(animator)
+
+ for (invocation in Mockito.mockingDetails(mockUpdateListener).invocations) {
+
+ // Grab the update map of Property -> AnimationUpdate that was passed to the mock update
+ // listener.
+ val updateMap = invocation.arguments[1]
+ as ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+ //
+ for ((property, update) in updateMap) {
+ val updatesForProperty = updates[property]!!
+
+ // This update should be the next one in the list for this property.
+ if (update != updatesForProperty[0]) {
+ Assert.fail("The update listener was called with an unexpected value: $update.")
+ }
+
+ updatesForProperty.remove(update)
+ }
+
+ // Mark this invocation verified.
+ verify(mockUpdateListener).onAnimationUpdateForProperty(animator.target, updateMap)
+ }
+
+ verifyNoMoreInteractions(mockUpdateListener)
+
+ // Since we were removing values as matching invocations were found, there should no longer
+ // be any values remaining. If there are, it means the update listener wasn't notified when
+ // it should have been.
+ assertEquals(0,
+ updates.values.fold(0, { count, propertyUpdates -> count + propertyUpdates.size }))
+
+ clearAnimationUpdateFrames(animator)
+ }
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
deleted file mode 100644
index 3dfe59e..0000000
--- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
+++ /dev/null
@@ -1,640 +0,0 @@
-/*
- ** Copyright 2015, The Android Open Source Project
- **
- ** Licensed under the Apache License, Version 2.0 (the "License");
- ** you may not use this file except in compliance with the License.
- ** You may obtain a copy of the License at
- **
- ** http://www.apache.org/licenses/LICENSE-2.0
- **
- ** Unless required by applicable law or agreed to in writing, software
- ** distributed under the License is distributed on an "AS IS" BASIS,
- ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ** See the License for the specific language governing permissions and
- ** limitations under the License.
- */
-
-package com.android.server.accessibility.gestures;
-
-import android.accessibilityservice.AccessibilityGestureEvent;
-import android.accessibilityservice.AccessibilityService;
-import android.content.Context;
-import android.gesture.GesturePoint;
-import android.graphics.PointF;
-import android.util.Slog;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-
-/**
- * This class handles gesture detection for the Touch Explorer. It collects
- * touch events and determines when they match a gesture, as well as when they
- * won't match a gesture. These state changes are then surfaced to mListener.
- */
-class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListener {
-
- private static final boolean DEBUG = false;
-
- // Tag for logging received events.
- private static final String LOG_TAG = "AccessibilityGestureDetector";
-
- // Constants for sampling motion event points.
- // We sample based on a minimum distance between points, primarily to improve accuracy by
- // reducing noisy minor changes in direction.
- private static final float MIN_INCHES_BETWEEN_SAMPLES = 0.1f;
- private final float mMinPixelsBetweenSamplesX;
- private final float mMinPixelsBetweenSamplesY;
-
- // Constants for separating gesture segments
- private static final float ANGLE_THRESHOLD = 0.0f;
-
- // Constants for line segment directions
- private static final int LEFT = 0;
- private static final int RIGHT = 1;
- private static final int UP = 2;
- private static final int DOWN = 3;
- private static final int[][] DIRECTIONS_TO_GESTURE_ID = {
- {
- AccessibilityService.GESTURE_SWIPE_LEFT,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_RIGHT,
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP,
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_UP,
- AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP,
- AccessibilityService.GESTURE_SWIPE_DOWN
- }
- };
-
-
- /**
- * Listener functions are called as a result of onMoveEvent(). The current
- * MotionEvent in the context of these functions is the event passed into
- * onMotionEvent.
- */
- public interface Listener {
- /**
- * Called when the user has performed a double tap and then held down
- * the second tap.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- */
- void onDoubleTapAndHold(MotionEvent event, int policyFlags);
-
- /**
- * Called when the user lifts their finger on the second tap of a double
- * tap.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- *
- * @return true if the event is consumed, else false
- */
- boolean onDoubleTap(MotionEvent event, int policyFlags);
-
- /**
- * Called when the system has decided the event stream is a gesture.
- *
- * @return true if the event is consumed, else false
- */
- boolean onGestureStarted();
-
- /**
- * Called when an event stream is recognized as a gesture.
- *
- * @param gestureEvent Information about the gesture.
- *
- * @return true if the event is consumed, else false
- */
- boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
-
- /**
- * Called when the system has decided an event stream doesn't match any
- * known gesture.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- *
- * @return true if the event is consumed, else false
- */
- public boolean onGestureCancelled(MotionEvent event, int policyFlags);
- }
-
- private final Listener mListener;
- private final Context mContext; // Retained for on-demand construction of GestureDetector.
- private final GestureDetector mGestureDetector; // Double-tap detector.
-
- // Indicates that a single tap has occurred.
- private boolean mFirstTapDetected;
-
- // Indicates that the down event of a double tap has occured.
- private boolean mDoubleTapDetected;
-
- // Indicates that motion events are being collected to match a gesture.
- private boolean mRecognizingGesture;
-
- // Indicates that we've collected enough data to be sure it could be a
- // gesture.
- private boolean mGestureStarted;
-
- // Indicates that motion events from the second pointer are being checked
- // for a double tap.
- private boolean mSecondFingerDoubleTap;
-
- // Tracks the most recent time where ACTION_POINTER_DOWN was sent for the
- // second pointer.
- private long mSecondPointerDownTime;
-
- // Policy flags of the previous event.
- private int mPolicyFlags;
-
- // These values track the previous point that was saved to use for gesture
- // detection. They are only updated when the user moves more than the
- // recognition threshold.
- private float mPreviousGestureX;
- private float mPreviousGestureY;
-
- // These values track the previous point that was used to determine if there
- // was a transition into or out of gesture detection. They are updated when
- // the user moves more than the detection threshold.
- private float mBaseX;
- private float mBaseY;
- private long mBaseTime;
-
- // This is the calculated movement threshold used track if the user is still
- // moving their finger.
- private final float mGestureDetectionThreshold;
-
- // Buffer for storing points for gesture detection.
- private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
- // The minimal delta between moves to add a gesture point.
- private static final int TOUCH_TOLERANCE = 3;
-
- // The minimal score for accepting a predicted gesture.
- private static final float MIN_PREDICTION_SCORE = 2.0f;
-
- // Distance a finger must travel before we decide if it is a gesture or not.
- private static final int GESTURE_CONFIRM_MM = 10;
-
- // Time threshold used to determine if an interaction is a gesture or not.
- // If the first movement of 1cm takes longer than this value, we assume it's
- // a slow movement, and therefore not a gesture.
- //
- // This value was determined by measuring the time for the first 1cm
- // movement when gesturing, and touch exploring. Based on user testing,
- // all gestures started with the initial movement taking less than 100ms.
- // When touch exploring, the first movement almost always takes longer than
- // 200ms.
- private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
-
- // Time threshold used to determine if a gesture should be cancelled. If
- // the finger takes more than this time to move 1cm, the ongoing gesture is
- // cancelled.
- private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
-
- /**
- * Construct the gesture detector for {@link TouchExplorer}.
- *
- * @see #AccessibilityGestureDetector(Context, Listener, GestureDetector)
- */
- AccessibilityGestureDetector(Context context, Listener listener) {
- this(context, listener, null);
- }
-
- /**
- * Construct the gesture detector for {@link TouchExplorer}.
- *
- * @param context A context handle for accessing resources.
- * @param listener A listener to callback with gesture state or information.
- * @param detector The gesture detector to handle touch event. If null the default one created
- * in place, or for testing purpose.
- */
- AccessibilityGestureDetector(Context context, Listener listener, GestureDetector detector) {
- mListener = listener;
- mContext = context;
-
- // Break the circular dependency between constructors and let the class to be testable
- if (detector == null) {
- mGestureDetector = new GestureDetector(context, this);
- } else {
- mGestureDetector = detector;
- }
- mGestureDetector.setOnDoubleTapListener(this);
- mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
- context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
-
- // Calculate minimum gesture velocity
- final float pixelsPerInchX = context.getResources().getDisplayMetrics().xdpi;
- final float pixelsPerInchY = context.getResources().getDisplayMetrics().ydpi;
- mMinPixelsBetweenSamplesX = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchX;
- mMinPixelsBetweenSamplesY = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchY;
- }
-
- /**
- * Handle a motion event. If an action is completed, the appropriate
- * callback on mListener is called, and the return value of the callback is
- * passed to the caller.
- *
- * @param event The transformed motion event to be handled.
- * @param rawEvent The raw motion event. It's important that this be the raw
- * event, before any transformations have been applied, so that measurements
- * can be made in physical units.
- * @param policyFlags Policy flags for the event.
- *
- * @return true if the event is consumed, else false
- */
- public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- // The accessibility gesture detector is interested in the movements in physical space,
- // so it uses the rawEvent to ignore magnification and other transformations.
- final float x = rawEvent.getX();
- final float y = rawEvent.getY();
- final long time = rawEvent.getEventTime();
-
- mPolicyFlags = policyFlags;
- switch (rawEvent.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDoubleTapDetected = false;
- mSecondFingerDoubleTap = false;
- mRecognizingGesture = true;
- mGestureStarted = false;
- mPreviousGestureX = x;
- mPreviousGestureY = y;
- mStrokeBuffer.clear();
- mStrokeBuffer.add(new GesturePoint(x, y, time));
-
- mBaseX = x;
- mBaseY = y;
- mBaseTime = time;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (mRecognizingGesture) {
- final float deltaX = mBaseX - x;
- final float deltaY = mBaseY - y;
- final double moveDelta = Math.hypot(deltaX, deltaY);
- if (moveDelta > mGestureDetectionThreshold) {
- // If the pointer has moved more than the threshold,
- // update the stored values.
- mBaseX = x;
- mBaseY = y;
- mBaseTime = time;
-
- // Since the pointer has moved, this is not a double
- // tap.
- mFirstTapDetected = false;
- mDoubleTapDetected = false;
-
- // If this hasn't been confirmed as a gesture yet, send
- // the event.
- if (!mGestureStarted) {
- mGestureStarted = true;
- return mListener.onGestureStarted();
- }
- } else if (!mFirstTapDetected) {
- // The finger may not move if they are double tapping.
- // In that case, we shouldn't cancel the gesture.
- final long timeDelta = time - mBaseTime;
- final long threshold = mGestureStarted ?
- CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
- CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
-
- // If the pointer hasn't moved for longer than the
- // timeout, cancel gesture detection.
- if (timeDelta > threshold) {
- cancelGesture();
- return mListener.onGestureCancelled(rawEvent, policyFlags);
- }
- }
-
- final float dX = Math.abs(x - mPreviousGestureX);
- final float dY = Math.abs(y - mPreviousGestureY);
- if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
- mPreviousGestureX = x;
- mPreviousGestureY = y;
- mStrokeBuffer.add(new GesturePoint(x, y, time));
- }
- }
- break;
-
- case MotionEvent.ACTION_UP:
- if (mDoubleTapDetected) {
- return finishDoubleTap(rawEvent, policyFlags);
- }
- if (mGestureStarted) {
- final float dX = Math.abs(x - mPreviousGestureX);
- final float dY = Math.abs(y - mPreviousGestureY);
- if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
- mStrokeBuffer.add(new GesturePoint(x, y, time));
- }
- return recognizeGesture(rawEvent, policyFlags);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN:
- // Once a second finger is used, we're definitely not
- // recognizing a gesture.
- cancelGesture();
-
- if (rawEvent.getPointerCount() == 2) {
- // If this was the second finger, attempt to recognize double
- // taps on it.
- mSecondFingerDoubleTap = true;
- mSecondPointerDownTime = time;
- } else {
- // If there are more than two fingers down, stop watching
- // for a double tap.
- mSecondFingerDoubleTap = false;
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- // If we're detecting taps on the second finger, see if we
- // should finish the double tap.
- if (mSecondFingerDoubleTap && mDoubleTapDetected) {
- return finishDoubleTap(rawEvent, policyFlags);
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- clear();
- break;
- }
-
- // If we're detecting taps on the second finger, map events from the
- // finger to the first finger.
- if (mSecondFingerDoubleTap) {
- MotionEvent newEvent = mapSecondPointerToFirstPointer(rawEvent);
- if (newEvent == null) {
- return false;
- }
- boolean handled = mGestureDetector.onTouchEvent(newEvent);
- newEvent.recycle();
- return handled;
- }
-
- if (!mRecognizingGesture) {
- return false;
- }
-
- // Pass the transformed event on to the standard gesture detector.
- return mGestureDetector.onTouchEvent(event);
- }
-
- public void clear() {
- mFirstTapDetected = false;
- mDoubleTapDetected = false;
- mSecondFingerDoubleTap = false;
- mGestureStarted = false;
- mGestureDetector.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_CANCEL,
- 0.0f, 0.0f, 0));
- cancelGesture();
- }
-
-
- @Override
- public void onLongPress(MotionEvent e) {
- maybeSendLongPress(e, mPolicyFlags);
- }
-
- @Override
- public boolean onSingleTapUp(MotionEvent event) {
- mFirstTapDetected = true;
- return false;
- }
-
- @Override
- public boolean onSingleTapConfirmed(MotionEvent event) {
- clear();
- return false;
- }
-
- @Override
- public boolean onDoubleTap(MotionEvent event) {
- // The processing of the double tap is deferred until the finger is
- // lifted, so that we can detect a long press on the second tap.
- mDoubleTapDetected = true;
- return false;
- }
-
- private void maybeSendLongPress(MotionEvent event, int policyFlags) {
- if (!mDoubleTapDetected) {
- return;
- }
-
- clear();
-
- mListener.onDoubleTapAndHold(event, policyFlags);
- }
-
- private boolean finishDoubleTap(MotionEvent event, int policyFlags) {
- clear();
-
- return mListener.onDoubleTap(event, policyFlags);
- }
-
- private void cancelGesture() {
- mRecognizingGesture = false;
- mGestureStarted = false;
- mStrokeBuffer.clear();
- }
-
- /**
- * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
- * Listener callbacks for success or failure.
- *
- * @param event The raw motion event to pass to the listener callbacks.
- * @param policyFlags Policy flags for the event.
- *
- * @return true if the event is consumed, else false
- */
- private boolean recognizeGesture(MotionEvent event, int policyFlags) {
- if (mStrokeBuffer.size() < 2) {
- return mListener.onGestureCancelled(event, policyFlags);
- }
-
- // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
- // direction change.
- // Method: for each sampled motion event, check the angle of the most recent motion vector
- // versus the preceding motion vector, and segment the line if the angle is about
- // 90 degrees.
-
- ArrayList<PointF> path = new ArrayList<>();
- PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
- path.add(lastDelimiter);
-
- float dX = 0; // Sum of unit vectors from last delimiter to each following point
- float dY = 0;
- int count = 0; // Number of points since last delimiter
- float length = 0; // Vector length from delimiter to most recent point
-
- PointF next = new PointF();
- for (int i = 1; i < mStrokeBuffer.size(); ++i) {
- next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
- if (count > 0) {
- // Average of unit vectors from delimiter to following points
- float currentDX = dX / count;
- float currentDY = dY / count;
-
- // newDelimiter is a possible new delimiter, based on a vector with length from
- // the last delimiter to the previous point, but in the direction of the average
- // unit vector from delimiter to previous points.
- // Using the averaged vector has the effect of "squaring off the curve",
- // creating a sharper angle between the last motion and the preceding motion from
- // the delimiter. In turn, this sharper angle achieves the splitting threshold
- // even in a gentle curve.
- PointF newDelimiter = new PointF(length * currentDX + lastDelimiter.x,
- length * currentDY + lastDelimiter.y);
-
- // Unit vector from newDelimiter to the most recent point
- float nextDX = next.x - newDelimiter.x;
- float nextDY = next.y - newDelimiter.y;
- float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
- nextDX = nextDX / nextLength;
- nextDY = nextDY / nextLength;
-
- // Compare the initial motion direction to the most recent motion direction,
- // and segment the line if direction has changed by about 90 degrees.
- float dot = currentDX * nextDX + currentDY * nextDY;
- if (dot < ANGLE_THRESHOLD) {
- path.add(newDelimiter);
- lastDelimiter = newDelimiter;
- dX = 0;
- dY = 0;
- count = 0;
- }
- }
-
- // Vector from last delimiter to most recent point
- float currentDX = next.x - lastDelimiter.x;
- float currentDY = next.y - lastDelimiter.y;
- length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
-
- // Increment sum of unit vectors from delimiter to each following point
- count = count + 1;
- dX = dX + currentDX / length;
- dY = dY + currentDY / length;
- }
-
- path.add(next);
- Slog.i(LOG_TAG, "path=" + path.toString());
-
- // Classify line segments, and call Listener callbacks.
- return recognizeGesturePath(event, policyFlags, path);
- }
-
- /**
- * Classifies a pair of line segments, by direction.
- * Calls Listener callbacks for success or failure.
- *
- * @param event The raw motion event to pass to the listener's onGestureCanceled method.
- * @param policyFlags Policy flags for the event.
- * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
- *
- * @return true if the event is consumed, else false
- */
- private boolean recognizeGesturePath(MotionEvent event, int policyFlags,
- ArrayList<PointF> path) {
-
- final int displayId = event.getDisplayId();
- if (path.size() == 2) {
- PointF start = path.get(0);
- PointF end = path.get(1);
-
- float dX = end.x - start.x;
- float dY = end.y - start.y;
- int direction = toDirection(dX, dY);
- switch (direction) {
- case LEFT:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_LEFT,
- displayId));
- case RIGHT:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_RIGHT,
- displayId));
- case UP:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_UP,
- displayId));
- case DOWN:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_DOWN,
- displayId));
- default:
- // Do nothing.
- }
-
- } else if (path.size() == 3) {
- PointF start = path.get(0);
- PointF mid = path.get(1);
- PointF end = path.get(2);
-
- float dX0 = mid.x - start.x;
- float dY0 = mid.y - start.y;
-
- float dX1 = end.x - mid.x;
- float dY1 = end.y - mid.y;
-
- int segmentDirection0 = toDirection(dX0, dY0);
- int segmentDirection1 = toDirection(dX1, dY1);
- int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(gestureId, displayId));
- }
- // else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
- return mListener.onGestureCancelled(event, policyFlags);
- }
-
- /** Maps a vector to a dominant direction in set {LEFT, RIGHT, UP, DOWN}. */
- private static int toDirection(float dX, float dY) {
- if (Math.abs(dX) > Math.abs(dY)) {
- // Horizontal
- return (dX < 0) ? LEFT : RIGHT;
- } else {
- // Vertical
- return (dY < 0) ? UP : DOWN;
- }
- }
-
- private MotionEvent mapSecondPointerToFirstPointer(MotionEvent event) {
- // Only map basic events when two fingers are down.
- if (event.getPointerCount() != 2 ||
- (event.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN &&
- event.getActionMasked() != MotionEvent.ACTION_POINTER_UP &&
- event.getActionMasked() != MotionEvent.ACTION_MOVE)) {
- return null;
- }
-
- int action = event.getActionMasked();
-
- if (action == MotionEvent.ACTION_POINTER_DOWN) {
- action = MotionEvent.ACTION_DOWN;
- } else if (action == MotionEvent.ACTION_POINTER_UP) {
- action = MotionEvent.ACTION_UP;
- }
-
- // Map the information from the second pointer to the first.
- return MotionEvent.obtain(mSecondPointerDownTime, event.getEventTime(), action,
- event.getX(1), event.getY(1), event.getPressure(1), event.getSize(1),
- event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
- event.getDeviceId(), event.getEdgeFlags());
- }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
new file mode 100644
index 0000000..9b7adc8
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+
+import static com.android.server.accessibility.gestures.Swipe.DOWN;
+import static com.android.server.accessibility.gestures.Swipe.LEFT;
+import static com.android.server.accessibility.gestures.Swipe.RIGHT;
+import static com.android.server.accessibility.gestures.Swipe.UP;
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class coordinates a series of individual gesture matchers to serve as a unified gesture
+ * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions
+ * when a gesture starts or completes.
+ */
+class GestureManifold implements GestureMatcher.StateChangeListener {
+
+ private static final String LOG_TAG = "GestureManifold";
+
+ private final List<GestureMatcher> mGestures = new ArrayList<>();
+ private final Context mContext;
+ // Handler for performing asynchronous operations.
+ private final Handler mHandler;
+ // Listener to be notified of gesture start and end.
+ private Listener mListener;
+ // Shared state information.
+ private TouchState mState;
+
+ GestureManifold(Context context, Listener listener, TouchState state) {
+ mContext = context;
+ mHandler = new Handler(context.getMainLooper());
+ mListener = listener;
+ mState = state;
+ // Set up gestures.
+ // Start with double tap.
+ mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
+ mGestures.add(new MultiTapAndHold(context, 2, GESTURE_DOUBLE_TAP_AND_HOLD, this));
+ // One-direction swipes.
+ mGestures.add(new Swipe(context, RIGHT, GESTURE_SWIPE_RIGHT, this));
+ mGestures.add(new Swipe(context, LEFT, GESTURE_SWIPE_LEFT, this));
+ mGestures.add(new Swipe(context, UP, GESTURE_SWIPE_UP, this));
+ mGestures.add(new Swipe(context, DOWN, GESTURE_SWIPE_DOWN, this));
+ // Two-direction swipes.
+ mGestures.add(new Swipe(context, LEFT, RIGHT, GESTURE_SWIPE_LEFT_AND_RIGHT, this));
+ mGestures.add(new Swipe(context, LEFT, UP, GESTURE_SWIPE_LEFT_AND_UP, this));
+ mGestures.add(new Swipe(context, LEFT, DOWN, GESTURE_SWIPE_LEFT_AND_DOWN, this));
+ mGestures.add(new Swipe(context, RIGHT, UP, GESTURE_SWIPE_RIGHT_AND_UP, this));
+ mGestures.add(new Swipe(context, RIGHT, DOWN, GESTURE_SWIPE_RIGHT_AND_DOWN, this));
+ mGestures.add(new Swipe(context, RIGHT, LEFT, GESTURE_SWIPE_RIGHT_AND_LEFT, this));
+ mGestures.add(new Swipe(context, DOWN, UP, GESTURE_SWIPE_DOWN_AND_UP, this));
+ mGestures.add(new Swipe(context, DOWN, LEFT, GESTURE_SWIPE_DOWN_AND_LEFT, this));
+ mGestures.add(new Swipe(context, DOWN, RIGHT, GESTURE_SWIPE_DOWN_AND_RIGHT, this));
+ mGestures.add(new Swipe(context, UP, DOWN, GESTURE_SWIPE_UP_AND_DOWN, this));
+ mGestures.add(new Swipe(context, UP, LEFT, GESTURE_SWIPE_UP_AND_LEFT, this));
+ mGestures.add(new Swipe(context, UP, RIGHT, GESTURE_SWIPE_UP_AND_RIGHT, this));
+ }
+
+ /**
+ * Processes a motion event.
+ *
+ * @param event The event as received from the previous entry in the event stream.
+ * @param rawEvent The event without any transformations e.g. magnification.
+ * @param policyFlags
+ * @return True if the event has been appropriately handled by the gesture manifold and related
+ * callback functions, false if it should be handled further by the calling function.
+ */
+ boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mState.isClear()) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // Sanity safeguard: if touch state is clear, then matchers should always be clear
+ // before processing the next down event.
+ clear();
+ } else {
+ // If for some reason other events come through while in the clear state they could
+ // compromise the state of particular matchers, so we just ignore them.
+ return false;
+ }
+ }
+ for (GestureMatcher matcher : mGestures) {
+ if (matcher.getState() != GestureMatcher.STATE_GESTURE_CANCELED) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, matcher.toString());
+ }
+ matcher.onMotionEvent(event, rawEvent, policyFlags);
+ if (DEBUG) {
+ Slog.d(LOG_TAG, matcher.toString());
+ }
+ if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
+ // Here we just clear and return. The actual gesture dispatch is done in
+ // onStateChanged().
+ clear();
+ // No need to process this event any further.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void clear() {
+ for (GestureMatcher matcher : mGestures) {
+ matcher.clear();
+ }
+ }
+
+ /**
+ * Listener that receives notifications of the state of the gesture detector. Listener functions
+ * are called as a result of onMotionEvent(). The current MotionEvent in the context of these
+ * functions is the event passed into onMotionEvent.
+ */
+ public interface Listener {
+ /**
+ * Called when the user has performed a double tap and then held down the second tap.
+ */
+ void onDoubleTapAndHold();
+
+ /**
+ * Called when the user lifts their finger on the second tap of a double tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap();
+
+ /**
+ * Called when the system has decided the event stream is a gesture.
+ *
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureStarted();
+
+ /**
+ * Called when an event stream is recognized as a gesture.
+ *
+ * @param gestureEvent Information about the gesture.
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
+
+ /**
+ * Called when the system has decided an event stream doesn't match any known gesture.
+ *
+ * @param event The most recent MotionEvent received.
+ * @param policyFlags The policy flags of the most recent event.
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ }
+
+ @Override
+ public void onStateChanged(
+ int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (state == GestureMatcher.STATE_GESTURE_STARTED && !mState.isGestureDetecting()) {
+ mListener.onGestureStarted();
+ } else if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
+ onGestureCompleted(gestureId);
+ } else if (state == GestureMatcher.STATE_GESTURE_CANCELED && mState.isGestureDetecting()) {
+ // We only want to call the cancelation callback if there are no other pending
+ // detectors.
+ for (GestureMatcher matcher : mGestures) {
+ if (matcher.getState() == GestureMatcher.STATE_GESTURE_STARTED) {
+ return;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Cancelling.");
+ }
+ mListener.onGestureCancelled(event, rawEvent, policyFlags);
+ }
+ }
+
+ private void onGestureCompleted(int gestureId) {
+ MotionEvent event = mState.getLastReceivedEvent();
+ // Note that gestures that complete immediately call clear() from onMotionEvent.
+ // Gestures that complete on a delay call clear() here.
+ switch (gestureId) {
+ case GESTURE_DOUBLE_TAP:
+ mListener.onDoubleTap();
+ clear();
+ break;
+ case GESTURE_DOUBLE_TAP_AND_HOLD:
+ mListener.onDoubleTapAndHold();
+ clear();
+ break;
+ default:
+ AccessibilityGestureEvent gestureEvent =
+ new AccessibilityGestureEvent(gestureId, event.getDisplayId());
+ mListener.onGestureCompleted(gestureEvent);
+ break;
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
new file mode 100644
index 0000000..0b30ff57
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class describes a common base for gesture matchers. A gesture matcher checks a series of
+ * motion events against a single gesture. Coordinating the individual gesture matchers is done by
+ * the GestureManifold. To create a new Gesture, extend this class and override the onDown, onMove,
+ * onUp, etc methods as necessary. If you don't override a method your matcher will do nothing in
+ * response to that type of event. Finally, be sure to give your gesture a name by overriding
+ * getGestureName().
+ */
+abstract class GestureMatcher {
+ // Potential states for this individual gesture matcher.
+ // In STATE_CLEAR, this matcher is accepting new motion events but has not formally signaled
+ // that there is enough data to judge that a gesture has started.
+ static final int STATE_CLEAR = 0;
+ // In STATE_GESTURE_STARTED, this matcher continues to accept motion events and it has signaled
+ // to the gesture manifold that what looks like the specified gesture has started.
+ static final int STATE_GESTURE_STARTED = 1;
+ // In STATE_GESTURE_COMPLETED, this matcher has successfully matched the specified gesture. and
+ // will not accept motion events until it is cleared.
+ static final int STATE_GESTURE_COMPLETED = 2;
+ // In STATE_GESTURE_CANCELED, this matcher will not accept new motion events because it is
+ // impossible that this set of motion events will match the specified gesture.
+ static final int STATE_GESTURE_CANCELED = 3;
+
+ @IntDef({STATE_CLEAR, STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELED})
+ public @interface State {}
+
+ @State private int mState = STATE_CLEAR;
+ // The id number of the gesture that gets passed to accessibility services.
+ private final int mGestureId;
+ // handler for asynchronous operations like timeouts
+ private final Handler mHandler;
+
+ private final StateChangeListener mListener;
+
+ // Use this to transition to new states after a delay.
+ // e.g. cancel or complete after some timeout.
+ // Convenience functions for tapTimeout and doubleTapTimeout are already defined here.
+ protected final DelayedTransition mDelayedTransition;
+
+ GestureMatcher(int gestureId, Handler handler, StateChangeListener listener) {
+ mGestureId = gestureId;
+ mHandler = handler;
+ mDelayedTransition = new DelayedTransition();
+ mListener = listener;
+ }
+
+ /**
+ * Resets all state information for this matcher. Subclasses that include their own state
+ * information should override this method to reset their own state information and call
+ * super.clear().
+ */
+ protected void clear() {
+ mState = STATE_CLEAR;
+ cancelPendingTransitions();
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Transitions to a new state and notifies any listeners. Note that any pending transitions are
+ * canceled.
+ */
+ private void setState(
+ @State int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mState = state;
+ cancelPendingTransitions();
+ mListener.onStateChanged(mGestureId, mState, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates that there is evidence to suggest that this gesture has started. */
+ protected final void startGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_STARTED, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates this stream of motion events can no longer match this gesture. */
+ protected final void cancelGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates this gesture is completed. */
+ protected final void completeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_COMPLETED, event, rawEvent, policyFlags);
+ }
+
+ public int getGestureId() {
+ return mGestureId;
+ }
+
+ /**
+ * Process a motion event and attempt to match it to this gesture.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ * @return the state of this matcher.
+ */
+ public final int onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mState == STATE_GESTURE_CANCELED || mState == STATE_GESTURE_COMPLETED) {
+ return mState;
+ }
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ onDown(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ onPointerDown(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ onMove(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onPointerUp(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_UP:
+ onUp(event, rawEvent, policyFlags);
+ break;
+ default:
+ // Cancel because of invalid event.
+ setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+ break;
+ }
+ return mState;
+ }
+
+ /**
+ * Matchers override this method to respond to ACTION_DOWN events. ACTION_DOWN events indicate
+ * the first finger has touched the screen. If not overridden the default response is to do
+ * nothing.
+ */
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_POINTER_DOWN events. ACTION_POINTER_DOWN
+ * indicates that more than one finger has touched the screen. If not overridden the default
+ * response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_MOVE events. ACTION_MOVE indicates that
+ * one or fingers has moved. If not overridden the default response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_POINTER_UP events. ACTION_POINTER_UP
+ * indicates that a finger has lifted from the screen but at least one finger continues to touch
+ * the screen. If not overridden the default response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_UP events. ACTION_UP indicates that there
+ * are no more fingers touching the screen. If not overridden the default response is to do
+ * nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /** Cancels this matcher after the tap timeout. Any pending state transitions are removed. */
+ protected void cancelAfterTapTimeout(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /** Cancels this matcher after the double tap timeout. Any pending cancelations are removed. */
+ protected final void cancelAfterDoubleTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Cancels this matcher after the specified timeout. Any pending cancelations are removed. Used
+ * to prevent this matcher from accepting motion events until it is cleared.
+ */
+ protected final void cancelAfter(
+ long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mDelayedTransition.cancel();
+ mDelayedTransition.post(STATE_GESTURE_CANCELED, timeout, event, rawEvent, policyFlags);
+ }
+
+ /** Cancels any delayed transitions between states scheduled for this matcher. */
+ protected final void cancelPendingTransitions() {
+ mDelayedTransition.cancel();
+ }
+
+ /**
+ * Signals that this gesture has been completed after the tap timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfterLongPressTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getLongPressTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the tap timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfterTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the specified timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfter(
+ long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mDelayedTransition.cancel();
+ mDelayedTransition.post(STATE_GESTURE_COMPLETED, timeout, event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the double-tap timeout has expired. Used
+ * to ensure that there is no conflict with another gesture or for gestures that explicitly
+ * require a hold.
+ */
+ protected final void completeAfterDoubleTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ public static String getStateSymbolicName(@State int state) {
+ switch (state) {
+ case STATE_CLEAR:
+ return "STATE_CLEAR";
+ case STATE_GESTURE_STARTED:
+ return "STATE_GESTURE_STARTED";
+ case STATE_GESTURE_COMPLETED:
+ return "STATE_GESTURE_COMPLETED";
+ case STATE_GESTURE_CANCELED:
+ return "STATE_GESTURE_CANCELED";
+ default:
+ return "Unknown state: " + state;
+ }
+ }
+
+ /**
+ * Returns a readable name for this matcher that can be displayed to the user and in system
+ * logs.
+ */
+ abstract String getGestureName();
+
+ /**
+ * Returns a String representation of this matcher. Each matcher can override this method to add
+ * extra state information to the string representation.
+ */
+ public String toString() {
+ return getGestureName() + ":" + getStateSymbolicName(mState);
+ }
+
+ /** This class allows matchers to transition between states on a delay. */
+ protected final class DelayedTransition implements Runnable {
+
+ private static final String LOG_TAG = "GestureMatcher.DelayedTransition";
+ int mTargetState;
+ MotionEvent mEvent;
+ MotionEvent mRawEvent;
+ int mPolicyFlags;
+
+ public void cancel() {
+ // Avoid meaningless debug messages.
+ if (DEBUG && isPending()) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": canceling delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ mHandler.removeCallbacks(this);
+ }
+
+ public void post(
+ int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mTargetState = state;
+ mEvent = event;
+ mRawEvent = rawEvent;
+ mPolicyFlags = policyFlags;
+ mHandler.postDelayed(this, delay);
+ if (DEBUG) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": posting delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ }
+
+ public boolean isPending() {
+ return mHandler.hasCallbacks(this);
+ }
+
+ public void forceSendAndRemove() {
+ if (isPending()) {
+ run();
+ cancel();
+ }
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": executing delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ setState(mTargetState, mEvent, mRawEvent, mPolicyFlags);
+ }
+ }
+
+ /** Interface to allow a class to listen for state changes in a specific gesture matcher */
+ interface StateChangeListener {
+
+ void onStateChanged(
+ int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
new file mode 100644
index 0000000..2891c6c
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class matches multi-tap gestures. The number of taps for each instance is specified in the
+ * constructor.
+ */
+class MultiTap extends GestureMatcher {
+
+ // Maximum reasonable number of taps.
+ public static final int MAX_TAPS = 10;
+ final int mTargetTaps;
+ // The acceptable distance between two taps
+ int mDoubleTapSlop;
+ // The acceptable distance the pointer can move and still count as a tap.
+ int mTouchSlop;
+ int mTapTimeout;
+ int mDoubleTapTimeout;
+ int mCurrentTaps;
+ float mBaseX;
+ float mBaseY;
+
+ MultiTap(Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+ super(gesture, new Handler(context.getMainLooper()), listener);
+ mTargetTaps = taps;
+ mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ clear();
+ }
+
+ @Override
+ protected void clear() {
+ mCurrentTaps = 0;
+ mBaseX = Float.NaN;
+ mBaseY = Float.NaN;
+ super.clear();
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterTapTimeout(event, rawEvent, policyFlags);
+ if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+ mBaseX = event.getX();
+ mBaseY = event.getY();
+ }
+ if (!isInsideSlop(rawEvent, mDoubleTapSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ mBaseX = event.getX();
+ mBaseY = event.getY();
+ }
+
+ @Override
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ if (!isInsideSlop(rawEvent, mTouchSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) {
+ mCurrentTaps++;
+ if (mCurrentTaps == mTargetTaps) {
+ // Done.
+ completeAfterTapTimeout(event, rawEvent, policyFlags);
+ return;
+ }
+ // Needs more taps.
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ } else {
+ // Either too many taps or nonsensical event stream.
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (!isInsideSlop(rawEvent, mTouchSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ public String getGestureName() {
+ switch (mTargetTaps) {
+ case 2:
+ return "Double Tap";
+ case 3:
+ return "Triple Tap";
+ default:
+ return Integer.toString(mTargetTaps) + " Taps";
+ }
+ }
+
+ private boolean isInsideSlop(MotionEvent rawEvent, int slop) {
+ final float deltaX = mBaseX - rawEvent.getX();
+ final float deltaY = mBaseY - rawEvent.getY();
+ if (deltaX == 0 && deltaY == 0) {
+ return true;
+ }
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ return moveDelta <= slop;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString()
+ + ", Taps:"
+ + mCurrentTaps
+ + ", mBaseX: "
+ + Float.toString(mBaseX)
+ + ", mBaseY: "
+ + Float.toString(mBaseY);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
new file mode 100644
index 0000000..6a1f1a5
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * This class matches gestures of the form multi-tap and hold. The number of taps for each instance
+ * is specified in the constructor.
+ */
+class MultiTapAndHold extends MultiTap {
+ MultiTapAndHold(
+ Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+ super(context, taps, gesture, listener);
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ super.onDown(event, rawEvent, policyFlags);
+ if (mCurrentTaps + 1 == mTargetTaps) {
+ completeAfterLongPressTimeout(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ public String getGestureName() {
+ switch (mTargetTaps) {
+ case 2:
+ return "Double Tap and Hold";
+ case 3:
+ return "Triple Tap and Hold";
+ default:
+ return Integer.toString(mTargetTaps) + " Taps and Hold";
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
new file mode 100644
index 0000000..b246c67
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.content.Context;
+import android.gesture.GesturePoint;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for matching one-finger swipe gestures. Each instance matches one swipe
+ * gesture. A swipe is specified as a series of one or more directions e.g. left, left and up, etc.
+ * At this time swipes with more than two directions are not supported.
+ */
+class Swipe extends GestureMatcher {
+
+ // Direction constants.
+ public static final int LEFT = 0;
+ public static final int RIGHT = 1;
+ public static final int UP = 2;
+ public static final int DOWN = 3;
+ // This is the calculated movement threshold used track if the user is still
+ // moving their finger.
+ private final float mGestureDetectionThreshold;
+
+ // Buffer for storing points for gesture detection.
+ private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+ // The minimal delta between moves to add a gesture point.
+ private static final int TOUCH_TOLERANCE_PIX = 3;
+
+ // The minimal score for accepting a predicted gesture.
+ private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+ // Distance a finger must travel before we decide if it is a gesture or not.
+ private static final int GESTURE_CONFIRM_CM = 1;
+
+ // Time threshold used to determine if an interaction is a gesture or not.
+ // If the first movement of 1cm takes longer than this value, we assume it's
+ // a slow movement, and therefore not a gesture.
+ //
+ // This value was determined by measuring the time for the first 1cm
+ // movement when gesturing, and touch exploring. Based on user testing,
+ // all gestures started with the initial movement taking less than 100ms.
+ // When touch exploring, the first movement almost always takes longer than
+ // 200ms.
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
+
+ // Time threshold used to determine if a gesture should be cancelled. If
+ // the finger takes more than this time to move 1cm, the ongoing gesture is
+ // cancelled.
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
+
+ private int[] mDirections;
+ private float mBaseX;
+ private float mBaseY;
+ private long mBaseTime;
+ private float mPreviousGestureX;
+ private float mPreviousGestureY;
+ // Constants for sampling motion event points.
+ // We sample based on a minimum distance between points, primarily to improve accuracy by
+ // reducing noisy minor changes in direction.
+ private static final float MIN_CM_BETWEEN_SAMPLES = 0.25f;
+ private final float mMinPixelsBetweenSamplesX;
+ private final float mMinPixelsBetweenSamplesY;
+
+ // Constants for separating gesture segments
+ private static final float ANGLE_THRESHOLD = 0.0f;
+
+ Swipe(
+ Context context,
+ int direction,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ this(context, new int[] {direction}, gesture, listener);
+ }
+
+ Swipe(
+ Context context,
+ int direction1,
+ int direction2,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ this(context, new int[] {direction1, direction2}, gesture, listener);
+ }
+
+ private Swipe(
+ Context context,
+ int[] directions,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ super(gesture, new Handler(context.getMainLooper()), listener);
+ mDirections = directions;
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ mGestureDetectionThreshold =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, displayMetrics)
+ * GESTURE_CONFIRM_CM;
+ // Calculate minimum gesture velocity
+ final float pixelsPerCmX = displayMetrics.xdpi / 2.54f;
+ final float pixelsPerCmY = displayMetrics.ydpi / 2.54f;
+ mMinPixelsBetweenSamplesX = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmX;
+ mMinPixelsBetweenSamplesY = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmY;
+ clear();
+ }
+
+ @Override
+ protected void clear() {
+ mBaseX = Float.NaN;
+ mBaseY = Float.NaN;
+ mBaseTime = 0;
+ mStrokeBuffer.clear();
+ super.clear();
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+ mBaseX = rawEvent.getX();
+ mBaseY = rawEvent.getY();
+ mBaseTime = event.getEventTime();
+ mPreviousGestureX = mBaseX;
+ mPreviousGestureY = mBaseY;
+ }
+ // Otherwise do nothing because this event doesn't make sense in the middle of a gesture.
+ }
+
+ @Override
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ final float x = rawEvent.getX();
+ final float y = rawEvent.getY();
+ final long time = event.getEventTime();
+ final float dX = Math.abs(x - mPreviousGestureX);
+ final float dY = Math.abs(y - mPreviousGestureY);
+ final long timeDelta = time - mBaseTime;
+ final double moveDelta = Math.hypot(Math.abs(x - mBaseX), Math.abs(y - mBaseY));
+ if (DEBUG) {
+ Slog.d(
+ getGestureName(),
+ "moveDelta:"
+ + Double.toString(moveDelta)
+ + " mGestureDetectionThreshold: "
+ + Float.toString(mGestureDetectionThreshold));
+ }
+ if (getState() == STATE_CLEAR) {
+ if (mStrokeBuffer.size() == 0) {
+ // First, make sure the pointer is going in the right direction.
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ int direction = toDirection(x - mBaseX, y - mBaseY);
+ if (direction != mDirections[0]) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ } else {
+ // This is confirmed to be some kind of swipe so start tracking points.
+ mStrokeBuffer.add(new GesturePoint(mBaseX, mBaseY, mBaseTime));
+ }
+ }
+ if (moveDelta > mGestureDetectionThreshold) {
+ // If the pointer has moved more than the threshold,
+ // update the stored values.
+ mBaseX = x;
+ mBaseY = y;
+ mBaseTime = time;
+ if (getState() == STATE_CLEAR) {
+ startGesture(event, rawEvent, policyFlags);
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ }
+ }
+ }
+ if (getState() == STATE_GESTURE_STARTED) {
+ if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+ mPreviousGestureX = x;
+ mPreviousGestureY = y;
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ }
+ }
+ }
+
+ @Override
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (getState() != STATE_GESTURE_STARTED) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+
+ final float x = rawEvent.getX();
+ final float y = rawEvent.getY();
+ final long time = event.getEventTime();
+ final float dX = Math.abs(x - mPreviousGestureX);
+ final float dY = Math.abs(y - mPreviousGestureY);
+ if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
+ }
+ recognizeGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ /**
+ * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have
+ * transitioned to STATE_GESTURE_STARTED the delay is longer.
+ */
+ private void cancelAfterDelay(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelPendingTransitions();
+ switch (getState()) {
+ case STATE_CLEAR:
+ cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS, event, rawEvent, policyFlags);
+ break;
+ case STATE_GESTURE_STARTED:
+ cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS, event, rawEvent, policyFlags);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
+ * Listener callbacks for success or failure.
+ *
+ * @param event The raw motion event to pass to the listener callbacks.
+ * @param policyFlags Policy flags for the event.
+ * @return true if the event is consumed, else false
+ */
+ private void recognizeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mStrokeBuffer.size() < 2) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+
+ // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
+ // direction change.
+ // Method: for each sampled motion event, check the angle of the most recent motion vector
+ // versus the preceding motion vector, and segment the line if the angle is about
+ // 90 degrees.
+
+ ArrayList<PointF> path = new ArrayList<>();
+ PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
+ path.add(lastDelimiter);
+
+ float dX = 0; // Sum of unit vectors from last delimiter to each following point
+ float dY = 0;
+ int count = 0; // Number of points since last delimiter
+ float length = 0; // Vector length from delimiter to most recent point
+
+ PointF next = new PointF();
+ for (int i = 1; i < mStrokeBuffer.size(); ++i) {
+ next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
+ if (count > 0) {
+ // Average of unit vectors from delimiter to following points
+ float currentDX = dX / count;
+ float currentDY = dY / count;
+
+ // newDelimiter is a possible new delimiter, based on a vector with length from
+ // the last delimiter to the previous point, but in the direction of the average
+ // unit vector from delimiter to previous points.
+ // Using the averaged vector has the effect of "squaring off the curve",
+ // creating a sharper angle between the last motion and the preceding motion from
+ // the delimiter. In turn, this sharper angle achieves the splitting threshold
+ // even in a gentle curve.
+ PointF newDelimiter =
+ new PointF(
+ length * currentDX + lastDelimiter.x,
+ length * currentDY + lastDelimiter.y);
+
+ // Unit vector from newDelimiter to the most recent point
+ float nextDX = next.x - newDelimiter.x;
+ float nextDY = next.y - newDelimiter.y;
+ float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
+ nextDX = nextDX / nextLength;
+ nextDY = nextDY / nextLength;
+
+ // Compare the initial motion direction to the most recent motion direction,
+ // and segment the line if direction has changed by about 90 degrees.
+ float dot = currentDX * nextDX + currentDY * nextDY;
+ if (dot < ANGLE_THRESHOLD) {
+ path.add(newDelimiter);
+ lastDelimiter = newDelimiter;
+ dX = 0;
+ dY = 0;
+ count = 0;
+ }
+ }
+
+ // Vector from last delimiter to most recent point
+ float currentDX = next.x - lastDelimiter.x;
+ float currentDY = next.y - lastDelimiter.y;
+ length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
+
+ // Increment sum of unit vectors from delimiter to each following point
+ count = count + 1;
+ dX = dX + currentDX / length;
+ dY = dY + currentDY / length;
+ }
+
+ path.add(next);
+ if (DEBUG) {
+ Slog.d(getGestureName(), "path=" + path.toString());
+ }
+ // Classify line segments, and call Listener callbacks.
+ recognizeGesturePath(event, rawEvent, policyFlags, path);
+ }
+
+ /**
+ * Classifies a pair of line segments, by direction. Calls Listener callbacks for success or
+ * failure.
+ *
+ * @param event The raw motion event to pass to the listener's onGestureCanceled method.
+ * @param policyFlags Policy flags for the event.
+ * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
+ * @return true if the event is consumed, else false
+ */
+ private void recognizeGesturePath(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags, ArrayList<PointF> path) {
+
+ final int displayId = event.getDisplayId();
+ if (path.size() != mDirections.length + 1) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ for (int i = 0; i < path.size() - 1; ++i) {
+ PointF start = path.get(i);
+ PointF end = path.get(i + 1);
+
+ float dX = end.x - start.x;
+ float dY = end.y - start.y;
+ int direction = toDirection(dX, dY);
+ if (direction != mDirections[i]) {
+ if (DEBUG) {
+ Slog.d(
+ getGestureName(),
+ "Found direction "
+ + directionToString(direction)
+ + " when expecting "
+ + directionToString(mDirections[i]));
+ }
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(getGestureName(), "Completed.");
+ }
+ completeGesture(event, rawEvent, policyFlags);
+ }
+
+ private static int toDirection(float dX, float dY) {
+ if (Math.abs(dX) > Math.abs(dY)) {
+ // Horizontal
+ return (dX < 0) ? LEFT : RIGHT;
+ } else {
+ // Vertical
+ return (dY < 0) ? UP : DOWN;
+ }
+ }
+
+ public static String directionToString(int direction) {
+ switch (direction) {
+ case LEFT:
+ return "left";
+ case RIGHT:
+ return "right";
+ case UP:
+ return "up";
+ case DOWN:
+ return "down";
+ default:
+ return "Unknown Direction";
+ }
+ }
+
+ @Override
+ String getGestureName() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Swipe ").append(directionToString(mDirections[0]));
+ for (int i = 1; i < mDirections.length; ++i) {
+ builder.append(" and ").append(directionToString(mDirections[i]));
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(super.toString());
+ if (getState() != STATE_GESTURE_CANCELED) {
+ builder.append(", mBaseX: ")
+ .append(mBaseX)
+ .append(", mBaseY: ")
+ .append(mBaseY)
+ .append(", mGestureDetectionThreshold:")
+ .append(mGestureDetectionThreshold)
+ .append(", mMinPixelsBetweenSamplesX:")
+ .append(mMinPixelsBetweenSamplesX)
+ .append(", mMinPixelsBetweenSamplesY:")
+ .append(mMinPixelsBetweenSamplesY);
+ }
+ return builder.toString();
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index b62e260..5f41638 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -59,7 +59,7 @@
* @hide
*/
public class TouchExplorer extends BaseEventStreamTransformation
- implements AccessibilityGestureDetector.Listener {
+ implements GestureManifold.Listener {
static final boolean DEBUG = false;
@@ -104,7 +104,7 @@
private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
// Helper to detect gestures.
- private final AccessibilityGestureDetector mGestureDetector;
+ private final GestureManifold mGestureDetector;
// Helper class to track received pointers.
private final TouchState.ReceivedPointerTracker mReceivedPointerTracker;
@@ -142,7 +142,7 @@
* one created in place, or for testing purpose.
*/
public TouchExplorer(Context context, AccessibilityManagerService service,
- AccessibilityGestureDetector detector) {
+ GestureManifold detector) {
mContext = context;
mAms = service;
mState = new TouchState();
@@ -161,7 +161,7 @@
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
mDetermineUserIntentTimeout);
if (detector == null) {
- mGestureDetector = new AccessibilityGestureDetector(context, this);
+ mGestureDetector = new GestureManifold(context, this, mState);
} else {
mGestureDetector = detector;
}
@@ -285,7 +285,7 @@
}
@Override
- public void onDoubleTapAndHold(MotionEvent event, int policyFlags) {
+ public void onDoubleTapAndHold() {
// Ignore the event if we aren't touch interacting.
if (!mState.isTouchInteracting()) {
return;
@@ -303,7 +303,7 @@
}
@Override
- public boolean onDoubleTap(MotionEvent event, int policyFlags) {
+ public boolean onDoubleTap() {
if (!mState.isTouchInteracting()) {
return false;
}
@@ -319,7 +319,7 @@
// Announce the end of a new touch interaction.
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-
+ mSendTouchInteractionEndDelayed.cancel();
// Try to use the standard accessibility API to click
if (!mAms.performActionOnAccessibilityFocusedItem(
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
@@ -356,7 +356,7 @@
}
@Override
- public boolean onGestureCancelled(MotionEvent event, int policyFlags) {
+ public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (mState.isGestureDetecting()) {
endGestureDetection(event.getActionMasked() == MotionEvent.ACTION_UP);
return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index f463260..d23dbbe 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -71,7 +71,10 @@
// Helper class to track received pointers.
// Todo: collapse or hide this class so multiple classes don't modify it.
private final ReceivedPointerTracker mReceivedPointerTracker;
+ // The most recently received motion event.
private MotionEvent mLastReceivedEvent;
+ // The accompanying raw event without any transformations.
+ private MotionEvent mLastReceivedRawEvent;
public TouchState() {
mReceivedPointerTracker = new ReceivedPointerTracker();
@@ -97,6 +100,9 @@
if (mLastReceivedEvent != null) {
mLastReceivedEvent.recycle();
}
+ if (mLastReceivedRawEvent != null) {
+ mLastReceivedRawEvent.recycle();
+ }
mLastReceivedEvent = MotionEvent.obtain(rawEvent);
mReceivedPointerTracker.onMotionEvent(rawEvent);
}
@@ -246,7 +252,6 @@
// or if it goes up the next one that most recently went down.
private int mPrimaryPointerId;
-
ReceivedPointerTracker() {
clear();
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 203bc61..b7adfa4 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -116,6 +116,7 @@
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.0-java",
"android.hardware.contexthub-V1.0-java",
+ "android.hardware.soundtrigger-V2.3-java",
"android.hidl.manager-V1.2-java",
"dnsresolver_aidl_interface-V2-java",
"netd_event_listener_interface-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 53f306b..49046b2 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -275,6 +275,11 @@
public abstract ComponentName getDefaultHomeActivity(int userId);
/**
+ * @return The SystemUI service component name.
+ */
+ public abstract ComponentName getSystemUiServiceComponent();
+
+ /**
* Called by DeviceOwnerManagerService to set the package names of device owner and profile
* owners.
*/
@@ -336,14 +341,16 @@
* @param responseObj The response of the first phase of ephemeral resolution
* @param origIntent The original intent that triggered ephemeral resolution
* @param resolvedType The resolved type of the intent
- * @param callingPackage The name of the package requesting the ephemeral application
+ * @param callingPkg The app requesting the ephemeral application
+ * @param isRequesterInstantApp Whether or not the app requesting the ephemeral application
+ * is an instant app
* @param verificationBundle Optional bundle to pass to the installer for additional
* verification
* @param userId The ID of the user that triggered ephemeral resolution
*/
public abstract void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
- Intent origIntent, String resolvedType, String callingPackage,
- Bundle verificationBundle, int userId);
+ Intent origIntent, String resolvedType, String callingPkg,
+ boolean isRequesterInstantApp, Bundle verificationBundle, int userId);
/**
* Grants implicit access based on an interaction between two apps. This grants the target app
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index a2e9341..d84197c 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -57,7 +57,7 @@
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set
* restrictions enforced by the user.
*
- * @param userId target user id for the local restrictions.
+ * @param originatingUserId user id of the user where the restriction originated.
* @param restrictions a bundle of user restrictions.
* @param restrictionOwnerType determines which admin {@code userId} corresponds to.
* The admin can be either
@@ -70,8 +70,8 @@
* otherwise it will be applied just on the current user.
* @see OwnerType
*/
- public abstract void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
- @OwnerType int restrictionOwnerType);
+ public abstract void setDevicePolicyUserRestrictions(int originatingUserId,
+ @Nullable Bundle restrictions, @OwnerType int restrictionOwnerType);
/**
* Returns the "base" user restrictions.
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 1e5b915..73b6c7a 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -22,6 +22,8 @@
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.UserHandle.USER_SYSTEM;
import android.annotation.UserIdInt;
import android.app.Activity;
@@ -41,10 +43,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
@@ -223,15 +222,6 @@
long mLastTimeChangeRealtime;
int mNumTimeChanged;
- // Bookkeeping about the identity of the "System UI" package, determined at runtime.
-
- /**
- * This permission must be defined by the canonical System UI package,
- * with protection level "signature".
- */
- private static final String SYSTEM_UI_SELF_PERMISSION =
- "android.permission.systemui.IDENTITY";
-
/**
* At boot we use SYSTEM_UI_SELF_PERMISSION to look up the definer's uid.
*/
@@ -3201,7 +3191,7 @@
}
void removeUserLocked(int userHandle) {
- if (userHandle == UserHandle.USER_SYSTEM) {
+ if (userHandle == USER_SYSTEM) {
// If we're told we're removing the system user, ignore it.
return;
}
@@ -3845,21 +3835,9 @@
}
int getSystemUiUid() {
- int sysUiUid = -1;
- final PackageManager pm = mContext.getPackageManager();
- try {
- PermissionInfo sysUiPerm = pm.getPermissionInfo(SYSTEM_UI_SELF_PERMISSION, 0);
- ApplicationInfo sysUi = pm.getApplicationInfo(sysUiPerm.packageName, 0);
- if ((sysUi.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
- sysUiUid = sysUi.uid;
- } else {
- Slog.e(TAG, "SysUI permission " + SYSTEM_UI_SELF_PERMISSION
- + " defined by non-privileged app " + sysUi.packageName
- + " - ignoring");
- }
- } catch (NameNotFoundException e) {
- }
- return sysUiUid;
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+ return pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
+ MATCH_SYSTEM_ONLY, USER_SYSTEM);
}
ClockReceiver getClockReceiver(AlarmManagerService service) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 119b987..470300e 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -16,6 +16,9 @@
package com.android.server;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.UserHandle.USER_SYSTEM;
+
import android.Manifest;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -42,6 +45,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Binder;
@@ -242,10 +246,10 @@
}
// DISALLOW_BLUETOOTH can only be set by DO or PO on the system user.
- if (userId == UserHandle.USER_SYSTEM
+ if (userId == USER_SYSTEM
&& UserRestrictionsUtils.restrictionsChanged(prevRestrictions,
newRestrictions, UserManager.DISALLOW_BLUETOOTH)) {
- if (userId == UserHandle.USER_SYSTEM && newRestrictions.getBoolean(
+ if (userId == USER_SYSTEM && newRestrictions.getBoolean(
UserManager.DISALLOW_BLUETOOTH)) {
updateOppLauncherComponentState(userId, true); // Sharing disallowed
sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED,
@@ -437,18 +441,18 @@
}
int systemUiUid = -1;
- try {
- // Check if device is configured with no home screen, which implies no SystemUI.
- boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen);
- if (!noHome) {
- systemUiUid = mContext.getPackageManager()
- .getPackageUidAsUser("com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
- UserHandle.USER_SYSTEM);
- }
+ // Check if device is configured with no home screen, which implies no SystemUI.
+ boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen);
+ if (!noHome) {
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+ systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
+ MATCH_SYSTEM_ONLY, USER_SYSTEM);
+ }
+ if (systemUiUid >= 0) {
Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid));
- } catch (PackageManager.NameNotFoundException e) {
+ } else {
// Some platforms, such as wearables do not have a system ui.
- Slog.w(TAG, "Unable to resolve SystemUI's UID.", e);
+ Slog.w(TAG, "Unable to resolve SystemUI's UID.");
}
mSystemUiUid = systemUiUid;
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4a0c7a3..0e144ed 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -31,7 +31,6 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -210,8 +209,6 @@
private int[] mDataConnectionNetworkType;
- private int[] mOtaspMode;
-
private ArrayList<List<CellInfo>> mCellInfo = null;
private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
@@ -260,7 +257,9 @@
private final LocalLog mListenLog = new LocalLog(100);
- private PreciseDataConnectionState[] mPreciseDataConnectionState;
+ // Per-phoneMap of APN Type to DataConnectionState
+ private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates =
+ new ArrayList<Map<String, PreciseDataConnectionState>>();
// Nothing here yet, but putting it here in case we want to add more in the future.
static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0;
@@ -405,7 +404,6 @@
mCallForwarding = copyOf(mCallForwarding, mNumPhones);
mCellLocation = copyOf(mCellLocation, mNumPhones);
mSrvccState = copyOf(mSrvccState, mNumPhones);
- mOtaspMode = copyOf(mOtaspMode, mNumPhones);
mPreciseCallState = copyOf(mPreciseCallState, mNumPhones);
mForegroundCallState = copyOf(mForegroundCallState, mNumPhones);
mBackgroundCallState = copyOf(mBackgroundCallState, mNumPhones);
@@ -415,7 +413,6 @@
mCallQuality = copyOf(mCallQuality, mNumPhones);
mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
mCallAttributes = copyOf(mCallAttributes, mNumPhones);
- mPreciseDataConnectionState = copyOf(mPreciseDataConnectionState, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
@@ -423,6 +420,7 @@
if (mNumPhones < oldNumPhones) {
cutListToSize(mCellInfo, mNumPhones);
cutListToSize(mImsReasonInfo, mNumPhones);
+ cutListToSize(mPreciseDataConnectionStates, mNumPhones);
return;
}
@@ -443,7 +441,6 @@
mCellInfo.add(i, null);
mImsReasonInfo.add(i, null);
mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
- mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
@@ -454,7 +451,7 @@
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
+ mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
}
// Note that location can be null for non-phone builds like
@@ -506,7 +503,6 @@
mCallForwarding = new boolean[numPhones];
mCellLocation = new Bundle[numPhones];
mSrvccState = new int[numPhones];
- mOtaspMode = new int[numPhones];
mPreciseCallState = new PreciseCallState[numPhones];
mForegroundCallState = new int[numPhones];
mBackgroundCallState = new int[numPhones];
@@ -516,7 +512,7 @@
mCallQuality = new CallQuality[numPhones];
mCallNetworkType = new int[numPhones];
mCallAttributes = new CallAttributes[numPhones];
- mPreciseDataConnectionState = new PreciseDataConnectionState[numPhones];
+ mPreciseDataConnectionStates = new ArrayList<>();
mCellInfo = new ArrayList<>();
mImsReasonInfo = new ArrayList<>();
mEmergencyNumberList = new HashMap<>();
@@ -538,7 +534,6 @@
mCellInfo.add(i, null);
mImsReasonInfo.add(i, null);
mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
- mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
@@ -549,7 +544,7 @@
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
+ mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
}
// Note that location can be null for non-phone builds like
@@ -880,13 +875,6 @@
remove(r.binder);
}
}
- if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
- try {
- r.callback.onOtaspChanged(mOtaspMode[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
try {
if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
@@ -922,8 +910,10 @@
}
if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
try {
- r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ for (PreciseDataConnectionState pdcs
+ : mPreciseDataConnectionStates.get(phoneId).values()) {
+ r.callback.onPreciseDataConnectionStateChanged(pdcs);
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -937,7 +927,8 @@
}
if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) !=0) {
try {
- r.callback.onVoiceActivationStateChanged(mVoiceActivationState[phoneId]);
+ r.callback.onVoiceActivationStateChanged(
+ mVoiceActivationState[phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1496,20 +1487,38 @@
}
}
- public void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
- boolean isDataAllowed,
- String apn, String apnType,
- LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int networkType, boolean roaming) {
+ /**
+ * Send a notification to registrants that the data connection state has changed.
+ *
+ * @param phoneId the phoneId carrying the data connection
+ * @param subId the subscriptionId for the data connection
+ * @param apnType the APN type that triggered a change in the data connection
+ * @param preciseState a PreciseDataConnectionState that has info about the data connection
+ */
+ public void notifyDataConnectionForSubscriber(
+ int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
+
+ String apn = "";
+ int state = TelephonyManager.DATA_UNKNOWN;
+ int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ LinkProperties linkProps = null;
+
+ if (preciseState != null) {
+ apn = preciseState.getDataConnectionApn();
+ state = preciseState.getState();
+ networkType = preciseState.getNetworkType();
+ linkProps = preciseState.getDataConnectionLinkProperties();
+ }
if (VDBG) {
log("notifyDataConnectionForSubscriber: subId=" + subId
- + " state=" + state + " isDataAllowed=" + isDataAllowed
- + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
- + " mRecords.size()=" + mRecords.size());
+ + " state=" + state + "' apn='" + apn
+ + "' apnType=" + apnType + " networkType=" + networkType
+ + "' preciseState=" + preciseState);
}
+
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
// We only call the callback when the change is for default APN type.
@@ -1541,30 +1550,48 @@
mDataConnectionState[phoneId] = state;
mDataConnectionNetworkType[phoneId] = networkType;
}
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- state, networkType,
- ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
- linkProperties, DataFailCause.NONE);
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
- && idMatch(r.subId, subId, phoneId)) {
- try {
- r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+
+ boolean needsNotify = false;
+ // State has been cleared for this APN Type
+ if (preciseState == null) {
+ // We try clear the state and check if the state was previously not cleared
+ needsNotify = mPreciseDataConnectionStates.get(phoneId).remove(apnType) != null;
+ } else {
+ // We need to check to see if the state actually changed
+ PreciseDataConnectionState oldPreciseState =
+ mPreciseDataConnectionStates.get(phoneId).put(apnType, preciseState);
+ needsNotify = !preciseState.equals(oldPreciseState);
+ }
+
+ if (needsNotify) {
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onPreciseDataConnectionStateChanged(preciseState);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
}
handleRemoveListLocked();
}
- broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties,
- networkCapabilities, roaming, subId);
+
+ broadcastDataConnectionStateChanged(state, apn, apnType, subId);
}
- public void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
+ /**
+ * Stub to satisfy the ITelephonyRegistry aidl interface; do not use this function.
+ * @see #notifyDataConnectionFailedForSubscriber
+ */
+ public void notifyDataConnectionFailed(String apnType) {
+ loge("This function should not be invoked");
+ }
+
+ private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
return;
}
@@ -1574,17 +1601,20 @@
}
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
- DataFailCause.NONE);
+ mPreciseDataConnectionStates.get(phoneId).put(
+ apnType,
+ new PreciseDataConnectionState(
+ TelephonyManager.DATA_UNKNOWN,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ DataFailCause.NONE));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ mPreciseDataConnectionStates.get(phoneId).get(apnType));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1635,29 +1665,6 @@
}
}
- public void notifyOtaspChanged(int subId, int otaspMode) {
- if (!checkNotifyPermission("notifyOtaspChanged()" )) {
- return;
- }
- int phoneId = SubscriptionManager.getPhoneId(subId);
- synchronized (mRecords) {
- if (validatePhoneId(phoneId)) {
- mOtaspMode[phoneId] = otaspMode;
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_OTASP_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
- try {
- r.callback.onOtaspChanged(otaspMode);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
- }
- }
- }
- }
- handleRemoveListLocked();
- }
- }
-
public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
int foregroundCallState, int backgroundCallState) {
if (!checkNotifyPermission("notifyPreciseCallState()")) {
@@ -1772,25 +1779,32 @@
if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
return;
}
+
+ // precise notify invokes imprecise notify
+ notifyDataConnectionFailedForSubscriber(phoneId, subId, apnType);
+
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
+ mPreciseDataConnectionStates.get(phoneId).put(
+ apnType,
+ new PreciseDataConnectionState(
+ TelephonyManager.DATA_UNKNOWN,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ failCause));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ mPreciseDataConnectionStates.get(phoneId).get(apnType));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
}
}
}
-
handleRemoveListLocked();
}
}
@@ -2048,7 +2062,6 @@
}
}
-
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2082,12 +2095,11 @@
pw.println("mCellInfo=" + mCellInfo.get(i));
pw.println("mImsCallDisconnectCause=" + mImsReasonInfo.get(i));
pw.println("mSrvccState=" + mSrvccState[i]);
- pw.println("mOtaspMode=" + mOtaspMode[i]);
pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
pw.println("mCallQuality=" + mCallQuality[i]);
pw.println("mCallAttributes=" + mCallAttributes[i]);
pw.println("mCallNetworkType=" + mCallNetworkType[i]);
- pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState[i]);
+ pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
pw.decreaseIndent();
@@ -2247,29 +2259,13 @@
}
}
- private void broadcastDataConnectionStateChanged(int state, boolean isDataAllowed, String apn,
- String apnType, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities,
- boolean roaming, int subId) {
+ private void broadcastDataConnectionStateChanged(int state, String apn,
+ String apnType, int subId) {
// Note: not reporting to the battery stats service here, because the
// status bar takes care of that after taking into account all of the
// required info.
Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
intent.putExtra(TelephonyManager.EXTRA_STATE, dataStateToString(state));
- if (!isDataAllowed) {
- intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
- }
- if (linkProperties != null) {
- intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
- String iface = linkProperties.getInterfaceName();
- if (iface != null) {
- intent.putExtra(PhoneConstants.DATA_IFACE_NAME_KEY, iface);
- }
- }
- if (networkCapabilities != null) {
- intent.putExtra(PhoneConstants.DATA_NETWORK_CAPABILITIES_KEY, networkCapabilities);
- }
- if (roaming) intent.putExtra(PhoneConstants.DATA_NETWORK_ROAMING_KEY, true);
intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 3330882..f6c11cd 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
@@ -79,7 +80,6 @@
implements InputManager.InputDeviceListener {
private static final String TAG = "VibratorService";
private static final boolean DEBUG = false;
- private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled";
@@ -139,6 +139,7 @@
private final PowerManager.WakeLock mWakeLock;
private final AppOpsManager mAppOps;
private final IBatteryStats mBatteryStatsService;
+ private final String mSystemUiPackage;
private PowerManagerInternal mPowerManagerInternal;
private InputManager mIm;
private Vibrator mVibrator;
@@ -284,7 +285,7 @@
}
public boolean isFromSystem() {
- return uid == Process.SYSTEM_UID || uid == 0 || SYSTEM_UI_PACKAGE.equals(opPkg);
+ return uid == Process.SYSTEM_UID || uid == 0 || mSystemUiPackage.equals(opPkg);
}
public VibrationInfo toInfo() {
@@ -372,6 +373,8 @@
mAppOps = mContext.getSystemService(AppOpsManager.class);
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
+ mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
+ .getSystemUiServiceComponent().getPackageName();
mPreviousVibrationsLimit = mContext.getResources().getInteger(
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e101fe0..5996b7d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4409,7 +4409,6 @@
return true;
}
- @Override
public boolean renameSharedAccountAsUser(Account account, String newName, int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
@@ -4425,7 +4424,6 @@
return r > 0;
}
- @Override
public boolean removeSharedAccountAsUser(Account account, int userId) {
return removeSharedAccountAsUser(account, userId, getCallingUid());
}
@@ -4443,7 +4441,6 @@
return deleted;
}
- @Override
public Account[] getSharedAccountsAsUser(int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b55d6ad..f1cee034 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8389,6 +8389,18 @@
return BugReportHandlerUtil.launchBugReportHandlerApp(mContext);
}
+ /**
+ * Get packages of bugreport-whitelisted apps to handle a bug report.
+ *
+ * @return packages of bugreport-whitelisted apps to handle a bug report.
+ */
+ @Override
+ public List<String> getBugreportWhitelistedPackages() {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_DEBUGGING,
+ "getBugreportWhitelistedPackages");
+ return new ArrayList<>(SystemConfig.getInstance().getBugreportWhitelistedPackages());
+ }
+
public void registerProcessObserver(IProcessObserver observer) {
enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
"registerProcessObserver()");
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 232bc08e..fc67e24 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -77,21 +77,28 @@
private static final String LOG_TAG = "AttentionManagerService";
private static final boolean DEBUG = false;
- /** Default value in absence of {@link DeviceConfig} override. */
- private static final boolean DEFAULT_SERVICE_ENABLED = true;
-
/** Service will unbind if connection is not used for that amount of time. */
private static final long CONNECTION_TTL_MILLIS = 60_000;
- /** If the check attention called within that period - cached value will be returned. */
- private static final long STALE_AFTER_MILLIS = 5_000;
+ /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
+ private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+ /** Default value in absence of {@link DeviceConfig} override. */
+ private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
+ /**
+ * DeviceConfig flag name, describes how much time we consider a result fresh; if the check
+ * attention called within that period - cached value will be returned.
+ */
+ @VisibleForTesting static final String KEY_STALE_AFTER_MILLIS = "stale_after_millis";
+
+ /** Default value in absence of {@link DeviceConfig} override. */
+ @VisibleForTesting static final long DEFAULT_STALE_AFTER_MILLIS = 1_000;
/** The size of the buffer that stores recent attention check results. */
@VisibleForTesting
protected static final int ATTENTION_CACHE_BUFFER_SIZE = 5;
- /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
- private static final String SERVICE_ENABLED = "service_enabled";
private static String sTestAttentionServicePackage;
private final Context mContext;
private final PowerManager mPowerManager;
@@ -160,11 +167,29 @@
@VisibleForTesting
protected boolean isServiceEnabled() {
- return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, SERVICE_ENABLED,
+ return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_SERVICE_ENABLED,
DEFAULT_SERVICE_ENABLED);
}
/**
+ * How much time we consider a result fresh; if the check attention called within that period -
+ * cached value will be returned.
+ */
+ @VisibleForTesting
+ protected long getStaleAfterMillis() {
+ final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS,
+ DEFAULT_STALE_AFTER_MILLIS);
+
+ if (millis < 0 || millis > 10_000) {
+ Slog.w(LOG_TAG, "Bad flag value supplied for: " + KEY_STALE_AFTER_MILLIS);
+ return DEFAULT_STALE_AFTER_MILLIS;
+ }
+
+ return millis;
+ }
+
+ /**
* Checks whether user attention is at the screen and calls in the provided callback.
*
* Calling this multiple times quickly in a row will result in either a) returning a cached
@@ -199,7 +224,7 @@
// throttle frequent requests
final AttentionCheckCache cache = userState.mAttentionCheckCacheBuffer == null ? null
: userState.mAttentionCheckCacheBuffer.getLast();
- if (cache != null && now < cache.mLastComputed + STALE_AFTER_MILLIS) {
+ if (cache != null && now < cache.mLastComputed + getStaleAfterMillis()) {
callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
return true;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 8144a71..60f420e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -600,19 +600,11 @@
sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
}
- /*package*/ void cancelA2dpDockTimeout() {
- mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
- }
-
/*package*/ void postA2dpActiveDeviceChange(
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
}
- /*package*/ boolean hasScheduledA2dpDockTimeout() {
- return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
- }
-
// must be called synchronized on mConnectedDevices
/*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
@@ -621,8 +613,8 @@
new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
}
- /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
- sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
+ /*package*/ void setA2dpTimeout(String address, int a2dpCodec, int delayMs) {
+ sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
}
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
@@ -781,7 +773,7 @@
}
}
break;
- case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_IL_BTA2DP_TIMEOUT:
// msg.obj == address of BTA2DP device
synchronized (mDeviceStateLock) {
mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
@@ -945,7 +937,7 @@
private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
- private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10;
+ private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
private static final int MSG_REPORT_NEW_ROUTES = 13;
@@ -981,7 +973,7 @@
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
- case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_IL_BTA2DP_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
case MSG_TOGGLE_HDMI:
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
@@ -1071,7 +1063,7 @@
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
- case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_IL_BTA2DP_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
if (sLastDeviceConnectMsgTime >= time) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 9061586..df56004 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -59,10 +59,21 @@
private static final String TAG = "AS.AudioDeviceInventory";
- // Actual list of connected devices
+ // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
+ private final Object mDevicesLock = new Object();
+
+ // List of connected devices
// Key for map created from DeviceInfo.makeDeviceListKey()
+ @GuardedBy("mDevicesLock")
private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>();
+ // List of devices actually connected to AudioPolicy (through AudioSystem), only one
+ // by device type, which is used as the key, value is the DeviceInfo generated key.
+ // For the moment only for A2DP sink devices.
+ // TODO: extend to all device types
+ @GuardedBy("mDevicesLock")
+ private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>();
+
// List of preferred devices for strategies
private final ArrayMap<Integer, AudioDeviceAddress> mPreferredDevices = new ArrayMap<>();
@@ -94,25 +105,30 @@
*/
private static class DeviceInfo {
final int mDeviceType;
- final String mDeviceName;
- final String mDeviceAddress;
+ final @NonNull String mDeviceName;
+ final @NonNull String mDeviceAddress;
int mDeviceCodecFormat;
DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
mDeviceType = deviceType;
- mDeviceName = deviceName;
- mDeviceAddress = deviceAddress;
+ mDeviceName = deviceName == null ? "" : deviceName;
+ mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
mDeviceCodecFormat = deviceCodecFormat;
}
@Override
public String toString() {
return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
- + " name:" + mDeviceName
+ + " (" + AudioSystem.getDeviceName(mDeviceType)
+ + ") name:" + mDeviceName
+ " addr:" + mDeviceAddress
+ " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
}
+ String getKey() {
+ return makeDeviceListKey(mDeviceType, mDeviceAddress);
+ }
+
/**
* Generate a unique key for the mConnectedDevices List by composing the device "type"
* and the "address" associated with a specific instance of that device type
@@ -147,6 +163,14 @@
pw.println("\n" + prefix + "Preferred devices for strategy:");
mPreferredDevices.forEach((strategy, device) -> {
pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); });
+ pw.println("\n" + prefix + "Connected devices:");
+ mConnectedDevices.forEach((key, deviceInfo) -> {
+ pw.println(" " + prefix + deviceInfo.toString()); });
+ pw.println("\n" + prefix + "APM Connected device (A2DP sink only):");
+ mApmConnectedDevices.forEach((keyType, valueAddress) -> {
+ pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType)
+ + " (" + AudioSystem.getDeviceName(keyType)
+ + ") addr:" + valueAddress); });
}
//------------------------------------------------------------
@@ -158,7 +182,8 @@
*/
// Always executed on AudioDeviceBroker message queue
/*package*/ void onRestoreDevices() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
+ //TODO iterate on mApmConnectedDevices instead once it handles all device types
for (DeviceInfo di : mConnectedDevices.values()) {
AudioSystem.setDeviceConnectionState(
di.mDeviceType,
@@ -168,7 +193,6 @@
di.mDeviceCodecFormat);
}
}
-
synchronized (mPreferredDevices) {
mPreferredDevices.forEach((strategy, device) -> {
AudioSystem.setPreferredDeviceForStrategy(strategy, device); });
@@ -187,6 +211,9 @@
+ state + " vol=" + a2dpVolume);
}
String address = btDevice.getAddress();
+ if (address == null) {
+ address = "";
+ }
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = "";
}
@@ -198,7 +225,7 @@
+ " codec=" + a2dpCodec
+ " vol=" + a2dpVolume));
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
btDevice.getAddress());
final DeviceInfo di = mConnectedDevices.get(key);
@@ -238,7 +265,7 @@
address = "";
}
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final String key = DeviceInfo.makeDeviceListKey(
AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
final DeviceInfo di = mConnectedDevices.get(key);
@@ -261,7 +288,7 @@
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
"onSetHearingAidConnectionState addr=" + address));
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
btDevice.getAddress());
final DeviceInfo di = mConnectedDevices.get(key);
@@ -297,7 +324,7 @@
"onBluetoothA2dpActiveDeviceChange addr=" + address
+ " event=" + BtHelper.a2dpDeviceEventToString(event)));
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
"A2dp config change ignored (scheduled connection change)"));
@@ -340,7 +367,7 @@
}
/*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
makeA2dpDeviceUnavailableNow(address, a2dpCodec);
}
}
@@ -377,7 +404,7 @@
AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
&& DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
mDeviceBroker.setBluetoothA2dpOnInt(true,
@@ -405,7 +432,7 @@
}
/*package*/ void onToggleHdmi() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
// Is HDMI connected?
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
final DeviceInfo di = mConnectedDevices.get(key);
@@ -472,7 +499,7 @@
+ Integer.toHexString(device) + " address:" + address
+ " name:" + deviceName + ")");
}
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
if (AudioService.DEBUG_DEVICES) {
Slog.i(TAG, "deviceKey:" + deviceKey);
@@ -511,7 +538,7 @@
/*package*/ void disconnectA2dp() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final ArraySet<String> toRemove = new ArraySet<>();
// Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
mConnectedDevices.values().forEach(deviceInfo -> {
@@ -531,7 +558,7 @@
}
/*package*/ void disconnectA2dpSink() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final ArraySet<String> toRemove = new ArraySet<>();
// Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
mConnectedDevices.values().forEach(deviceInfo -> {
@@ -544,7 +571,7 @@
}
/*package*/ void disconnectHearingAid() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final ArraySet<String> toRemove = new ArraySet<>();
// Disconnect ALL DEVICE_OUT_HEARING_AID devices
mConnectedDevices.values().forEach(deviceInfo -> {
@@ -568,7 +595,7 @@
// from AudioSystem
/*package*/ int checkSendBecomingNoisyIntent(int device,
@AudioService.ConnectionState int state, int musicDevice) {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
}
}
@@ -595,7 +622,7 @@
if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
throw new IllegalArgumentException("invalid profile " + profile);
}
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
@AudioService.ConnectionState int asState =
(state == BluetoothA2dp.STATE_CONNECTED)
@@ -635,7 +662,7 @@
/*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
String address, String name, String caller) {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
mDeviceBroker.postSetWiredDeviceConnectionState(
new WiredDeviceConnectionState(type, state, address, name, caller),
@@ -648,7 +675,7 @@
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
boolean suppressNoisyIntent, int musicDevice) {
int delay;
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
if (!suppressNoisyIntent) {
int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
@@ -665,39 +692,58 @@
//-------------------------------------------------------------------
// Internal utilities
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
int a2dpCodec) {
// enable A2DP before notifying A2DP connection to avoid unnecessary processing in
// audio policy manager
mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
+ // at this point there could be another A2DP device already connected in APM, but it
+ // doesn't matter as this new one will overwrite the previous one
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
// Reset A2DP suspend state each time a new sink is connected
AudioSystem.setParameters("A2dpSuspended=false");
- mConnectedDevices.put(
- DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
- new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, a2dpCodec));
+
+ final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
+ address, a2dpCodec);
+ final String diKey = di.getKey();
+ mConnectedDevices.put(diKey, di);
+ // on a connection always overwrite the device seen by AudioPolicy, see comment above when
+ // calling AudioSystem
+ mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey);
+
mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
setCurrentAudioRouteNameIfPossible(name);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
if (address == null) {
return;
}
+ final String deviceToRemoveKey =
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+
+ mConnectedDevices.remove(deviceToRemoveKey);
+ if (!mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)
+ .equals(deviceToRemoveKey)) {
+ // removing A2DP device not currently used by AudioPolicy, log but don't act on it
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
+ return;
+ }
+
+ // device to remove was visible by APM, update APM
mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
- mConnectedDevices.remove(
- DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+ mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
// Remove A2DP routes as well
setCurrentAudioRouteNameIfPossible(null);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
// prevent any activity on the A2DP audio output to avoid unwanted
// reconnection of the sink.
@@ -711,11 +757,11 @@
// the device will be made unavailable later, so consider it disconnected right away
mConnectedDevices.remove(deviceKey);
// send the delayed message to make the device unavailable later
- mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
+ mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpSrcAvailable(String address) {
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
@@ -726,7 +772,7 @@
address, AudioSystem.AUDIO_FORMAT_DEFAULT));
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpSrcUnavailable(String address) {
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
@@ -735,7 +781,7 @@
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeHearingAidDeviceAvailable(
String address, String name, int streamType, String eventSource) {
final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
@@ -755,7 +801,7 @@
setCurrentAudioRouteNameIfPossible(name);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeHearingAidDeviceUnavailable(String address) {
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
@@ -766,7 +812,7 @@
setCurrentAudioRouteNameIfPossible(null);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void setCurrentAudioRouteNameIfPossible(String name) {
synchronized (mCurAudioRoutes) {
if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
@@ -779,7 +825,7 @@
}
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private boolean isCurrentDeviceConnected() {
return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
@@ -807,7 +853,7 @@
// must be called before removing the device from mConnectedDevices
// musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
// from AudioSystem
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private int checkSendBecomingNoisyIntentInt(int device,
@AudioService.ConnectionState int state, int musicDevice) {
if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
@@ -1015,7 +1061,7 @@
public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
device.getAddress());
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
return (mConnectedDevices.get(key) != null);
}
}
diff --git a/services/core/java/com/android/server/biometrics/OWNERS b/services/core/java/com/android/server/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 9bae902..af8a366 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -39,11 +39,11 @@
import android.annotation.Nullable;
import android.content.Context;
import android.net.ISocketKeepaliveCallback;
+import android.net.InvalidPacketException;
import android.net.KeepalivePacketData;
import android.net.NattKeepalivePacketData;
import android.net.NetworkAgent;
import android.net.NetworkUtils;
-import android.net.SocketKeepalive.InvalidPacketException;
import android.net.SocketKeepalive.InvalidSocketException;
import android.net.TcpKeepalivePacketData;
import android.net.util.IpUtils;
@@ -657,7 +657,10 @@
final TcpKeepalivePacketData packet;
try {
packet = TcpKeepaliveController.getTcpKeepalivePacket(fd);
- } catch (InvalidPacketException | InvalidSocketException e) {
+ } catch (InvalidSocketException e) {
+ notifyErrorCallback(cb, e.error);
+ return;
+ } catch (InvalidPacketException e) {
notifyErrorCallback(cb, e.error);
return;
}
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index e570ef1e..1129899 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -30,8 +30,8 @@
import static android.system.OsConstants.TIOCOUTQ;
import android.annotation.NonNull;
+import android.net.InvalidPacketException;
import android.net.NetworkUtils;
-import android.net.SocketKeepalive.InvalidPacketException;
import android.net.SocketKeepalive.InvalidSocketException;
import android.net.TcpKeepalivePacketData;
import android.net.TcpKeepalivePacketDataParcelable;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2cb7e3b..069aeef 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -47,7 +47,8 @@
import android.media.Session2CommandGroup;
import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
-import android.media.session.ICallback;
+import android.media.session.IOnMediaKeyEventDispatchedListener;
+import android.media.session.IOnMediaKeyEventSessionChangedListener;
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
@@ -750,7 +751,10 @@
private final int mFullUserId;
private final MediaSessionStack mPriorityStack;
- private final HashMap<IBinder, CallbackRecord> mCallbacks = new HashMap<>();
+ private final HashMap<IBinder, OnMediaKeyEventDispatchedListenerRecord>
+ mOnMediaKeyEventDispatchedListeners = new HashMap<>();
+ private final HashMap<IBinder, OnMediaKeyEventSessionChangedListenerRecord>
+ mOnMediaKeyEventSessionChangedListeners = new HashMap<>();
private PendingIntent mLastMediaButtonReceiver;
private ComponentName mRestoredMediaButtonReceiver;
@@ -796,21 +800,47 @@
}
}
- public void registerCallbackLocked(ICallback callback, int uid) {
- IBinder cbBinder = callback.asBinder();
- CallbackRecord cr = new CallbackRecord(callback, uid);
- mCallbacks.put(cbBinder, cr);
+ public void addOnMediaKeyEventDispatchedListenerLocked(
+ IOnMediaKeyEventDispatchedListener listener, int uid) {
+ IBinder cbBinder = listener.asBinder();
+ OnMediaKeyEventDispatchedListenerRecord cr =
+ new OnMediaKeyEventDispatchedListenerRecord(listener, uid);
+ mOnMediaKeyEventDispatchedListeners.put(cbBinder, cr);
try {
cbBinder.linkToDeath(cr, 0);
} catch (RemoteException e) {
- Log.w(TAG, "Failed to register callback", e);
- mCallbacks.remove(cbBinder);
+ Log.w(TAG, "Failed to add listener", e);
+ mOnMediaKeyEventDispatchedListeners.remove(cbBinder);
}
}
- public void unregisterCallbackLocked(ICallback callback) {
- IBinder cbBinder = callback.asBinder();
- CallbackRecord cr = mCallbacks.remove(cbBinder);
+ public void removeOnMediaKeyEventDispatchedListenerLocked(
+ IOnMediaKeyEventDispatchedListener listener) {
+ IBinder cbBinder = listener.asBinder();
+ OnMediaKeyEventDispatchedListenerRecord cr =
+ mOnMediaKeyEventDispatchedListeners.remove(cbBinder);
+ cbBinder.unlinkToDeath(cr, 0);
+ }
+
+ public void addOnMediaKeyEventSessionChangedListenerLocked(
+ IOnMediaKeyEventSessionChangedListener listener, int uid) {
+ IBinder cbBinder = listener.asBinder();
+ OnMediaKeyEventSessionChangedListenerRecord cr =
+ new OnMediaKeyEventSessionChangedListenerRecord(listener, uid);
+ mOnMediaKeyEventSessionChangedListeners.put(cbBinder, cr);
+ try {
+ cbBinder.linkToDeath(cr, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to add listener", e);
+ mOnMediaKeyEventSessionChangedListeners.remove(cbBinder);
+ }
+ }
+
+ public void removeOnMediaKeyEventSessionChangedListener(
+ IOnMediaKeyEventSessionChangedListener listener) {
+ IBinder cbBinder = listener.asBinder();
+ OnMediaKeyEventSessionChangedListenerRecord cr =
+ mOnMediaKeyEventSessionChangedListeners.remove(cbBinder);
cbBinder.unlinkToDeath(cr, 0);
}
@@ -832,8 +862,16 @@
pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
pw.println(indent + "Media key listener package: "
+ getCallingPackageName(mOnMediaKeyListenerUid));
- pw.println(indent + "Callbacks: registered " + mCallbacks.size() + " callback(s)");
- for (CallbackRecord cr : mCallbacks.values()) {
+ pw.println(indent + "OnMediaKeyEventDispatchedListener: added "
+ + mOnMediaKeyEventDispatchedListeners.size() + " listener(s)");
+ for (OnMediaKeyEventDispatchedListenerRecord cr
+ : mOnMediaKeyEventDispatchedListeners.values()) {
+ pw.println(indent + " from " + getCallingPackageName(cr.uid));
+ }
+ pw.println(indent + "OnMediaKeyEventSessionChangedListener: added "
+ + mOnMediaKeyEventSessionChangedListeners.size() + " listener(s)");
+ for (OnMediaKeyEventSessionChangedListenerRecord cr
+ : mOnMediaKeyEventSessionChangedListeners.values()) {
pw.println(indent + " from " + getCallingPackageName(cr.uid));
}
pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
@@ -895,19 +933,22 @@
mFullUserId);
}
- private void pushAddressedPlayerChangedLocked(ICallback callback) {
+ private void pushAddressedPlayerChangedLocked(
+ IOnMediaKeyEventSessionChangedListener callback) {
try {
MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
if (mediaButtonSession != null) {
- callback.onAddressedPlayerChangedToMediaSession(
+ callback.onMediaKeyEventSessionChanged(mediaButtonSession.getPackageName(),
mediaButtonSession.getSessionToken());
} else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
- callback.onAddressedPlayerChangedToMediaButtonReceiver(
+ callback.onMediaKeyEventSessionChanged(
mCurrentFullUserRecord.mLastMediaButtonReceiver
- .getIntent().getComponent());
+ .getIntent().getComponent().getPackageName(),
+ null);
} else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
- callback.onAddressedPlayerChangedToMediaButtonReceiver(
- mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
+ callback.onMediaKeyEventSessionChanged(
+ mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(),
+ null);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
@@ -915,7 +956,8 @@
}
private void pushAddressedPlayerChangedLocked() {
- for (CallbackRecord cr : mCallbacks.values()) {
+ for (OnMediaKeyEventSessionChangedListenerRecord cr
+ : mOnMediaKeyEventSessionChangedListeners.values()) {
pushAddressedPlayerChangedLocked(cr.callback);
}
}
@@ -954,11 +996,12 @@
return COMPONENT_TYPE_BROADCAST;
}
- final class CallbackRecord implements IBinder.DeathRecipient {
- public final ICallback callback;
+ final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient {
+ public final IOnMediaKeyEventDispatchedListener callback;
public final int uid;
- CallbackRecord(ICallback callback, int uid) {
+ OnMediaKeyEventDispatchedListenerRecord(IOnMediaKeyEventDispatchedListener callback,
+ int uid) {
this.callback = callback;
this.uid = uid;
}
@@ -966,7 +1009,25 @@
@Override
public void binderDied() {
synchronized (mLock) {
- mCallbacks.remove(callback.asBinder());
+ mOnMediaKeyEventDispatchedListeners.remove(callback.asBinder());
+ }
+ }
+ }
+
+ final class OnMediaKeyEventSessionChangedListenerRecord implements IBinder.DeathRecipient {
+ public final IOnMediaKeyEventSessionChangedListener callback;
+ public final int uid;
+
+ OnMediaKeyEventSessionChangedListenerRecord(
+ IOnMediaKeyEventSessionChangedListener callback, int uid) {
+ this.callback = callback;
+ this.uid = uid;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mOnMediaKeyEventSessionChangedListeners.remove(callback.asBinder());
}
}
}
@@ -1355,7 +1416,8 @@
}
@Override
- public void registerCallback(final ICallback callback) {
+ public void addOnMediaKeyEventDispatchedListener(
+ final IOnMediaKeyEventDispatchedListener listener) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
@@ -1363,18 +1425,18 @@
try {
if (!hasMediaControlPermission(pid, uid)) {
throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
- + " register Callback");
+ + " add MediaKeyEventDispatchedListener");
}
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null || user.mFullUserId != userId) {
- Log.w(TAG, "Only the full user can register the callback"
+ Log.w(TAG, "Only the full user can add the listener"
+ ", userId=" + userId);
return;
}
- user.registerCallbackLocked(callback, uid);
- Log.d(TAG, "The callback (" + callback.asBinder()
- + ") is registered by " + getCallingPackageName(uid));
+ user.addOnMediaKeyEventDispatchedListenerLocked(listener, uid);
+ Log.d(TAG, "The MediaKeyEventDispatchedListener (" + listener.asBinder()
+ + ") is added by " + getCallingPackageName(uid));
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1382,7 +1444,8 @@
}
@Override
- public void unregisterCallback(final ICallback callback) {
+ public void removeOnMediaKeyEventDispatchedListener(
+ final IOnMediaKeyEventDispatchedListener listener) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
@@ -1390,18 +1453,74 @@
try {
if (!hasMediaControlPermission(pid, uid)) {
throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
- + " unregister Callback");
+ + " remove MediaKeyEventDispatchedListener");
}
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null || user.mFullUserId != userId) {
- Log.w(TAG, "Only the full user can unregister the callback"
+ Log.w(TAG, "Only the full user can remove the listener"
+ ", userId=" + userId);
return;
}
- user.unregisterCallbackLocked(callback);
- Log.d(TAG, "The callback (" + callback.asBinder()
- + ") is unregistered by " + getCallingPackageName(uid));
+ user.removeOnMediaKeyEventDispatchedListenerLocked(listener);
+ Log.d(TAG, "The MediaKeyEventDispatchedListener (" + listener.asBinder()
+ + ") is removed by " + getCallingPackageName(uid));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void addOnMediaKeyEventSessionChangedListener(
+ final IOnMediaKeyEventSessionChangedListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!hasMediaControlPermission(pid, uid)) {
+ throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+ + " add MediaKeyEventSessionChangedListener");
+ }
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null || user.mFullUserId != userId) {
+ Log.w(TAG, "Only the full user can add the listener"
+ + ", userId=" + userId);
+ return;
+ }
+ user.addOnMediaKeyEventSessionChangedListenerLocked(listener, uid);
+ Log.d(TAG, "The MediaKeyEventSessionChangedListener (" + listener.asBinder()
+ + ") is added by " + getCallingPackageName(uid));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void removeOnMediaKeyEventSessionChangedListener(
+ final IOnMediaKeyEventSessionChangedListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!hasMediaControlPermission(pid, uid)) {
+ throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+ + " remove MediaKeyEventSessionChangedListener");
+ }
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null || user.mFullUserId != userId) {
+ Log.w(TAG, "Only the full user can remove the listener"
+ + ", userId=" + userId);
+ return;
+ }
+ user.removeOnMediaKeyEventSessionChangedListener(listener);
+ Log.d(TAG, "The MediaKeyEventSessionChangedListener (" + listener.asBinder()
+ + ") is removed by " + getCallingPackageName(uid));
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -2015,10 +2134,10 @@
needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
mKeyEventReceiver);
try {
- for (FullUserRecord.CallbackRecord cr
- : mCurrentFullUserRecord.mCallbacks.values()) {
- cr.callback.onMediaKeyEventDispatchedToMediaSession(
- keyEvent, session.getSessionToken());
+ for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+ : mCurrentFullUserRecord.mOnMediaKeyEventDispatchedListeners.values()) {
+ cr.callback.onMediaKeyEventDispatched(
+ keyEvent, session.getPackageName(), session.getSessionToken());
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to send callback", e);
@@ -2048,10 +2167,11 @@
ComponentName componentName = mCurrentFullUserRecord
.mLastMediaButtonReceiver.getIntent().getComponent();
if (componentName != null) {
- for (FullUserRecord.CallbackRecord cr
- : mCurrentFullUserRecord.mCallbacks.values()) {
- cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
- keyEvent, componentName);
+ for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+ : mCurrentFullUserRecord
+ .mOnMediaKeyEventDispatchedListeners.values()) {
+ cr.callback.onMediaKeyEventDispatched(keyEvent,
+ componentName.getPackageName(), null);
}
}
} else {
@@ -2083,10 +2203,11 @@
Log.w(TAG, "Error sending media button to the restored intent "
+ receiver + ", type=" + componentType, e);
}
- for (FullUserRecord.CallbackRecord cr
- : mCurrentFullUserRecord.mCallbacks.values()) {
- cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
- keyEvent, receiver);
+ for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+ : mCurrentFullUserRecord
+ .mOnMediaKeyEventDispatchedListeners.values()) {
+ cr.callback.onMediaKeyEventDispatched(keyEvent,
+ receiver.getPackageName(), null);
}
}
} catch (CanceledException e) {
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 7098435..a7e40cb 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -59,6 +59,12 @@
return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
}
+ // If a score has been assigned by notification assistant service, use this service
+ // rank results within each bucket instead of this comparator implementation.
+ if (left.getRankingScore() != right.getRankingScore()) {
+ return -1 * Float.compare(left.getRankingScore(), right.getRankingScore());
+ }
+
// first all colorized notifications
boolean leftImportantColorized = isImportantColorized(left);
boolean rightImportantColorized = isImportantColorized(right);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e968fb70..c8afcc9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -144,6 +144,7 @@
private int mSystemImportance = IMPORTANCE_UNSPECIFIED;
private int mAssistantImportance = IMPORTANCE_UNSPECIFIED;
private int mImportance = IMPORTANCE_UNSPECIFIED;
+ private float mRankingScore = 0f;
// Field used in global sort key to bypass normal notifications
private int mCriticality = CriticalNotificationExtractor.NORMAL;
// A MetricsEvent.NotificationImportanceExplanation, tracking source of mImportance.
@@ -655,6 +656,9 @@
importance = Math.min(IMPORTANCE_HIGH, importance);
setAssistantImportance(importance);
}
+ if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
+ mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
+ }
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
}
@@ -772,6 +776,10 @@
return mImportance;
}
+ public float getRankingScore() {
+ return mRankingScore;
+ }
+
public CharSequence getImportanceExplanation() {
switch (mImportanceExplanationCode) {
case MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN:
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index e055116..ac3bf9a 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -26,6 +26,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.text.TextUtils;
+import android.util.Pair;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
@@ -44,6 +45,38 @@
private final VerifyCallback mVerifyCallback;
+ /**
+ * @return nullable actor result with {@link ActorState} failure status
+ */
+ static Pair<String, ActorState> getPackageNameForActor(String actorUriString,
+ Map<String, Map<String, String>> namedActors) {
+ if (namedActors.isEmpty()) {
+ return Pair.create(null, ActorState.NO_NAMED_ACTORS);
+ }
+
+ Uri actorUri = Uri.parse(actorUriString);
+
+ String actorScheme = actorUri.getScheme();
+ List<String> actorPathSegments = actorUri.getPathSegments();
+ if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
+ return Pair.create(null, ActorState.INVALID_OVERLAYABLE_ACTOR_NAME);
+ }
+
+ String actorNamespace = actorUri.getAuthority();
+ Map<String, String> namespace = namedActors.get(actorNamespace);
+ if (namespace == null) {
+ return Pair.create(null, ActorState.MISSING_NAMESPACE);
+ }
+
+ String actorName = actorPathSegments.get(0);
+ String packageName = namespace.get(actorName);
+ if (TextUtils.isEmpty(packageName)) {
+ return Pair.create(null, ActorState.MISSING_ACTOR_NAME);
+ }
+
+ return Pair.create(packageName, ActorState.ALLOWED);
+ }
+
public OverlayActorEnforcer(@NonNull VerifyCallback verifyCallback) {
mVerifyCallback = verifyCallback;
}
@@ -141,31 +174,14 @@
}
}
- Map<String, ? extends Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
- if (namedActors.isEmpty()) {
- return ActorState.NO_NAMED_ACTORS;
+ Map<String, Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
+ Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors);
+ ActorState actorUriState = actorUriPair.second;
+ if (actorUriState != ActorState.ALLOWED) {
+ return actorUriState;
}
- Uri actorUri = Uri.parse(actor);
-
- String actorScheme = actorUri.getScheme();
- List<String> actorPathSegments = actorUri.getPathSegments();
- if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
- return ActorState.INVALID_OVERLAYABLE_ACTOR_NAME;
- }
-
- String actorNamespace = actorUri.getAuthority();
- Map<String, String> namespace = namedActors.get(actorNamespace);
- if (namespace == null) {
- return ActorState.MISSING_NAMESPACE;
- }
-
- String actorName = actorPathSegments.get(0);
- String packageName = namespace.get(actorName);
- if (TextUtils.isEmpty(packageName)) {
- return ActorState.MISSING_ACTOR_NAME;
- }
-
+ String packageName = actorUriPair.first;
PackageInfo packageInfo = mVerifyCallback.getPackageInfo(packageName, userId);
if (packageInfo == null) {
return ActorState.MISSING_APP_INFO;
@@ -192,7 +208,7 @@
* For easier logging/debugging, a set of all possible failure/success states when running
* enforcement.
*/
- private enum ActorState {
+ enum ActorState {
ALLOWED,
INVALID_ACTOR,
MISSING_NAMESPACE,
@@ -244,7 +260,7 @@
* value maps actor name to package name
*/
@NonNull
- Map<String, ? extends Map<String, String>> getNamedActors();
+ Map<String, Map<String, String>> getNamedActors();
/**
* @return true if the target package has declared an overlayable
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 8b69946..f1947ac 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1073,8 +1073,6 @@
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
- // TODO(b/143096091): Remove PackageInfo cache so that PackageManager is always queried
- // to enforce visibility/other permission checks
public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
final boolean useCache) {
if (useCache) {
@@ -1097,18 +1095,12 @@
@Override
public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
- // TODO(b/143096091): Remove clearing calling ID
- long callingIdentity = Binder.clearCallingIdentity();
- try {
- return getPackageInfo(packageName, userId, true);
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
- }
+ return getPackageInfo(packageName, userId, true);
}
@NonNull
@Override
- public Map<String, ? extends Map<String, String>> getNamedActors() {
+ public Map<String, Map<String, String>> getNamedActors() {
return SystemConfig.getInstance().getNamedActors();
}
@@ -1136,57 +1128,45 @@
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@Nullable String targetOverlayableName, int userId)
throws IOException {
- // TODO(b/143096091): Remove clearing calling ID
- long callingIdentity = Binder.clearCallingIdentity();
+ PackageInfo packageInfo = getPackageInfo(packageName, userId);
+ if (packageInfo == null) {
+ throw new IOException("Unable to get target package");
+ }
+
+ String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+
+ ApkAssets apkAssets = null;
try {
- PackageInfo packageInfo = getPackageInfo(packageName, userId);
- if (packageInfo == null) {
- throw new IOException("Unable to get target package");
- }
-
- String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
- ApkAssets apkAssets = null;
- try {
- apkAssets = ApkAssets.loadFromPath(baseCodePath);
- return apkAssets.getOverlayableInfo(targetOverlayableName);
- } finally {
- if (apkAssets != null) {
- try {
- apkAssets.close();
- } catch (Throwable ignored) {
- }
+ apkAssets = ApkAssets.loadFromPath(baseCodePath);
+ return apkAssets.getOverlayableInfo(targetOverlayableName);
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
}
}
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
}
}
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws RemoteException, IOException {
- // TODO(b/143096091): Remove clearing calling ID
- long callingIdentity = Binder.clearCallingIdentity();
- try {
- PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
- userId);
- String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
+ userId);
+ String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
- ApkAssets apkAssets = null;
- try {
- apkAssets = ApkAssets.loadFromPath(baseCodePath);
- return apkAssets.definesOverlayable();
- } finally {
- if (apkAssets != null) {
- try {
- apkAssets.close();
- } catch (Throwable ignored) {
- }
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(baseCodePath);
+ return apkAssets.definesOverlayable();
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
}
}
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
}
}
@@ -1229,16 +1209,10 @@
@Nullable
@Override
public String[] getPackagesForUid(int uid) {
- // TODO(b/143096091): Remove clearing calling ID
- long callingIdentity = Binder.clearCallingIdentity();
try {
- try {
- return mPackageManager.getPackagesForUid(uid);
- } catch (RemoteException ignored) {
- return null;
- }
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
+ return mPackageManager.getPackagesForUid(uid);
+ } catch (RemoteException ignored) {
+ return null;
}
}
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
new file mode 100644
index 0000000..8bea119
--- /dev/null
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.parsing.AndroidPackage;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.PackageSetting;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Track visibility of a targets and overlays to actors.
+ *
+ * 4 cases to handle:
+ * <ol>
+ * <li>Target adds/changes an overlayable to add a reference to an actor
+ * <ul>
+ * <li>Must expose target to actor</li>
+ * <li>Must expose any overlays that pointed to that overlayable name to the actor</li>
+ * </ul>
+ * </li>
+ * <li>Target removes/changes an overlayable to remove a reference to an actor
+ * <ul>
+ * <li>If this target has no other overlayables referencing the actor, hide the
+ * target</li>
+ * <li>For all overlays targeting this overlayable, if the overlay is only visible to
+ * the actor through this overlayable, hide the overlay</li>
+ * </ul>
+ * </li>
+ * <li>Overlay adds/changes an overlay tag to add a reference to an overlayable name
+ * <ul>
+ * <li>Expose this overlay to the actor defined by the target overlayable</li>
+ * </ul>
+ * </li>
+ * <li>Overlay removes/changes an overlay tag to remove a reference to an overlayable name
+ * <ul>
+ * <li>If this overlay is only visible to an actor through this overlayable name's
+ * target's actor</li>
+ * </ul>
+ * </li>
+ * </ol>
+ *
+ * In this class, the names "actor", "target", and "overlay" all refer to the ID representations.
+ * All other use cases are named appropriate. "actor" is actor name, "target" is target package
+ * name, and "overlay" is overlay package name.
+ */
+public class OverlayReferenceMapper {
+
+ private final Object mLock = new Object();
+
+ /**
+ * Keys are actors, values are maps which map target to a set of overlays targeting it.
+ * The presence of a target in the value map means the actor and targets are connected, even
+ * if the corresponding target's set is empty.
+ * See class comment for specific types.
+ */
+ @GuardedBy("mLock")
+ private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>();
+
+ /**
+ * Keys are actor package names, values are generic package names the actor should be able
+ * to see.
+ */
+ @GuardedBy("mLock")
+ private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>();
+
+ @GuardedBy("mLock")
+ private boolean mDeferRebuild;
+
+ @NonNull
+ private final Provider mProvider;
+
+ /**
+ * @param deferRebuild whether or not to defer rebuild calls on add/remove until first get call;
+ * useful during boot when multiple packages are added in rapid succession
+ * and queries in-between are not expected
+ */
+ public OverlayReferenceMapper(boolean deferRebuild, @Nullable Provider provider) {
+ this.mDeferRebuild = deferRebuild;
+ this.mProvider = provider != null ? provider : new Provider() {
+ @Nullable
+ @Override
+ public String getActorPkg(String actor) {
+ Map<String, Map<String, String>> namedActors = SystemConfig.getInstance()
+ .getNamedActors();
+
+ Pair<String, OverlayActorEnforcer.ActorState> actorPair =
+ OverlayActorEnforcer.getPackageNameForActor(actor, namedActors);
+ return actorPair.first;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
+ String target = pkg.getOverlayTarget();
+ if (TextUtils.isEmpty(target)) {
+ return Collections.emptyMap();
+ }
+
+ String overlayable = pkg.getOverlayTargetName();
+ Map<String, Set<String>> targetToOverlayables = new HashMap<>();
+ Set<String> overlayables = new HashSet<>();
+ overlayables.add(overlayable);
+ targetToOverlayables.put(target, overlayables);
+ return targetToOverlayables;
+ }
+ };
+ }
+
+ /**
+ * @return mapping of actor package to a set of packages it can view
+ */
+ @NonNull
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public Map<String, Set<String>> getActorPkgToPkgs() {
+ return mActorPkgToPkgs;
+ }
+
+ public boolean isValidActor(@NonNull String targetName, @NonNull String actorPackageName) {
+ synchronized (mLock) {
+ assertMapBuilt();
+ Set<String> validSet = mActorPkgToPkgs.get(actorPackageName);
+ return validSet != null && validSet.contains(targetName);
+ }
+ }
+
+ /**
+ * Add a package to be considered for visibility. Currently supports adding as a target and/or
+ * an overlay. Adding an actor is not supported. Those are configured as part of
+ * {@link SystemConfig#getNamedActors()}.
+ *
+ * @param pkg the package to add
+ * @param otherPkgs map of other packages to consider, excluding {@param pkg}
+ */
+ public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
+ synchronized (mLock) {
+ if (!pkg.getOverlayables().isEmpty()) {
+ addTarget(pkg, otherPkgs);
+ }
+
+ // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
+ if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
+ addOverlay(pkg, otherPkgs);
+ }
+
+ if (!mDeferRebuild) {
+ rebuild();
+ }
+ }
+ }
+
+ /**
+ * Removes a package to be considered for visibility. Currently supports removing as a target
+ * and/or an overlay. Removing an actor is not supported. Those are staticly configured as part
+ * of {@link SystemConfig#getNamedActors()}.
+ *
+ * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)}
+ */
+ public void removePkg(String pkgName) {
+ synchronized (mLock) {
+ removeTarget(pkgName);
+ removeOverlay(pkgName);
+
+ if (!mDeferRebuild) {
+ rebuild();
+ }
+ }
+ }
+
+ private void removeTarget(String target) {
+ synchronized (mLock) {
+ Iterator<Map<String, Set<String>>> iterator =
+ mActorToTargetToOverlays.values().iterator();
+ while (iterator.hasNext()) {
+ Map<String, Set<String>> next = iterator.next();
+ next.remove(target);
+ if (next.isEmpty()) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Associate an actor with an association of a new target to overlays for that target.
+ *
+ * If a target overlays itself, it will not be associated with itself, as only one half of the
+ * relationship needs to exist for visibility purposes.
+ */
+ private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) {
+ synchronized (mLock) {
+ String target = targetPkg.getPackageName();
+ removeTarget(target);
+
+ Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
+ for (String overlayable : overlayablesToActors.keySet()) {
+ String actor = overlayablesToActors.get(overlayable);
+ addTargetToMap(actor, target);
+
+ for (AndroidPackage overlayPkg : otherPkgs.values()) {
+ Map<String, Set<String>> targetToOverlayables =
+ mProvider.getTargetToOverlayables(overlayPkg);
+ Set<String> overlayables = targetToOverlayables.get(target);
+ if (CollectionUtils.isEmpty(overlayables)) {
+ continue;
+ }
+
+ if (overlayables.contains(overlayable)) {
+ addOverlayToMap(actor, target, overlayPkg.getPackageName());
+ }
+ }
+ }
+ }
+ }
+
+ private void removeOverlay(String overlay) {
+ synchronized (mLock) {
+ for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) {
+ for (Set<String> overlays : targetToOverlays.values()) {
+ overlays.remove(overlay);
+ }
+ }
+ }
+ }
+
+ /**
+ * Associate an actor with an association of targets to overlays for a new overlay.
+ *
+ * If an overlay targets itself, it will not be associated with itself, as only one half of the
+ * relationship needs to exist for visibility purposes.
+ */
+ private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) {
+ synchronized (mLock) {
+ String overlay = overlayPkg.getPackageName();
+ removeOverlay(overlay);
+
+ Map<String, Set<String>> targetToOverlayables =
+ mProvider.getTargetToOverlayables(overlayPkg);
+ for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) {
+ String target = entry.getKey();
+ Set<String> overlayables = entry.getValue();
+ AndroidPackage targetPkg = otherPkgs.get(target);
+ if (targetPkg == null) {
+ continue;
+ }
+
+ String targetPkgName = targetPkg.getPackageName();
+ Map<String, String> overlayableToActor = targetPkg.getOverlayables();
+ for (String overlayable : overlayables) {
+ String actor = overlayableToActor.get(overlayable);
+ if (TextUtils.isEmpty(actor)) {
+ continue;
+ }
+ addOverlayToMap(actor, targetPkgName, overlay);
+ }
+ }
+ }
+ }
+
+ public void rebuildIfDeferred() {
+ synchronized (mLock) {
+ if (mDeferRebuild) {
+ rebuild();
+ mDeferRebuild = false;
+ }
+ }
+ }
+
+ private void assertMapBuilt() {
+ if (mDeferRebuild) {
+ throw new IllegalStateException("The actor map must be built by calling "
+ + "rebuildIfDeferred before it is queried");
+ }
+ }
+
+ private void rebuild() {
+ synchronized (mLock) {
+ mActorPkgToPkgs.clear();
+ for (String actor : mActorToTargetToOverlays.keySet()) {
+ String actorPkg = mProvider.getActorPkg(actor);
+ if (TextUtils.isEmpty(actorPkg)) {
+ continue;
+ }
+
+ Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ Set<String> pkgs = new HashSet<>();
+
+ for (String target : targetToOverlays.keySet()) {
+ Set<String> overlays = targetToOverlays.get(target);
+ pkgs.add(target);
+ pkgs.addAll(overlays);
+ }
+
+ mActorPkgToPkgs.put(actorPkg, pkgs);
+ }
+ }
+ }
+
+ private void addTargetToMap(String actor, String target) {
+ Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ if (targetToOverlays == null) {
+ targetToOverlays = new HashMap<>();
+ mActorToTargetToOverlays.put(actor, targetToOverlays);
+ }
+
+ Set<String> overlays = targetToOverlays.get(target);
+ if (overlays == null) {
+ overlays = new HashSet<>();
+ targetToOverlays.put(target, overlays);
+ }
+ }
+
+ private void addOverlayToMap(String actor, String target, String overlay) {
+ synchronized (mLock) {
+ Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ if (targetToOverlays == null) {
+ targetToOverlays = new HashMap<>();
+ mActorToTargetToOverlays.put(actor, targetToOverlays);
+ }
+
+ Set<String> overlays = targetToOverlays.get(target);
+ if (overlays == null) {
+ overlays = new HashSet<>();
+ targetToOverlays.put(target, overlays);
+ }
+
+ overlays.add(overlay);
+ }
+ }
+
+ public interface Provider {
+
+ /**
+ * Given the actor string from an overlayable definition, return the actor's package name.
+ */
+ @Nullable
+ String getActorPkg(String actor);
+
+ /**
+ * Mock response of multiple overlay tags.
+ *
+ * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests
+ */
+ @NonNull
+ Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 8374ee6..c4bcf80 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -16,8 +16,6 @@
package com.android.server.pm;
-import static android.content.pm.PackageParser.Component;
-import static android.content.pm.PackageParser.IntentInfo;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
@@ -26,7 +24,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
import android.content.pm.parsing.AndroidPackage;
import android.content.pm.parsing.ComponentParseUtils;
import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
@@ -50,9 +47,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.FgThread;
+import com.android.server.om.OverlayReferenceMapper;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -109,11 +106,16 @@
private final FeatureConfig mFeatureConfig;
+ private final OverlayReferenceMapper mOverlayReferenceMapper;
+
AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist,
- boolean systemAppsQueryable) {
+ boolean systemAppsQueryable,
+ @Nullable OverlayReferenceMapper.Provider overlayProvider) {
mFeatureConfig = featureConfig;
mForceQueryableByDevicePackageNames = forceQueryableWhitelist;
mSystemAppsQueryable = systemAppsQueryable;
+ mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/,
+ overlayProvider);
}
public interface FeatureConfig {
@@ -193,7 +195,7 @@
}
}
return new AppsFilter(featureConfig, forcedQueryablePackageNames,
- forceSystemAppsQueryable);
+ forceSystemAppsQueryable, null);
}
/** Returns true if the querying package may query for the potential target package */
@@ -282,6 +284,7 @@
public void onSystemReady() {
mFeatureConfig.onSystemReady();
+ mOverlayReferenceMapper.rebuildIfDeferred();
}
/**
@@ -338,6 +341,16 @@
}
}
}
+
+ int existingSize = existingSettings.size();
+ ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize);
+ for (int index = 0; index < existingSize; index++) {
+ PackageSetting pkgSetting = existingSettings.valueAt(index);
+ if (pkgSetting.pkg != null) {
+ existingPkgs.put(pkgSetting.name, pkgSetting.pkg);
+ }
+ }
+ mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -381,6 +394,8 @@
addPackage(setting.sharedUser.packages.valueAt(i), existingSettings);
}
}
+
+ mOverlayReferenceMapper.removePkg(setting.name);
}
/**
@@ -397,8 +412,7 @@
PackageSetting targetPkgSetting, int userId) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication");
try {
- if (!shouldFilterApplicationInternal(callingUid, callingSetting,
- targetPkgSetting,
+ if (!shouldFilterApplicationInternal(callingUid, callingSetting, targetPkgSetting,
userId)) {
return false;
}
@@ -412,8 +426,8 @@
}
}
- private boolean shouldFilterApplicationInternal(int callingUid,
- SettingBase callingSetting, PackageSetting targetPkgSetting, int userId) {
+ private boolean shouldFilterApplicationInternal(int callingUid, SettingBase callingSetting,
+ PackageSetting targetPkgSetting, int userId) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal");
try {
final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
@@ -530,6 +544,29 @@
}
}
}
+
+ if (callingSharedPkgSettings != null) {
+ int size = callingSharedPkgSettings.size();
+ for (int index = 0; index < size; index++) {
+ PackageSetting pkgSetting = callingSharedPkgSettings.valueAt(index);
+ if (mOverlayReferenceMapper.isValidActor(targetName, pkgSetting.name)) {
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting,
+ "matches shared user of package that acts on target of "
+ + "overlay");
+ }
+ return false;
+ }
+ }
+ } else {
+ if (mOverlayReferenceMapper.isValidActor(targetName, callingPkgSetting.name)) {
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting, "acts on target of overlay");
+ }
+ return false;
+ }
+ }
+
return true;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 30b2c9d..8333ae5 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -39,6 +39,7 @@
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.InstantAppIntentFilter;
import android.content.pm.InstantAppRequest;
+import android.content.pm.InstantAppRequestInfo;
import android.content.pm.InstantAppResolveInfo;
import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
import android.metrics.LogMaker;
@@ -47,6 +48,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -63,7 +66,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
-import java.util.UUID;
/** @hide */
public abstract class InstantAppResolver {
@@ -117,26 +119,40 @@
return sanitizedIntent;
}
+ /**
+ * Generate an {@link InstantAppDigest} from an {@link Intent} which contains hashes of the
+ * host. The object contains both secure and insecure hash array variants, and the secure
+ * version must be passed along to ensure the random data is consistent.
+ */
+ @NonNull
+ public static InstantAppDigest parseDigest(@NonNull Intent origIntent) {
+ if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) {
+ return new InstantAppResolveInfo.InstantAppDigest(origIntent.getData().getHost(),
+ 5 /*maxDigests*/);
+ } else {
+ return InstantAppResolveInfo.InstantAppDigest.UNDEFINED;
+ }
+ }
+
public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(
InstantAppResolverConnection connection, InstantAppRequest requestObj) {
final long startTime = System.currentTimeMillis();
- final String token = UUID.randomUUID().toString();
+ final String token = requestObj.token;
if (DEBUG_INSTANT) {
Log.d(TAG, "[" + token + "] Phase1; resolving");
}
- final Intent origIntent = requestObj.origIntent;
- final Intent sanitizedIntent = sanitizeIntent(origIntent);
AuxiliaryResolveInfo resolveInfo = null;
@ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
+ Intent origIntent = requestObj.origIntent;
try {
final List<InstantAppResolveInfo> instantAppResolveInfoList =
- connection.getInstantAppResolveInfoList(sanitizedIntent,
- requestObj.digest.getDigestPrefixSecure(), requestObj.userId, token);
+ connection.getInstantAppResolveInfoList(buildRequestInfo(requestObj));
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
resolveInfo = InstantAppResolver.filterInstantAppIntent(
instantAppResolveInfoList, origIntent, requestObj.resolvedType,
- requestObj.userId, origIntent.getPackage(), requestObj.digest, token);
+ requestObj.userId, origIntent.getPackage(), token,
+ requestObj.hostDigestPrefixSecure);
}
} catch (ConnectionException e) {
if (e.failure == ConnectionException.FAILURE_BIND) {
@@ -166,7 +182,7 @@
// if the match external flag is set, return an empty resolve info instead of a null result.
if (resolveInfo == null && (origIntent.getFlags() & FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
return new AuxiliaryResolveInfo(token, false, createFailureIntent(origIntent, token),
- null /* filters */);
+ null /* filters */, requestObj.hostDigestPrefixSecure);
}
return resolveInfo;
}
@@ -175,7 +191,7 @@
InstantAppResolverConnection connection, InstantAppRequest requestObj,
ActivityInfo instantAppInstaller, Handler callbackHandler) {
final long startTime = System.currentTimeMillis();
- final String token = requestObj.responseObj.token;
+ final String token = requestObj.token;
if (DEBUG_INSTANT) {
Log.d(TAG, "[" + token + "] Phase2; resolving");
}
@@ -191,8 +207,8 @@
final AuxiliaryResolveInfo instantAppIntentInfo =
InstantAppResolver.filterInstantAppIntent(
instantAppResolveInfoList, origIntent, null /*resolvedType*/,
- 0 /*userId*/, origIntent.getPackage(), requestObj.digest,
- token);
+ 0 /*userId*/, origIntent.getPackage(),
+ token, requestObj.hostDigestPrefixSecure);
if (instantAppIntentInfo != null) {
failureIntent = instantAppIntentInfo.failureIntent;
} else {
@@ -223,8 +239,7 @@
}
};
try {
- connection.getInstantAppIntentFilterList(sanitizedIntent,
- requestObj.digest.getDigestPrefixSecure(), requestObj.userId, token, callback,
+ connection.getInstantAppIntentFilterList(buildRequestInfo(requestObj), callback,
callbackHandler, startTime);
} catch (ConnectionException e) {
@ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
@@ -356,10 +371,22 @@
return intent;
}
+ private static InstantAppRequestInfo buildRequestInfo(InstantAppRequest request) {
+ return new InstantAppRequestInfo(
+ sanitizeIntent(request.origIntent),
+ // This must only expose the secured version of the host
+ request.hostDigestPrefixSecure,
+ UserHandle.getUserHandleForUid(request.userId),
+ request.isRequesterInstantApp,
+ request.token
+ );
+ }
+
private static AuxiliaryResolveInfo filterInstantAppIntent(
- List<InstantAppResolveInfo> instantAppResolveInfoList,
- Intent origIntent, String resolvedType, int userId, String packageName,
- InstantAppDigest digest, String token) {
+ List<InstantAppResolveInfo> instantAppResolveInfoList, Intent origIntent,
+ String resolvedType, int userId, String packageName, String token,
+ int[] hostDigestPrefixSecure) {
+ InstantAppDigest digest = InstantAppResolver.parseDigest(origIntent);
final int[] shaPrefix = digest.getDigestPrefix();
final byte[][] digestBytes = digest.getDigestBytes();
boolean requiresSecondPhase = false;
@@ -404,7 +431,7 @@
}
if (filters != null && !filters.isEmpty()) {
return new AuxiliaryResolveInfo(token, requiresSecondPhase,
- createFailureIntent(origIntent, token), filters);
+ createFailureIntent(origIntent, token), filters, hostDigestPrefixSecure);
}
// Hash or filter mis-match; no instant apps for this domain.
return null;
diff --git a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
index c0e9f58..0fe2eb1 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.InstantAppRequestInfo;
import android.content.pm.InstantAppResolveInfo;
import android.os.Binder;
import android.os.Build;
@@ -85,13 +86,13 @@
mBgHandler = BackgroundThread.getHandler();
}
- public List<InstantAppResolveInfo> getInstantAppResolveInfoList(Intent sanitizedIntent,
- int[] hashPrefix, int userId, String token) throws ConnectionException {
+ public List<InstantAppResolveInfo> getInstantAppResolveInfoList(InstantAppRequestInfo request)
+ throws ConnectionException {
throwIfCalledOnMainThread();
IInstantAppResolver target = null;
try {
try {
- target = getRemoteInstanceLazy(token);
+ target = getRemoteInstanceLazy(request.token);
} catch (TimeoutException e) {
throw new ConnectionException(ConnectionException.FAILURE_BIND);
} catch (InterruptedException e) {
@@ -99,8 +100,7 @@
}
try {
return mGetInstantAppResolveInfoCaller
- .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, userId,
- token);
+ .getInstantAppResolveInfoList(target, request);
} catch (TimeoutException e) {
throw new ConnectionException(ConnectionException.FAILURE_CALL);
} catch (RemoteException ignore) {
@@ -113,8 +113,8 @@
return null;
}
- public void getInstantAppIntentFilterList(Intent sanitizedIntent, int[] hashPrefix, int userId,
- String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
+ public void getInstantAppIntentFilterList(InstantAppRequestInfo request,
+ PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
throws ConnectionException {
final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
@Override
@@ -126,9 +126,8 @@
}
};
try {
- getRemoteInstanceLazy(token)
- .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, userId, token,
- remoteCallback);
+ getRemoteInstanceLazy(request.token)
+ .getInstantAppIntentFilterList(request, remoteCallback);
} catch (TimeoutException e) {
throw new ConnectionException(ConnectionException.FAILURE_BIND);
} catch (InterruptedException e) {
@@ -352,12 +351,10 @@
};
}
- public List<InstantAppResolveInfo> getInstantAppResolveInfoList(
- IInstantAppResolver target, Intent sanitizedIntent, int[] hashPrefix, int userId,
- String token) throws RemoteException, TimeoutException {
+ public List<InstantAppResolveInfo> getInstantAppResolveInfoList(IInstantAppResolver target,
+ InstantAppRequestInfo request) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
- target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, userId, token,
- sequence, mCallback);
+ target.getInstantAppResolveInfoList(request, sequence, mCallback);
return getResultTimed(sequence);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index dceca0a..a54534b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -637,7 +637,7 @@
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
installSource, params, createdMillis,
- stageDir, stageCid, false, false, false, null, SessionInfo.INVALID_ID,
+ stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
@@ -1014,12 +1014,28 @@
}
}
+ static void sendPendingStreaming(Context context, IntentSender target, int sessionId,
+ Throwable cause) {
+ final Intent intent = new Intent();
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_STREAMING);
+ if (cause != null && !TextUtils.isEmpty(cause.getMessage())) {
+ intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
+ "Staging Image Not Ready [" + cause.getMessage() + "]");
+ } else {
+ intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
+ }
+ try {
+ target.sendIntent(context, 0, intent, null, null);
+ } catch (SendIntentException ignored) {
+ }
+ }
+
static void sendOnUserActionRequired(Context context, IntentSender target, int sessionId,
Intent intent) {
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
- fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_PENDING_USER_ACTION);
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
try {
target.sendIntent(context, 0, fillIn, null, null);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d6ca86a..286d291 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -21,6 +21,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
@@ -30,11 +31,13 @@
import static com.android.internal.util.XmlUtils.readBitmapAttribute;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.readUriAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -123,6 +126,7 @@
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
@@ -142,6 +146,7 @@
/** XML constants used for persisting a session */
static final String TAG_SESSION = "session";
static final String TAG_CHILD_SESSION = "childSession";
+ static final String TAG_SESSION_FILE = "sessionFile";
private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION =
"whitelisted-restricted-permission";
@@ -183,6 +188,9 @@
private static final String ATTR_VOLUME_UUID = "volumeUuid";
private static final String ATTR_NAME = "name";
private static final String ATTR_INSTALL_REASON = "installRason";
+ private static final String ATTR_DATA_LOADER_PACKAGE_NAME = "dataLoaderPackageName";
+ private static final String ATTR_LENGTH_BYTES = "lengthBytes";
+ private static final String ATTR_METADATA = "metadata";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
@@ -278,6 +286,29 @@
@GuardedBy("mLock")
private int mParentSessionId;
+ static class FileInfo {
+ public final String name;
+ public final Long lengthBytes;
+ public final byte[] metadata;
+
+ public static FileInfo added(String name, Long lengthBytes, byte[] metadata) {
+ return new FileInfo(name, lengthBytes, metadata);
+ }
+
+ public static FileInfo removed(String name) {
+ return new FileInfo(name, -1L, null);
+ }
+
+ FileInfo(String name, Long lengthBytes, byte[] metadata) {
+ this.name = name;
+ this.lengthBytes = lengthBytes;
+ this.metadata = metadata;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private ArrayList<FileInfo> mFiles = new ArrayList<>();
+
@GuardedBy("mLock")
private boolean mStagedSessionApplied;
@GuardedBy("mLock")
@@ -313,6 +344,7 @@
@GuardedBy("mLock")
private boolean mVerityFound;
+ // TODO(b/146080380): merge file list with Callback installation.
private IncrementalFileStorages mIncrementalFileStorages;
private static final FileFilter sAddedFilter = new FileFilter() {
@@ -344,7 +376,9 @@
IntentSender statusReceiver;
switch (msg.what) {
case MSG_SEAL:
- handleSeal((IntentSender) msg.obj);
+ statusReceiver = (IntentSender) msg.obj;
+
+ handleSeal(statusReceiver);
break;
case MSG_COMMIT:
handleCommit();
@@ -378,6 +412,10 @@
}
};
+ private boolean isDataLoaderInstallation() {
+ return !TextUtils.isEmpty(params.dataLoaderPackageName);
+ }
+
/**
* @return {@code true} iff the installing is app an device owner or affiliated profile owner.
*/
@@ -435,7 +473,8 @@
PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
SessionParams params, long createdMillis,
- File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed,
+ File stageDir, String stageCid, FileInfo[] files, boolean prepared,
+ boolean committed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
String stagedSessionErrorMessage) {
@@ -464,6 +503,12 @@
}
this.mParentSessionId = parentSessionId;
+ if (files != null) {
+ for (FileInfo file : files) {
+ mFiles.add(file);
+ }
+ }
+
if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) {
throw new IllegalArgumentException(
"Exactly one of stageDir or stageCid stage must be set");
@@ -592,15 +637,19 @@
}
}
+ @GuardedBy("mLock")
+ private void setClientProgressLocked(float progress) {
+ // Always publish first staging movement
+ final boolean forcePublish = (mClientProgress == 0);
+ mClientProgress = progress;
+ computeProgressLocked(forcePublish);
+ }
+
@Override
public void setClientProgress(float progress) {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
-
- // Always publish first staging movement
- final boolean forcePublish = (mClientProgress == 0);
- mClientProgress = progress;
- computeProgressLocked(forcePublish);
+ setClientProgressLocked(progress);
}
}
@@ -608,8 +657,7 @@
public void addClientProgress(float progress) {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
-
- setClientProgress(mClientProgress + progress);
+ setClientProgressLocked(mClientProgress + progress);
}
}
@@ -637,7 +685,10 @@
@GuardedBy("mLock")
private String[] getNamesLocked() {
- return stageDir.list();
+ if (!isDataLoaderInstallation()) {
+ return stageDir.list();
+ }
+ return mFiles.stream().map(fileInfo -> fileInfo.name).toArray(String[]::new);
}
private static File[] filterFiles(File parent, String[] names, FileFilter filter) {
@@ -659,6 +710,10 @@
@Override
public void removeSplit(String splitName) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot remove splits in a callback installation session.");
+ }
if (TextUtils.isEmpty(params.appPackageName)) {
throw new IllegalStateException("Must specify package name to remove a split");
}
@@ -693,8 +748,31 @@
}
}
+ private void assertCanWrite(boolean reverseMode) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot write regular files in a callback installation session.");
+ }
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("assertCanWrite");
+ }
+ if (reverseMode) {
+ switch (Binder.getCallingUid()) {
+ case android.os.Process.SHELL_UID:
+ case android.os.Process.ROOT_UID:
+ case android.os.Process.SYSTEM_UID:
+ break;
+ default:
+ throw new SecurityException(
+ "Reverse mode only supported from shell or system");
+ }
+ }
+ }
+
@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+ assertCanWrite(false);
try {
return doWriteInternal(name, offsetBytes, lengthBytes, null);
} catch (IOException e) {
@@ -705,6 +783,7 @@
@Override
public void write(String name, long offsetBytes, long lengthBytes,
ParcelFileDescriptor fd) {
+ assertCanWrite(fd != null);
try {
doWriteInternal(name, offsetBytes, lengthBytes, fd);
} catch (IOException e) {
@@ -720,9 +799,6 @@
final RevocableFileDescriptor fd;
final FileBridge bridge;
synchronized (mLock) {
- assertCallerIsOwnerOrRootLocked();
- assertPreparedAndNotSealedLocked("openWrite");
-
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
fd = new RevocableFileDescriptor();
bridge = null;
@@ -765,16 +841,6 @@
}
if (incomingFd != null) {
- switch (Binder.getCallingUid()) {
- case android.os.Process.SHELL_UID:
- case android.os.Process.ROOT_UID:
- case android.os.Process.SYSTEM_UID:
- break;
- default:
- throw new SecurityException(
- "Reverse mode only supported from shell or system");
- }
-
// In "reverse" mode, we're streaming data ourselves from the
// incoming FD, which means we never have to hand out our
// sensitive internal FD. We still rely on a "bridge" being
@@ -786,7 +852,10 @@
if (params.sizeBytes > 0) {
final long delta = progress - last.value;
last.value = progress;
- addClientProgress((float) delta / (float) params.sizeBytes);
+ synchronized (mLock) {
+ setClientProgressLocked(mClientProgress
+ + (float) delta / (float) params.sizeBytes);
+ }
}
});
} finally {
@@ -821,6 +890,10 @@
@Override
public ParcelFileDescriptor openRead(String name) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot read regular files in a callback installation session.");
+ }
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
@@ -926,6 +999,7 @@
return;
}
}
+
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
@@ -988,6 +1062,14 @@
}
}
+ /** {@hide} */
+ private class StreamingException extends Exception {
+ StreamingException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+
/**
* Sanity checks to make sure it's ok to commit the session.
*/
@@ -1039,12 +1121,22 @@
}
wasSealed = mSealed;
- if (!mSealed) {
+ try {
+ if (!mSealed) {
+ sealLocked(childSessions);
+ }
+
try {
- sealAndValidateLocked(childSessions);
- } catch (PackageManagerException e) {
+ streamAndValidateLocked();
+ } catch (StreamingException e) {
+ // In case of streaming failure we don't want to fail or commit the session.
+ // Just return from this method and allow caller to commit again.
+ PackageInstallerService.sendPendingStreaming(mContext, mRemoteStatusReceiver,
+ sessionId, e);
return false;
}
+ } catch (PackageManagerException e) {
+ return false;
}
// Client staging is fully done at this point
@@ -1131,17 +1223,26 @@
}
/**
- * Seal the session to prevent further modification and validate the contents of it.
+ * Convenience wrapper, see {@link #sealLocked(List<PackageInstallerSession>) seal} and
+ * {@link #streamAndValidateLocked()}.
+ */
+ @GuardedBy("mLock")
+ private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
+ throws PackageManagerException, StreamingException {
+ sealLocked(childSessions);
+ streamAndValidateLocked();
+ }
+
+ /**
+ * Seal the session to prevent further modification.
*
* <p>The session will be sealed after calling this method even if it failed.
*
- * @param childSessions the child sessions of a multipackage that will be checked for
- * consistency. Can be null if session is not multipackage.
* @throws PackageManagerException if the session was sealed but something went wrong. If the
* session was sealed this is the only possible exception.
*/
@GuardedBy("mLock")
- private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
+ private void sealLocked(List<PackageInstallerSession> childSessions)
throws PackageManagerException {
try {
assertNoWriteFileTransfersOpenLocked();
@@ -1152,7 +1253,26 @@
if (childSessions != null) {
assertMultiPackageConsistencyLocked(childSessions);
}
+ } catch (PackageManagerException e) {
+ throw onSessionVerificationFailure(e);
+ } catch (Throwable e) {
+ // Convert all exceptions into package manager exceptions as only those are handled
+ // in the code above.
+ throw onSessionVerificationFailure(new PackageManagerException(e));
+ }
+ }
+ /**
+ * Prepare DataLoader and stream content for DataLoader sessions.
+ * Validate the contents of all session.
+ *
+ * @throws StreamingException if streaming failed.
+ * @throws PackageManagerException if validation failed.
+ */
+ @GuardedBy("mLock")
+ private void streamAndValidateLocked()
+ throws PackageManagerException, StreamingException {
+ try {
// Read transfers from the original owner stay open, but as the session's data cannot
// be modified anymore, there is no leak of information. For staged sessions, further
// validation is performed by the staging manager.
@@ -1161,6 +1281,8 @@
params.appPackageName, PackageManager.GET_SIGNATURES
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+ prepareDataLoader();
+
if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
validateApexInstallLocked();
} else {
@@ -1173,6 +1295,8 @@
}
} catch (PackageManagerException e) {
throw onSessionVerificationFailure(e);
+ } catch (StreamingException e) {
+ throw e;
} catch (Throwable e) {
// Convert all exceptions into package manager exceptions as only those are handled
// in the code above.
@@ -1208,6 +1332,8 @@
synchronized (mLock) {
try {
sealAndValidateLocked(childSessions);
+ } catch (StreamingException e) {
+ Slog.e(TAG, "Streaming failed", e);
} catch (PackageManagerException e) {
Slog.e(TAG, "Package not valid", e);
}
@@ -1277,6 +1403,8 @@
try {
sealAndValidateLocked(childSessions);
+ } catch (StreamingException e) {
+ throw new IllegalArgumentException("Streaming failed", e);
} catch (PackageManagerException e) {
throw new IllegalArgumentException("Package is not valid", e);
}
@@ -2224,6 +2352,132 @@
}
@Override
+ public void addFile(String name, long lengthBytes, byte[] metadata) {
+ if (mIncrementalFileStorages != null) {
+ try {
+ mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
+ } catch (IOException ex) {
+ throw new IllegalStateException(
+ "Failed to add and configure Incremental File: " + name, ex);
+ }
+ }
+ if (!isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot add files to non-callback installation session.");
+ }
+ // Use installer provided name for now; we always rename later
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new IllegalArgumentException("Invalid name: " + name);
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("addFile");
+
+ mFiles.add(FileInfo.added(name, lengthBytes, metadata));
+ }
+ }
+
+ @Override
+ public void removeFile(String name) {
+ if (!isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot add files to non-callback installation session.");
+ }
+ if (TextUtils.isEmpty(params.appPackageName)) {
+ throw new IllegalStateException("Must specify package name to remove a split");
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("removeFile");
+
+ mFiles.add(FileInfo.removed(getRemoveMarkerName(name)));
+ }
+ }
+
+ /**
+ * Makes sure files are present in staging location.
+ */
+ private void prepareDataLoader()
+ throws PackageManagerException, StreamingException {
+ if (!isDataLoaderInstallation()) {
+ return;
+ }
+
+ FilesystemConnector connector = new FilesystemConnector();
+
+ FileInfo[] addedFiles = mFiles.stream().filter(
+ file -> sAddedFilter.accept(new File(file.name))).toArray(FileInfo[]::new);
+ String[] removedFiles = mFiles.stream().filter(
+ file -> sRemovedFilter.accept(new File(file.name))).map(
+ file -> file.name.substring(0,
+ file.name.length() - REMOVE_MARKER_EXTENSION.length())).toArray(
+ String[]::new);
+
+ DataLoader dataLoader = new DataLoader();
+ try {
+ dataLoader.onCreate(connector);
+
+ if (!dataLoader.onPrepareImage(addedFiles, removedFiles)) {
+ throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+ "Failed to prepare image.");
+ }
+ } catch (IOException e) {
+ throw new StreamingException(e);
+ } finally {
+ dataLoader.onDestroy();
+ }
+ }
+
+ // TODO(b/146080380): implement DataLoader using Incremental infrastructure.
+ class FilesystemConnector {
+ void writeData(FileInfo fileInfo, long offset, long lengthBytes,
+ ParcelFileDescriptor incomingFd) throws IOException {
+ doWriteInternal(fileInfo.name, offset, lengthBytes, incomingFd);
+ }
+ }
+
+ static class DataLoader {
+ private ParcelFileDescriptor mInFd = null;
+ private FilesystemConnector mConnector = null;
+
+ void onCreate(FilesystemConnector connector) throws IOException {
+ mConnector = connector;
+ }
+
+ void onDestroy() {
+ IoUtils.closeQuietly(mInFd);
+ }
+
+ private static final String STDIN_PATH = "-";
+ boolean onPrepareImage(FileInfo[] addedFiles, String[] removedFiles) throws IOException {
+ for (FileInfo fileInfo : addedFiles) {
+ String filePath = new String(fileInfo.metadata, StandardCharsets.UTF_8);
+ if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
+ if (mInFd == null) {
+ Slog.e(TAG, "Invalid stdin file descriptor.");
+ return false;
+ }
+ ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(mInFd.getFileDescriptor());
+ mConnector.writeData(fileInfo, 0, fileInfo.lengthBytes, inFd);
+ } else {
+ File localFile = new File(filePath);
+ ParcelFileDescriptor incomingFd = null;
+ try {
+ incomingFd = ParcelFileDescriptor.open(localFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ mConnector.writeData(fileInfo, 0, localFile.length(), incomingFd);
+ } finally {
+ IoUtils.closeQuietly(incomingFd);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ @Override
public int[] getChildSessionIds() {
final int[] childSessionIds = mChildSessionIds.copyKeys();
if (childSessionIds != null) {
@@ -2295,20 +2549,6 @@
return mParentSessionId;
}
- @Override
- public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
- if (mIncrementalFileStorages == null) {
- throw new IllegalStateException(
- "Cannot add Incremental File to a non-Incremental session.");
- }
- try {
- mIncrementalFileStorages.addFile(new InstallationFile(name, size, metadata));
- } catch (IOException ex) {
- throw new IllegalStateException(
- "Failed to add and configure Incremental File: " + name, ex);
- }
- }
-
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
final IntentSender statusReceiver;
final String packageName;
@@ -2321,7 +2561,7 @@
}
if (statusReceiver != null) {
- // Execute observer.onPackageInstalled on different tread as we don't want callers
+ // Execute observer.onPackageInstalled on different thread as we don't want callers
// inside the system server have to worry about catching the callbacks while they are
// calling into the session
final SomeArgs args = SomeArgs.obtain();
@@ -2595,6 +2835,8 @@
writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
+ writeStringAttribute(out, ATTR_DATA_LOADER_PACKAGE_NAME, params.dataLoaderPackageName);
+
writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
writeWhitelistedRestrictedPermissionsLocked(out,
params.whitelistedRestrictedPermissions);
@@ -2624,6 +2866,13 @@
writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
out.endTag(null, TAG_CHILD_SESSION);
}
+ for (FileInfo fileInfo : mFiles) {
+ out.startTag(null, TAG_SESSION_FILE);
+ writeStringAttribute(out, ATTR_NAME, fileInfo.name);
+ writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes);
+ writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata);
+ out.endTag(null, TAG_SESSION_FILE);
+ }
}
out.endTag(null, TAG_SESSION);
@@ -2697,6 +2946,8 @@
params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
+ params.dataLoaderPackageName = readStringAttribute(in, ATTR_DATA_LOADER_PACKAGE_NAME);
+
final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
if (appIconFile.exists()) {
params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
@@ -2723,6 +2974,7 @@
List<String> grantedRuntimePermissions = new ArrayList<>();
List<String> whitelistedRestrictedPermissions = new ArrayList<>();
List<Integer> childSessionIds = new ArrayList<>();
+ List<FileInfo> files = new ArrayList<>();
int outerDepth = in.getDepth();
int type;
while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -2740,6 +2992,11 @@
if (TAG_CHILD_SESSION.equals(in.getName())) {
childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
}
+ if (TAG_SESSION_FILE.equals(in.getName())) {
+ files.add(new FileInfo(readStringAttribute(in, ATTR_NAME),
+ readLongAttribute(in, ATTR_LENGTH_BYTES, -1),
+ readByteArrayAttribute(in, ATTR_METADATA)));
+ }
}
if (grantedRuntimePermissions.size() > 0) {
@@ -2758,11 +3015,16 @@
childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
}
+ FileInfo[] fileInfosArray = null;
+ if (!files.isEmpty()) {
+ fileInfosArray = files.stream().toArray(FileInfo[]::new);
+ }
+
InstallSource installSource = InstallSource.create(installInitiatingPackageName,
installOriginatingPackageName, installerPackageName, false);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerUid,
- installSource, params, createdMillis, stageDir, stageCid,
+ installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
prepared, committed, sealed, childSessionIdsArray, parentSessionId,
isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ea9a0cb..1153fb5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -155,6 +155,7 @@
import android.content.pm.InstallSourceInfo;
import android.content.pm.InstantAppInfo;
import android.content.pm.InstantAppRequest;
+import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
@@ -376,6 +377,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -6143,10 +6145,12 @@
private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
Intent origIntent, String resolvedType, String callingPackage,
- Bundle verificationBundle, int userId) {
+ boolean isRequesterInstantApp, Bundle verificationBundle, int userId) {
final Message msg = mHandler.obtainMessage(INSTANT_APP_RESOLUTION_PHASE_TWO,
new InstantAppRequest(responseObj, origIntent, resolvedType,
- callingPackage, userId, verificationBundle, false /*resolveForStart*/));
+ callingPackage, isRequesterInstantApp, userId, verificationBundle,
+ false /*resolveForStart*/, responseObj.hostDigestPrefixSecure,
+ responseObj.token));
mHandler.sendMessage(msg);
}
@@ -6765,8 +6769,10 @@
}
}
if (addInstant) {
- result = maybeAddInstantAppInstaller(
- result, intent, resolvedType, flags, userId, resolveForStart);
+ String callingPkgName = getInstantAppPackageName(filterCallingUid);
+ boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
+ result = maybeAddInstantAppInstaller(result, intent, resolvedType, flags, userId,
+ resolveForStart, isRequesterInstantApp);
}
if (sortResult) {
Collections.sort(result, RESOLVE_PRIORITY_SORTER);
@@ -6777,7 +6783,8 @@
}
private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent,
- String resolvedType, int flags, int userId, boolean resolveForStart) {
+ String resolvedType, int flags, int userId, boolean resolveForStart,
+ boolean isRequesterInstantApp) {
// first, check to see if we've got an instant app already installed
final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
ResolveInfo localInstantApp = null;
@@ -6825,10 +6832,13 @@
if (localInstantApp == null) {
// we don't have an instant app locally, resolve externally
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral");
+ String token = UUID.randomUUID().toString();
+ InstantAppDigest digest = InstantAppResolver.parseDigest(intent);
final InstantAppRequest requestObject = new InstantAppRequest(
null /*responseObj*/, intent /*origIntent*/, resolvedType,
- null /*callingPackage*/, userId, null /*verificationBundle*/,
- resolveForStart);
+ null /*callingPackage*/, isRequesterInstantApp,
+ userId, null /*verificationBundle*/, resolveForStart,
+ digest.getDigestPrefixSecure(), token);
auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne(
mInstantAppResolverConnection, requestObject);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -22748,6 +22758,12 @@
}
@Override
+ public ComponentName getSystemUiServiceComponent() {
+ return ComponentName.unflattenFromString(mContext.getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
+ }
+
+ @Override
public void setDeviceAndProfileOwnerPackages(
int deviceOwnerUserId, String deviceOwnerPackage,
SparseArray<String> profileOwnerPackages) {
@@ -22827,10 +22843,10 @@
@Override
public void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
Intent origIntent, String resolvedType, String callingPackage,
- Bundle verificationBundle, int userId) {
+ boolean isRequesterInstantApp, Bundle verificationBundle, int userId) {
PackageManagerService.this.requestInstantAppResolutionPhaseTwo(
- responseObj, origIntent, resolvedType, callingPackage, verificationBundle,
- userId);
+ responseObj, origIntent, resolvedType, callingPackage, isRequesterInstantApp,
+ verificationBundle, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fff404f..dfffbd6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -114,6 +114,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -135,6 +136,8 @@
private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
private static final int DEFAULT_WAIT_MS = 60 * 1000;
+ private static final String PM_SHELL_DATALOADER = "com.android.pm.dataloader";
+
final IPackageManager mInterface;
final IPermissionManager mPermissionManager;
final private WeakHashMap<String, Resources> mResourceCache =
@@ -175,6 +178,8 @@
return runQueryIntentReceivers();
case "install":
return runInstall();
+ case "install-streaming":
+ return runStreamingInstall();
case "install-abandon":
case "install-destroy":
return runInstallAbandon();
@@ -1152,9 +1157,21 @@
return 0;
}
- private int runInstall() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
+ private int runStreamingInstall() throws RemoteException {
final InstallParams params = makeInstallParams();
+ if (TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName)) {
+ params.sessionParams.setDataLoaderPackageName(PM_SHELL_DATALOADER);
+ }
+ return doRunInstall(params);
+ }
+
+ private int runInstall() throws RemoteException {
+ return doRunInstall(makeInstallParams());
+ }
+
+ private int doRunInstall(final InstallParams params) throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final boolean streaming = !TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName);
ArrayList<String> inPaths = getRemainingArgs();
if (inPaths.isEmpty()) {
@@ -1181,17 +1198,30 @@
return 1;
}
- setParamsSize(params, inPaths);
+ if (!streaming) {
+ setParamsSize(params, inPaths);
+ }
+
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
boolean abandonSession = true;
try {
for (String inPath : inPaths) {
- String splitName = hasSplits ? (new File(inPath)).getName()
- : "base." + (isApex ? "apex" : "apk");
- if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
- false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
- return 1;
+ if (streaming) {
+ String name = new File(inPath).getName();
+ byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8);
+ if (doAddFile(sessionId, name, params.sessionParams.sizeBytes, metadata,
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ } else {
+ String splitName = hasSplits ? new File(inPath).getName()
+ : "base." + (isApex ? "apex" : "apk");
+
+ if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
}
}
if (doCommitSession(sessionId, false /*logSuccess*/)
@@ -2927,11 +2957,32 @@
return sessionId;
}
+ private int doAddFile(int sessionId, String name, long sizeBytes, byte[] metadata,
+ boolean logSuccess) throws RemoteException {
+ PackageInstaller.Session session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+ try {
+ session.addFile(name, sizeBytes, metadata);
+
+ if (logSuccess) {
+ getOutPrintWriter().println("Success");
+ }
+
+ return 0;
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
+ }
+
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
PackageInstaller.Session session = null;
try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+
final PrintWriter pw = getOutPrintWriter();
+
final ParcelFileDescriptor fd;
if (STDIN_PATH.equals(inPath)) {
fd = ParcelFileDescriptor.dup(getInFileDescriptor());
@@ -2953,8 +3004,6 @@
return 1;
}
- session = new PackageInstaller.Session(
- mInterface.getPackageInstaller().openSession(sessionId));
session.write(splitName, 0, sizeBytes, fd);
if (logSuccess) {
@@ -3000,7 +3049,6 @@
try {
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
-
for (String splitName : splitNames) {
session.removeSplit(splitName);
}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index e8798ff..59a5804 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -24,7 +24,20 @@
]
},
{
- "name": "PackageManagerShellCommandTest"
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
+ }
+ ]
+ },
+ {
+ "name": "GtsSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "com.google.android.security.gts.PackageVerifierTest"
+ }
+ ]
}
],
"postsubmit": [
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4d436c0..5fabdb6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -351,6 +351,7 @@
* User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
* that should be applied to all users, including guests. Only non-empty restriction bundles are
* stored.
+ * The key is the user id of the user whom the restriction originated from.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyGlobalUserRestrictions = new SparseArray<>();
@@ -364,6 +365,7 @@
/**
* User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
* for each user. Only non-empty restriction bundles are stored.
+ * The key is the user id of the user whom the restriction originated from.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
@@ -1621,7 +1623,7 @@
/**
* See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
*/
- private void setDevicePolicyUserRestrictionsInner(@UserIdInt int userId,
+ private void setDevicePolicyUserRestrictionsInner(@UserIdInt int originatingUserId,
@Nullable Bundle restrictions,
@UserManagerInternal.OwnerType int restrictionOwnerType) {
final Bundle global = new Bundle();
@@ -1635,16 +1637,16 @@
synchronized (mRestrictionsLock) {
// Update global and local restrictions if they were changed.
globalChanged = updateRestrictionsIfNeededLR(
- userId, global, mDevicePolicyGlobalUserRestrictions);
+ originatingUserId, global, mDevicePolicyGlobalUserRestrictions);
localChanged = updateRestrictionsIfNeededLR(
- userId, local, mDevicePolicyLocalUserRestrictions);
+ originatingUserId, local, mDevicePolicyLocalUserRestrictions);
if (restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER) {
// Remember the global restriction owner userId to be able to make a distinction
// in getUserRestrictionSource on who set local policies.
- mDeviceOwnerUserId = userId;
+ mDeviceOwnerUserId = originatingUserId;
} else {
- if (mDeviceOwnerUserId == userId) {
+ if (mDeviceOwnerUserId == originatingUserId) {
// When profile owner sets restrictions it passes null global bundle and we
// reset global restriction owner userId.
// This means this user used to have DO, but now the DO is gone and the user
@@ -1654,15 +1656,16 @@
}
}
if (DBG) {
- Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: userId=" + userId
- + " global=" + global + (globalChanged ? " (changed)" : "")
- + " local=" + local + (localChanged ? " (changed)" : "")
+ Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
+ + " originatingUserId=" + originatingUserId
+ + " global=" + global + (globalChanged ? " (changed)" : "")
+ + " local=" + local + (localChanged ? " (changed)" : "")
);
}
// Don't call them within the mRestrictionsLock.
synchronized (mPackagesLock) {
if (localChanged || globalChanged) {
- writeUserLP(getUserDataNoChecks(userId));
+ writeUserLP(getUserDataNoChecks(originatingUserId));
}
}
@@ -1670,7 +1673,7 @@
if (globalChanged) {
applyUserRestrictionsForAllUsersLR();
} else if (localChanged) {
- applyUserRestrictionsLR(userId);
+ applyUserRestrictionsLR(originatingUserId);
}
}
}
@@ -4507,9 +4510,10 @@
private class LocalService extends UserManagerInternal {
@Override
- public void setDevicePolicyUserRestrictions(@UserIdInt int userId,
- @Nullable Bundle restrictions, @OwnerType int restrictionOwnerType) {
- UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId,
+ public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId,
+ @Nullable Bundle restrictions,
+ @OwnerType int restrictionOwnerType) {
+ UserManagerService.this.setDevicePolicyUserRestrictionsInner(originatingUserId,
restrictions, restrictionOwnerType);
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 0beff7a..e0bd0b4 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -411,6 +411,13 @@
}
/**
+ * @return true if a restriction is settable by profile owner of an organization owned device.
+ */
+ public static boolean canProfileOwnerOfOrganizationOwnedDeviceChange(String restriction) {
+ return PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS.contains(restriction);
+ }
+
+ /**
* Returns the user restrictions that default to {@code true} for device owners.
* These user restrictions are local, though. ie only for the device owner's user id.
*/
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
new file mode 100644
index 0000000..3fa5230
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+/**
+ * An implementation of SoundTriggerMiddlewareImpl.AudioSessionProvider that ties to native
+ * AudioSystem module via JNI.
+ */
+class AudioSessionProviderImpl extends SoundTriggerMiddlewareImpl.AudioSessionProvider {
+ @Override
+ public native AudioSession acquireSession();
+
+ @Override
+ public native void releaseSession(int sessionHandle);
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
new file mode 100644
index 0000000..9b22f33
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.media.audio.common.AudioConfig;
+import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service
+ * types.
+ *
+ * @hide
+ */
+class ConversionUtil {
+ static @NonNull
+ SoundTriggerModuleProperties hidl2aidlProperties(
+ @NonNull ISoundTriggerHw.Properties hidlProperties) {
+ SoundTriggerModuleProperties aidlProperties = new SoundTriggerModuleProperties();
+ aidlProperties.implementor = hidlProperties.implementor;
+ aidlProperties.description = hidlProperties.description;
+ aidlProperties.version = hidlProperties.version;
+ aidlProperties.uuid = hidl2aidlUuid(hidlProperties.uuid);
+ aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
+ aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
+ aidlProperties.maxUsers = hidlProperties.maxUsers;
+ aidlProperties.recognitionModes = hidlProperties.recognitionModes;
+ aidlProperties.captureTransition = hidlProperties.captureTransition;
+ aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
+ aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
+ aidlProperties.triggerInEvent = hidlProperties.triggerInEvent;
+ aidlProperties.powerConsumptionMw = hidlProperties.powerConsumptionMw;
+ return aidlProperties;
+ }
+
+ static @NonNull
+ String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
+ if (hidlUuid.node == null || hidlUuid.node.length != 6) {
+ throw new IllegalArgumentException("UUID.node must be of length 6.");
+ }
+ return String.format(UuidUtil.FORMAT,
+ hidlUuid.timeLow,
+ hidlUuid.timeMid,
+ hidlUuid.versionAndTimeHigh,
+ hidlUuid.variantAndClockSeqHigh,
+ hidlUuid.node[0],
+ hidlUuid.node[1],
+ hidlUuid.node[2],
+ hidlUuid.node[3],
+ hidlUuid.node[4],
+ hidlUuid.node[5]);
+ }
+
+ static @NonNull
+ Uuid aidl2hidlUuid(@NonNull String aidlUuid) {
+ Matcher matcher = UuidUtil.PATTERN.matcher(aidlUuid);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Illegal format for UUID: " + aidlUuid);
+ }
+ Uuid hidlUuid = new Uuid();
+ hidlUuid.timeLow = Integer.parseUnsignedInt(matcher.group(1), 16);
+ hidlUuid.timeMid = (short) Integer.parseUnsignedInt(matcher.group(2), 16);
+ hidlUuid.versionAndTimeHigh = (short) Integer.parseUnsignedInt(matcher.group(3), 16);
+ hidlUuid.variantAndClockSeqHigh = (short) Integer.parseUnsignedInt(matcher.group(4), 16);
+ hidlUuid.node = new byte[]{(byte) Integer.parseUnsignedInt(matcher.group(5), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(6), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(7), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(8), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(9), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(10), 16)};
+ return hidlUuid;
+ }
+
+ static int aidl2hidlSoundModelType(int aidlType) {
+ switch (aidlType) {
+ case SoundModelType.GENERIC:
+ return android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC;
+ case SoundModelType.KEYPHRASE:
+ return android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE;
+ default:
+ throw new IllegalArgumentException("Unknown sound model type: " + aidlType);
+ }
+ }
+
+ static int hidl2aidlSoundModelType(int hidlType) {
+ switch (hidlType) {
+ case android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC:
+ return SoundModelType.GENERIC;
+ case android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE:
+ return SoundModelType.KEYPHRASE;
+ default:
+ throw new IllegalArgumentException("Unknown sound model type: " + hidlType);
+ }
+ }
+
+ static @NonNull
+ ISoundTriggerHw.Phrase aidl2hidlPhrase(@NonNull Phrase aidlPhrase) {
+ ISoundTriggerHw.Phrase hidlPhrase = new ISoundTriggerHw.Phrase();
+ hidlPhrase.id = aidlPhrase.id;
+ hidlPhrase.recognitionModes = aidl2hidlRecognitionModes(aidlPhrase.recognitionModes);
+ for (int aidlUser : aidlPhrase.users) {
+ hidlPhrase.users.add(aidlUser);
+ }
+ hidlPhrase.locale = aidlPhrase.locale;
+ hidlPhrase.text = aidlPhrase.text;
+ return hidlPhrase;
+ }
+
+ static int aidl2hidlRecognitionModes(int aidlModes) {
+ int hidlModes = 0;
+
+ if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ }
+ return hidlModes;
+ }
+
+ static int hidl2aidlRecognitionModes(int hidlModes) {
+ int aidlModes = 0;
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER) != 0) {
+ aidlModes |= RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION)
+ != 0) {
+ aidlModes |= RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION)
+ != 0) {
+ aidlModes |= RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER) != 0) {
+ aidlModes |= RecognitionMode.GENERIC_TRIGGER;
+ }
+ return aidlModes;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.SoundModel aidl2hidlSoundModel(@NonNull SoundModel aidlModel) {
+ ISoundTriggerHw.SoundModel hidlModel = new ISoundTriggerHw.SoundModel();
+ hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
+ hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
+ hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
+ hidlModel.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlModel.data,
+ "SoundTrigger SoundModel");
+ return hidlModel;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.PhraseSoundModel aidl2hidlPhraseSoundModel(
+ @NonNull PhraseSoundModel aidlModel) {
+ ISoundTriggerHw.PhraseSoundModel hidlModel = new ISoundTriggerHw.PhraseSoundModel();
+ hidlModel.common = aidl2hidlSoundModel(aidlModel.common);
+ for (Phrase aidlPhrase : aidlModel.phrases) {
+ hidlModel.phrases.add(aidl2hidlPhrase(aidlPhrase));
+ }
+ return hidlModel;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+ @NonNull RecognitionConfig aidlConfig) {
+ ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
+ hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+ for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
+ hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+ }
+ hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+ "SoundTrigger RecognitionConfig");
+ return hidlConfig;
+ }
+
+ static @NonNull
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra aidl2hidlPhraseRecognitionExtra(
+ @NonNull PhraseRecognitionExtra aidlExtra) {
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ hidlExtra.id = aidlExtra.id;
+ hidlExtra.recognitionModes = aidl2hidlRecognitionModes(aidlExtra.recognitionModes);
+ hidlExtra.confidenceLevel = aidlExtra.confidenceLevel;
+ hidlExtra.levels.ensureCapacity(aidlExtra.levels.length);
+ for (ConfidenceLevel aidlLevel : aidlExtra.levels) {
+ hidlExtra.levels.add(aidl2hidlConfidenceLevel(aidlLevel));
+ }
+ return hidlExtra;
+ }
+
+ static @NonNull
+ PhraseRecognitionExtra hidl2aidlPhraseRecognitionExtra(
+ @NonNull android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra) {
+ PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+ aidlExtra.id = hidlExtra.id;
+ aidlExtra.recognitionModes = hidl2aidlRecognitionModes(hidlExtra.recognitionModes);
+ aidlExtra.confidenceLevel = hidlExtra.confidenceLevel;
+ aidlExtra.levels = new ConfidenceLevel[hidlExtra.levels.size()];
+ for (int i = 0; i < hidlExtra.levels.size(); ++i) {
+ aidlExtra.levels[i] = hidl2aidlConfidenceLevel(hidlExtra.levels.get(i));
+ }
+ return aidlExtra;
+ }
+
+ static @NonNull
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel aidl2hidlConfidenceLevel(
+ @NonNull ConfidenceLevel aidlLevel) {
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ hidlLevel.userId = aidlLevel.userId;
+ hidlLevel.levelPercent = aidlLevel.levelPercent;
+ return hidlLevel;
+ }
+
+ static @NonNull
+ ConfidenceLevel hidl2aidlConfidenceLevel(
+ @NonNull android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel) {
+ ConfidenceLevel aidlLevel = new ConfidenceLevel();
+ aidlLevel.userId = hidlLevel.userId;
+ aidlLevel.levelPercent = hidlLevel.levelPercent;
+ return aidlLevel;
+ }
+
+ static int hidl2aidlRecognitionStatus(int hidlStatus) {
+ switch (hidlStatus) {
+ case ISoundTriggerHwCallback.RecognitionStatus.SUCCESS:
+ return RecognitionStatus.SUCCESS;
+ case ISoundTriggerHwCallback.RecognitionStatus.ABORT:
+ return RecognitionStatus.ABORTED;
+ case ISoundTriggerHwCallback.RecognitionStatus.FAILURE:
+ return RecognitionStatus.FAILURE;
+ case 3: // This doesn't have a constant in HIDL.
+ return RecognitionStatus.FORCED;
+ default:
+ throw new IllegalArgumentException("Unknown recognition status: " + hidlStatus);
+ }
+ }
+
+ static @NonNull
+ RecognitionEvent hidl2aidlRecognitionEvent(@NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+ RecognitionEvent aidlEvent = new RecognitionEvent();
+ aidlEvent.status = hidl2aidlRecognitionStatus(hidlEvent.status);
+ aidlEvent.type = hidl2aidlSoundModelType(hidlEvent.type);
+ aidlEvent.captureAvailable = hidlEvent.captureAvailable;
+ // hidlEvent.captureSession is never a valid field.
+ aidlEvent.captureSession = -1;
+ aidlEvent.captureDelayMs = hidlEvent.captureDelayMs;
+ aidlEvent.capturePreambleMs = hidlEvent.capturePreambleMs;
+ aidlEvent.triggerInData = hidlEvent.triggerInData;
+ aidlEvent.audioConfig = hidl2aidlAudioConfig(hidlEvent.audioConfig);
+ aidlEvent.data = new byte[hidlEvent.data.size()];
+ for (int i = 0; i < aidlEvent.data.length; ++i) {
+ aidlEvent.data[i] = hidlEvent.data.get(i);
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ RecognitionEvent hidl2aidlRecognitionEvent(
+ @NonNull ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+ RecognitionEvent aidlEvent = hidl2aidlRecognitionEvent(hidlEvent.header);
+ // Data needs to get overridden with 2.1 data.
+ aidlEvent.data = HidlMemoryUtil.hidlMemoryToByteArray(hidlEvent.data);
+ return aidlEvent;
+ }
+
+ static @NonNull
+ PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(@NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+ PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+ aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+ aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+ for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+ aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+ hidlEvent.phraseExtras.get(i));
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(
+ @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+ PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+ aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+ aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+ for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+ aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+ hidlEvent.phraseExtras.get(i));
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ AudioConfig hidl2aidlAudioConfig(
+ @NonNull android.hardware.audio.common.V2_0.AudioConfig hidlConfig) {
+ AudioConfig aidlConfig = new AudioConfig();
+ // TODO(ytai): channelMask and format might need a more careful conversion to make sure the
+ // constants match.
+ aidlConfig.sampleRateHz = hidlConfig.sampleRateHz;
+ aidlConfig.channelMask = hidlConfig.channelMask;
+ aidlConfig.format = hidlConfig.format;
+ aidlConfig.offloadInfo = hidl2aidlOffloadInfo(hidlConfig.offloadInfo);
+ aidlConfig.frameCount = hidlConfig.frameCount;
+ return aidlConfig;
+ }
+
+ static @NonNull
+ AudioOffloadInfo hidl2aidlOffloadInfo(
+ @NonNull android.hardware.audio.common.V2_0.AudioOffloadInfo hidlInfo) {
+ AudioOffloadInfo aidlInfo = new AudioOffloadInfo();
+ // TODO(ytai): channelMask, format, streamType and usage might need a more careful
+ // conversion to make sure the constants match.
+ aidlInfo.sampleRateHz = hidlInfo.sampleRateHz;
+ aidlInfo.channelMask = hidlInfo.channelMask;
+ aidlInfo.format = hidlInfo.format;
+ aidlInfo.streamType = hidlInfo.streamType;
+ aidlInfo.bitRatePerSecond = hidlInfo.bitRatePerSecond;
+ aidlInfo.durationMicroseconds = hidlInfo.durationMicroseconds;
+ aidlInfo.hasVideo = hidlInfo.hasVideo;
+ aidlInfo.isStreaming = hidlInfo.isStreaming;
+ aidlInfo.bitWidth = hidlInfo.bitWidth;
+ aidlInfo.bufferSize = hidlInfo.bufferSize;
+ aidlInfo.usage = hidlInfo.usage;
+ return aidlInfo;
+ }
+
+ @Nullable
+ static ModelParameterRange hidl2aidlModelParameterRange(
+ android.hardware.soundtrigger.V2_3.ModelParameterRange hidlRange) {
+ if (hidlRange == null) {
+ return null;
+ }
+ ModelParameterRange aidlRange = new ModelParameterRange();
+ aidlRange.minInclusive = hidlRange.start;
+ aidlRange.maxInclusive = hidlRange.end;
+ return aidlRange;
+ }
+
+ static int aidl2hidlModelParameter(int aidlParam) {
+ switch (aidlParam) {
+ case ModelParameter.THRESHOLD_FACTOR:
+ return android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR;
+
+ default:
+ return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalException.java b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
new file mode 100644
index 0000000..8b3e708
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a non-zero status code returned by a HAL invocation.
+ * Depending on the operation that threw the error, the integrity of the HAL implementation and the
+ * client's tolerance to error, this error may or may not be recoverable. The HAL itself is expected
+ * to retain the state it had prior to the invocation (so, unless the error is a result of a HAL
+ * bug, normal operation may resume).
+ * <p>
+ * The reason why this is a RuntimeException, even though the HAL interface allows returning them
+ * is because we expect none of them to actually occur as part of correct usage of the HAL.
+ *
+ * @hide
+ */
+public class HalException extends RuntimeException {
+ public final int errorCode;
+
+ public HalException(int errorCode, @NonNull String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public HalException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
new file mode 100644
index 0000000..f0a0d83
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.os.HidlMemoryUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Utilities for maintaining data compatibility between different minor versions of soundtrigger@2.x
+ * HAL.
+ * Note that some of these conversion utilities are destructive, i.e. mutate their input (for the
+ * sake of simplifying code and reducing copies).
+ */
+class Hw2CompatUtil {
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel convertSoundModel_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 = soundModel.header;
+ // Note: this mutates the input!
+ model_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(soundModel.data);
+ return model_2_0;
+ }
+
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent convertRecognitionEvent_2_0_to_2_1(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+ event_2_1.header = event;
+ event_2_1.data = HidlMemoryUtil.byteListToHidlMemory(event_2_1.header.data,
+ "SoundTrigger RecognitionEvent");
+ // Note: this mutates the input!
+ event_2_1.header.data = new ArrayList<>();
+ return event_2_1;
+ }
+
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent convertPhraseRecognitionEvent_2_0_to_2_1(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+ event_2_1 =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ event_2_1.common = convertRecognitionEvent_2_0_to_2_1(event.common);
+ event_2_1.phraseExtras = event.phraseExtras;
+ return event_2_1;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel convertPhraseSoundModel_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel();
+ model_2_0.common = convertSoundModel_2_1_to_2_0(soundModel.common);
+ model_2_0.phrases = soundModel.phrases;
+ return model_2_0;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
+ config.header;
+ // Note: this mutates the input!
+ config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+ return config_2_0;
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
new file mode 100644
index 0000000..81252c9
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.ModelParameterRange;
+import android.hidl.base.V1_0.IBase;
+import android.os.IHwBinder;
+
+/**
+ * This interface mimics android.hardware.soundtrigger.V2_x.ISoundTriggerHw and
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHwCallback, with a few key differences:
+ * <ul>
+ * <li>Methods in the original interface generally have a status return value and potentially a
+ * second return value which is the actual return value. This is reflected via a synchronous
+ * callback, which is not very pleasant to work with. This interface replaces that pattern with
+ * the convention that a HalException is thrown for non-OK status, and then we can use the
+ * return value for the actual return value.
+ * <li>This interface will always include all the methods from the latest 2.x version (and thus
+ * from every 2.x version) interface, with the convention that unsupported methods throw a
+ * {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * <li>Cases where the original interface had multiple versions of a method representing the exact
+ * thing, or there exists a trivial conversion between the new and old version, this interface
+ * represents only the latest version, without any _version suffixes.
+ * <li>Removes some of the obscure IBinder methods.
+ * <li>No RemoteExceptions are specified. Some implementations of this interface may rethrow
+ * RemoteExceptions as RuntimeExceptions, some can guarantee handling them somehow and never throw
+ * them.
+ * <li>soundModelCallback has been removed, since nobody cares about it. Implementations are free
+ * to silently discard it.
+ * </ul>
+ * For cases where the client wants to explicitly handle specific versions of the underlying driver
+ * interface, they may call {@link #interfaceDescriptor()}.
+ * <p>
+ * <b>Note to maintainers</b>: This class must always be kept in sync with the latest 2.x version,
+ * so that clients have access to the entire functionality without having to burden themselves with
+ * compatibility, as much as possible.
+ */
+public interface ISoundTriggerHw2 {
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+ */
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback)
+ */
+ int loadSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadPhraseSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback)
+ */
+ int loadPhraseSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#unloadSoundModel(int)
+ */
+ void unloadSoundModel(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopRecognition(int)
+ */
+ void stopRecognition(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopAllRecognitions()
+ */
+ void stopAllRecognitions();
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
+ */
+ void startRecognition(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getModelState(int)
+ */
+ void getModelState(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getParameter(int, int,
+ * ISoundTriggerHw.getParameterCallback)
+ */
+ int getModelParameter(int modelHandle, int param);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#setParameter(int, int, int)
+ */
+ void setModelParameter(int modelHandle, int param, int value);
+
+ /**
+ * @return null if not supported.
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#queryParameter(int, int,
+ * ISoundTriggerHw.queryParameterCallback)
+ */
+ ModelParameterRange queryParameter(int modelHandle, int param);
+
+ /**
+ * @see IHwBinder#linkToDeath(IHwBinder.DeathRecipient, long)
+ */
+ boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie);
+
+ /**
+ * @see IHwBinder#unlinkToDeath(IHwBinder.DeathRecipient)
+ */
+ boolean unlinkToDeath(IHwBinder.DeathRecipient recipient);
+
+ /**
+ * @see IBase#interfaceDescriptor()
+ */
+ String interfaceDescriptor() throws android.os.RemoteException;
+
+ interface Callback {
+ /**
+ * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#recognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent,
+ * int)
+ */
+ void recognitionCallback(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#phraseRecognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent,
+ * int)
+ */
+ void phraseRecognitionCallback(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie);
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
new file mode 100644
index 0000000..e1fb226
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * An internal server error.
+ * <p>
+ * This exception wraps any exception thrown from a service implementation, which is a result of a
+ * bug in the server implementation (or any of its dependencies).
+ * <p>
+ * Specifically, this type is excluded from the set of whitelisted exceptions that binder would
+ * tunnel to the client process, since these exceptions are ambiguous regarding whether the client
+ * had done something wrong or the server is buggy. For example, a client getting an
+ * IllegalArgumentException cannot easily determine whether they had provided illegal arguments to
+ * the method they were calling, or whether the method implementation provided illegal arguments to
+ * some method it was calling due to a bug.
+ *
+ * @hide
+ */
+public class InternalServerError extends RuntimeException {
+ public InternalServerError(@NonNull Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
new file mode 100644
index 0000000..8361850
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a fault which:
+ * <ul>
+ * <li>Could not have been anticipated by a caller (i.e. is not a violation of any preconditions).
+ * <li>Is guaranteed to not have been caused any meaningful state change in the callee. The caller
+ * may continue operation as if the call has never been made.
+ * </ul>
+ * <p>
+ * Some recoverable faults are permanent and some are transient / circumstantial, the specific error
+ * code can provide more information about the possible recovery options.
+ * <p>
+ * The reason why this is a RuntimeException is to allow it to go through interfaces defined by
+ * AIDL, which we have no control over.
+ *
+ * @hide
+ */
+public class RecoverableException extends RuntimeException {
+ public final int errorCode;
+
+ public RecoverableException(int errorCode, @NonNull String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public RecoverableException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
new file mode 100644
index 0000000..4a852c4
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * An implementation of {@link ISoundTriggerHw2}, on top of any
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHw implementation. This class hides away some of
+ * the details involved with retaining backward compatibility and adapts to the more pleasant syntax
+ * exposed by {@link ISoundTriggerHw2}, compared to the bare driver interface.
+ * <p>
+ * Exception handling:
+ * <ul>
+ * <li>All {@link RemoteException}s get rethrown as {@link RuntimeException}.
+ * <li>All HAL malfunctions get thrown as {@link HalException}.
+ * <li>All unsupported operations get thrown as {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * </ul>
+ */
+final class SoundTriggerHw2Compat implements ISoundTriggerHw2 {
+ private final @NonNull
+ IHwBinder mBinder;
+ private final @NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw mUnderlying_2_0;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw mUnderlying_2_1;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw mUnderlying_2_2;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw mUnderlying_2_3;
+
+ public SoundTriggerHw2Compat(
+ @NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw underlying) {
+ this(underlying.asBinder());
+ }
+
+ public SoundTriggerHw2Compat(IHwBinder binder) {
+ Objects.requireNonNull(binder);
+
+ mBinder = binder;
+
+ // We want to share the proxy instances rather than create a separate proxy for every
+ // version, so we go down the versions in descending order to find the latest one supported,
+ // and then simply up-cast it to obtain all the versions that are earlier.
+
+ // Attempt 2.3
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3 =
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.asInterface(binder);
+ if (as2_3 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = as2_3;
+ return;
+ }
+
+ // Attempt 2.2
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2 =
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw.asInterface(binder);
+ if (as2_2 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = as2_2;
+ mUnderlying_2_3 = null;
+ return;
+ }
+
+ // Attempt 2.1
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1 =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.asInterface(binder);
+ if (as2_1 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = as2_1;
+ mUnderlying_2_2 = mUnderlying_2_3 = null;
+ return;
+ }
+
+ // Attempt 2.0
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0 =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.asInterface(binder);
+ if (as2_0 != null) {
+ mUnderlying_2_0 = as2_0;
+ mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = null;
+ return;
+ }
+
+ throw new RuntimeException("Binder doesn't support ISoundTriggerHw@2.0");
+ }
+
+ private static void handleHalStatus(int status, String methodName) {
+ if (status != 0) {
+ throw new HalException(status, methodName);
+ }
+ }
+
+ @Override
+ public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+ properties =
+ new AtomicReference<>();
+ as2_0().getProperties(
+ (r, p) -> {
+ retval.set(r);
+ properties.set(p);
+ });
+ handleHalStatus(retval.get(), "getProperties");
+ return properties.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public int loadSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ Callback callback, int cookie) {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ try {
+ as2_1().loadSoundModel_2_1(soundModel, new SoundTriggerCallback(callback), cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return loadSoundModel_2_0(soundModel, callback, cookie);
+ }
+ handleHalStatus(retval.get(), "loadSoundModel_2_1");
+ return handle.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public int loadPhraseSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ Callback callback, int cookie) {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ try {
+ as2_1().loadPhraseSoundModel_2_1(soundModel, new SoundTriggerCallback(callback),
+ cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return loadPhraseSoundModel_2_0(soundModel, callback, cookie);
+ }
+ handleHalStatus(retval.get(), "loadSoundModel_2_1");
+ return handle.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void unloadSoundModel(int modelHandle) {
+ try {
+ int retval = as2_0().unloadSoundModel(modelHandle);
+ handleHalStatus(retval, "unloadSoundModel");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ try {
+ int retval = as2_0().stopRecognition(modelHandle);
+ handleHalStatus(retval, "stopRecognition");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+
+ }
+
+ @Override
+ public void stopAllRecognitions() {
+ try {
+ int retval = as2_0().stopAllRecognitions();
+ handleHalStatus(retval, "stopAllRecognitions");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ Callback callback, int cookie) {
+ try {
+ try {
+ int retval = as2_1().startRecognition_2_1(modelHandle, config,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition_2_1");
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ startRecognition_2_0(modelHandle, config, callback, cookie);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void getModelState(int modelHandle) {
+ try {
+ int retval = as2_2().getModelState(modelHandle);
+ handleHalStatus(retval, "getModelState");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int param) {
+ AtomicInteger status = new AtomicInteger(-1);
+ AtomicInteger value = new AtomicInteger(0);
+ try {
+ as2_3().getParameter(modelHandle, param,
+ (s, v) -> {
+ status.set(s);
+ value.set(v);
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ handleHalStatus(status.get(), "getParameter");
+ return value.get();
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int param, int value) {
+ try {
+ int retval = as2_3().setParameter(modelHandle, param, value);
+ handleHalStatus(retval, "setParameter");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ }
+
+ @Override
+ public android.hardware.soundtrigger.V2_3.ModelParameterRange queryParameter(int modelHandle,
+ int param) {
+ AtomicInteger status = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_3.OptionalModelParameterRange>
+ optionalRange =
+ new AtomicReference<>();
+ try {
+ as2_3().queryParameter(modelHandle, param,
+ (s, r) -> {
+ status.set(s);
+ optionalRange.set(r);
+ });
+ } catch (NotSupported e) {
+ // For older drivers, we consider no model parameter to be supported.
+ return null;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ handleHalStatus(status.get(), "queryParameter");
+ return (optionalRange.get().getDiscriminator()
+ == android.hardware.soundtrigger.V2_3.OptionalModelParameterRange.hidl_discriminator.range)
+ ?
+ optionalRange.get().range() : null;
+ }
+
+ @Override
+ public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+ return mBinder.linkToDeath(recipient, cookie);
+ }
+
+ @Override
+ public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+ return mBinder.unlinkToDeath(recipient);
+ }
+
+ @Override
+ public String interfaceDescriptor() throws RemoteException {
+ return as2_0().interfaceDescriptor();
+ }
+
+ private int loadSoundModel_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ Callback callback, int cookie)
+ throws RemoteException {
+ // Convert the soundModel to V2.0.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 =
+ Hw2CompatUtil.convertSoundModel_2_1_to_2_0(soundModel);
+
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ as2_0().loadSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie, (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ handleHalStatus(retval.get(), "loadSoundModel");
+ return handle.get();
+ }
+
+ private int loadPhraseSoundModel_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ Callback callback, int cookie)
+ throws RemoteException {
+ // Convert the soundModel to V2.0.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+ Hw2CompatUtil.convertPhraseSoundModel_2_1_to_2_0(soundModel);
+
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ as2_0().loadPhraseSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ handleHalStatus(retval.get(), "loadSoundModel");
+ return handle.get();
+ }
+
+ private void startRecognition_2_0(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ Callback callback, int cookie)
+ throws RemoteException {
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
+ Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+ int retval = as2_0().startRecognition(modelHandle, config_2_0,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition");
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0() {
+ return mUnderlying_2_0;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1() throws NotSupported {
+ if (mUnderlying_2_1 == null) {
+ throw new NotSupported("Underlying driver version < 2.1");
+ }
+ return mUnderlying_2_1;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2() throws NotSupported {
+ if (mUnderlying_2_2 == null) {
+ throw new NotSupported("Underlying driver version < 2.2");
+ }
+ return mUnderlying_2_2;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3() throws NotSupported {
+ if (mUnderlying_2_3 == null) {
+ throw new NotSupported("Underlying driver version < 2.3");
+ }
+ return mUnderlying_2_3;
+ }
+
+ /**
+ * A checked exception representing the requested interface version not being supported.
+ * At the public interface layer, use {@link #throwAsRecoverableException()} to propagate it to
+ * the caller if the request cannot be fulfilled.
+ */
+ private static class NotSupported extends Exception {
+ NotSupported(String message) {
+ super(message);
+ }
+
+ /**
+ * Throw this as a recoverable exception.
+ *
+ * @return Never actually returns anything. Always throws. Used so that caller can write
+ * throw e.throwAsRecoverableException().
+ */
+ RecoverableException throwAsRecoverableException() {
+ throw new RecoverableException(Status.OPERATION_NOT_SUPPORTED, getMessage());
+ }
+ }
+
+ private static class SoundTriggerCallback extends
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.Stub {
+ private final @NonNull
+ Callback mDelegate;
+
+ private SoundTriggerCallback(
+ @NonNull Callback delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ @Override
+ public void recognitionCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie) {
+ mDelegate.recognitionCallback(event, cookie);
+ }
+
+ @Override
+ public void phraseRecognitionCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie) {
+ mDelegate.phraseRecognitionCallback(event, cookie);
+ }
+
+ @Override
+ public void soundModelCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent event,
+ int cookie) {
+ // Nobody cares.
+ }
+
+ @Override
+ public void recognitionCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+ Hw2CompatUtil.convertRecognitionEvent_2_0_to_2_1(event);
+ mDelegate.recognitionCallback(event_2_1, cookie);
+ }
+
+ @Override
+ public void phraseRecognitionCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+ event_2_1 = Hw2CompatUtil.convertPhraseRecognitionEvent_2_0_to_2_1(event);
+ mDelegate.phraseRecognitionCallback(event_2_1, cookie);
+ }
+
+ @Override
+ public void soundModelCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent event,
+ int cookie) {
+ // Nobody cares.
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
new file mode 100644
index 0000000..9d51b65
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an implementation of the ISoundTriggerMiddlewareService interface.
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle invalid
+ * usage, and such usage will result in undefined behavior. If this service is to be offered to an
+ * untrusted client, it must be wrapped with input and state validation.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status}
+ * constants. Any other exception thrown should be regarded as a bug in the implementation or one
+ * of its dependencies (assuming correct usage).
+ * <li>The implementation is designed for testibility by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies on
+ * Android runtime.
+ * <li>The implementation is thread-safe.
+ * </ul>
+ *
+ * @hide
+ */
+public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService {
+ static private final String TAG = "SoundTriggerMiddlewareImpl";
+ private final SoundTriggerModule[] mModules;
+
+ /**
+ * Interface to the audio system, which can allocate capture session handles.
+ * SoundTrigger uses those sessions in order to associate a recognition session with an optional
+ * capture from the same device that triggered the recognition.
+ */
+ public static abstract class AudioSessionProvider {
+ public static final class AudioSession {
+ final int mSessionHandle;
+ final int mIoHandle;
+ final int mDeviceHandle;
+
+ AudioSession(int sessionHandle, int ioHandle, int deviceHandle) {
+ mSessionHandle = sessionHandle;
+ mIoHandle = ioHandle;
+ mDeviceHandle = deviceHandle;
+ }
+ }
+
+ public abstract AudioSession acquireSession();
+
+ public abstract void releaseSession(int sessionHandle);
+ }
+
+ /**
+ * Most generic constructor - gets an array of HAL driver instances.
+ */
+ public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+ @NonNull AudioSessionProvider audioSessionProvider) {
+ List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+
+ for (int i = 0; i < halServices.length; ++i) {
+ ISoundTriggerHw service = halServices[i];
+ try {
+ modules.add(new SoundTriggerModule(service, audioSessionProvider));
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+ }
+ }
+
+ mModules = modules.toArray(new SoundTriggerModule[modules.size()]);
+ }
+
+ /**
+ * Convenience constructor - gets a single HAL driver instance.
+ */
+ public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+ @NonNull AudioSessionProvider audioSessionProvider) {
+ this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ SoundTriggerModuleDescriptor[] result = new SoundTriggerModuleDescriptor[mModules.length];
+
+ for (int i = 0; i < mModules.length; ++i) {
+ SoundTriggerModuleDescriptor desc = new SoundTriggerModuleDescriptor();
+ desc.handle = i;
+ desc.properties = mModules[i].getProperties();
+ result[i] = desc;
+ }
+ return result;
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+ return mModules[handle].attach(callback);
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ for (SoundTriggerModule module : mModules) {
+ module.setExternalCaptureState(active);
+ }
+ }
+
+ @Override
+ public @NonNull
+ IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not inteded to be used directly with Binder.");
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
new file mode 100644
index 0000000..a7cfe10
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.SystemService;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes
+ * it as a Binder service and enforces permissions and correct usage by the client, as well as makes
+ * sure that exceptions representing a server malfunction do not get sent to the client.
+ * <p>
+ * This is intended to extract the non-business logic out of the underlying implementation and thus
+ * make it easier to maintain each one of those separate aspects. A design trade-off is being made
+ * here, in that this class would need to essentially eavesdrop on all the client-server
+ * communication and retain all state known to the client, while the client doesn't necessarily care
+ * about all of it, and while the server has its own representation of this information. However,
+ * in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
+ * There is also some additional cost in employing a simplistic locking mechanism here, but
+ * following the same line of reasoning, the benefits in code simplicity outweigh it.
+ * <p>
+ * Every public method in this class, overriding an interface method, must follow the following
+ * pattern:
+ * <code><pre>
+ * @Override public T method(S arg) {
+ * // Permission check.
+ * checkPermissions();
+ * // Input validation.
+ * ValidationUtil.validateS(arg);
+ * synchronized (this) {
+ * // State validation.
+ * if (...state is not valid for this call...) {
+ * throw new IllegalStateException("State is invalid because...");
+ * }
+ * // From here on, every exception isn't client's fault.
+ * try {
+ * T result = mDelegate.method(arg);
+ * // Update state.;
+ * ...
+ * return result;
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * }
+ * }
+ * </pre></code>
+ * Following this patterns ensures a consistent and rigorous handling of all aspects associated
+ * with client-server separation.
+ * <p>
+ * <b>Exception handling approach:</b><br>
+ * We make sure all client faults (permissions, argument and state validation) happen first, and
+ * would throw {@link SecurityException}, {@link IllegalArgumentException}/
+ * {@link NullPointerException} or {@link
+ * IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
+ * will get sent back to the client.<br>
+ * Once this is done, any subsequent fault is considered a server fault. Only {@link
+ * RecoverableException}s thrown by the implementation are special-cased: they would get sent back
+ * to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
+ * exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
+ * that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
+ * exception handler on the server side, typically resulting in rebooting the server.
+ * <p>
+ * <b>Exposing this service as a System Service:</b><br>
+ * Insert this line into {@link com.android.server.SystemServer}:
+ * <code><pre>
+ * mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ * </pre></code>
+ *
+ * {@hide}
+ */
+public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
+ static private final String TAG = "SoundTriggerMiddlewareService";
+
+ final ISoundTriggerMiddlewareService mDelegate;
+ final Context mContext;
+ Set<Integer> mModuleHandles;
+
+ /**
+ * Constructor for internal use only. Could be exposed for testing purposes in the future.
+ * Users should access this class via {@link Lifecycle}.
+ */
+ private SoundTriggerMiddlewareService(
+ @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
+ mDelegate = delegate;
+ mContext = context;
+ }
+
+ /**
+ * Generic exception handling for exceptions thrown by the underlying implementation.
+ *
+ * Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
+ * by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
+ * (<b>not</b> passed by Binder to the caller).
+ * <p>
+ * Typical usage:
+ * <code><pre>
+ * try {
+ * ... Do server operations ...
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * </pre></code>
+ */
+ private static @NonNull
+ RuntimeException handleException(@NonNull Exception e) {
+ if (e instanceof RecoverableException) {
+ throw new ServiceSpecificException(((RecoverableException) e).errorCode,
+ e.getMessage());
+ }
+ throw new InternalServerError(e);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
+ mModuleHandles = new HashSet<>(result.length);
+ for (SoundTriggerModuleDescriptor desc : result) {
+ mModuleHandles.add(desc.handle);
+ }
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(callback.asBinder());
+
+ synchronized (this) {
+ // State validation.
+ if (mModuleHandles == null) {
+ throw new IllegalStateException(
+ "Client must call listModules() prior to attaching.");
+ }
+ if (!mModuleHandles.contains(handle)) {
+ throw new IllegalArgumentException("Invalid handle: " + handle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModuleService moduleService = new ModuleService(callback);
+ moduleService.attach(mDelegate.attach(handle, moduleService));
+ return moduleService;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ // Permission check.
+ checkPreemptPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setExternalCaptureState(active);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
+ * service.
+ */
+ private void checkPermissions() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
+ "Caller must have the android.permission.RECORD_AUDIO permission.");
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
+ "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
+ * active sound trigger sessions.
+ */
+ private void checkPreemptPermissions() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
+ "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+ }
+
+ /** State of a sound model. */
+ static class ModelState {
+ /** Activity state of a sound model. */
+ enum Activity {
+ /** Model is loaded, recognition is inactive. */
+ LOADED,
+ /** Model is loaded, recognition is active. */
+ ACTIVE
+ }
+
+ /** Activity state. */
+ public Activity activityState = Activity.LOADED;
+
+ /**
+ * A map of known parameter support. A missing key means we don't know yet whether the
+ * parameter is supported. A null value means it is known to not be supported. A non-null
+ * value indicates the valid value range.
+ */
+ private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
+
+ /**
+ * Check that the given parameter is known to be supported for this model.
+ *
+ * @param modelParam The parameter key.
+ */
+ public void checkSupported(int modelParam) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ }
+
+ /**
+ * Check that the given parameter is known to be supported for this model and that the given
+ * value is a valid value for it.
+ *
+ * @param modelParam The parameter key.
+ * @param value The value.
+ */
+ public void checkSupported(int modelParam, int value) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
+ "value");
+ }
+
+ /**
+ * Update support state for the given parameter for this model.
+ *
+ * @param modelParam The parameter key.
+ * @param range The parameter value range, or null if not supported.
+ */
+ public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+ parameterSupport.put(modelParam, range);
+ }
+ }
+
+ /**
+ * Entry-point to this module: exposes the module as a {@link SystemService}.
+ */
+ public static final class Lifecycle extends SystemService {
+ private SoundTriggerMiddlewareService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ ISoundTriggerHw[] services;
+ try {
+ services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
+ Log.d(TAG, "Connected to default ISoundTriggerHw");
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
+ services = new ISoundTriggerHw[0];
+ }
+
+ mService = new SoundTriggerMiddlewareService(
+ new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
+ getContext());
+ publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+ }
+ }
+
+ /**
+ * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
+ * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
+ */
+ private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
+ DeathRecipient {
+ private final ISoundTriggerCallback mCallback;
+ private ISoundTriggerModule mDelegate;
+ private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+
+ ModuleService(@NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void attach(@NonNull ISoundTriggerModule delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateGenericModel(model);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validatePhraseModel(model);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadPhraseModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for unloading: " + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.unloadModel(modelHandle);
+ mLoadedModels.remove(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateRecognitionConfig(config);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for starting recognition: "
+ + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.startRecognition(modelHandle, config);
+ modelState.activityState = ModelState.Activity.ACTIVE;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // stopRecognition is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.stopRecognition(modelHandle);
+ modelState.activityState = ModelState.Activity.LOADED;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // forceRecognitionEvent is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam, value);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ return mDelegate.getModelParameter(modelHandle, modelParam);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
+ modelParam);
+ modelState.updateParameterSupport(modelParam, result);
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void detach() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has already been detached.");
+ }
+ if (!mLoadedModels.isEmpty()) {
+ throw new IllegalStateException("Cannot detach while models are loaded.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ private void detachInternal() {
+ try {
+ mDelegate.detach();
+ mDelegate = null;
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Callbacks
+
+ @Override
+ public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
+ synchronized (this) {
+ if (event.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
+ synchronized (this) {
+ if (event.common.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onPhraseRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ synchronized (this) {
+ try {
+ mCallback.onRecognitionAvailabilityChange(available);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ // This is called whenever our client process dies.
+ synchronized (this) {
+ try {
+ // Gracefully stop all active recognitions and unload the models.
+ for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
+ if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
+ mDelegate.stopRecognition(entry.getKey());
+ }
+ mDelegate.unloadModel(entry.getKey());
+ }
+ // Detach.
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
new file mode 100644
index 0000000..81789e1
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is an implementation of a single module of the ISoundTriggerMiddlewareService interface,
+ * exposing itself through the {@link ISoundTriggerModule} interface, possibly to multiple separate
+ * clients.
+ * <p>
+ * Typical usage is to query the module capabilities using {@link #getProperties()} and then to use
+ * the module through an {@link ISoundTriggerModule} instance, obtained via {@link
+ * #attach(ISoundTriggerCallback)}. Every such interface is its own session and state is not shared
+ * between sessions (i.e. cannot use a handle obtained from one session through another).
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle
+ * invalid usage, and such usage will result in undefined behavior. If this service is to be
+ * offered to an untrusted client, it must be wrapped with input and state validation.
+ * <li>The underlying driver is assumed to be correct. This implementation does not attempt to
+ * gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
+ * service is to used with an untrusted driver, the driver must be wrapped with validation / error
+ * recovery code.
+ * <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
+ * considered recoverable faults and should not occur in a properly functioning system.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status} constants. Any other exception
+ * thrown should be regarded as a bug in the implementation or one of its dependencies
+ * (assuming correct usage).
+ * <li>The implementation is designed for testability by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies
+ * on Android runtime.
+ * <li>The implementation is thread-safe. This is achieved by a simplistic model, where all entry-
+ * points (both client API and driver callbacks) obtain a lock on the SoundTriggerModule instance
+ * for their entire scope. Any other method can be assumed to be running with the lock already
+ * obtained, so no further locking should be done. While this is not necessarily the most efficient
+ * synchronization strategy, it is very easy to reason about and this code is likely not on any
+ * performance-critical
+ * path.
+ * </ul>
+ *
+ * @hide
+ */
+class SoundTriggerModule {
+ static private final String TAG = "SoundTriggerModule";
+ @NonNull private final ISoundTriggerHw2 mHalService;
+ @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
+ private final Set<Session> mActiveSessions = new HashSet<>();
+ private int mNumLoadedModels = 0;
+ private SoundTriggerModuleProperties mProperties = null;
+ private boolean mRecognitionAvailable;
+
+ /**
+ * Ctor.
+ *
+ * @param halService The underlying HAL driver.
+ */
+ SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+ @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
+ assert halService != null;
+ mHalService = new SoundTriggerHw2Compat(halService);
+ mAudioSessionProvider = audioSessionProvider;
+ mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
+
+ // We conservatively assume that external capture is active until explicitly told otherwise.
+ mRecognitionAvailable = mProperties.concurrentCapture;
+ }
+
+ /**
+ * Establish a client session with this module.
+ *
+ * This module may be shared by multiple clients, each will get its own session. While resources
+ * are shared between the clients, each session has its own state and data should not be shared
+ * across sessions.
+ *
+ * @param callback The client callback, which will be used for all messages. This is a oneway
+ * callback, so will never block, throw an unchecked exception or return a
+ * value.
+ * @return The interface through which this module can be controlled.
+ */
+ synchronized @NonNull
+ Session attach(@NonNull ISoundTriggerCallback callback) {
+ Log.d(TAG, "attach()");
+ Session session = new Session(callback);
+ mActiveSessions.add(session);
+ return session;
+ }
+
+ /**
+ * Query the module's properties.
+ *
+ * @return The properties structure.
+ */
+ synchronized @NonNull
+ SoundTriggerModuleProperties getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Notify the module that external capture has started / finished, using the same input device
+ * used for recognition.
+ * If the underlying driver does not support recognition while capturing, capture will be
+ * aborted, and the recognition callback will receive and abort event. In addition, all active
+ * clients will be notified of the change in state.
+ *
+ * @param active true iff external capture is active.
+ */
+ synchronized void setExternalCaptureState(boolean active) {
+ Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+ if (mProperties.concurrentCapture) {
+ // If we support concurrent capture, we don't care about any of this.
+ return;
+ }
+ mRecognitionAvailable = !active;
+ if (!mRecognitionAvailable) {
+ // Our module does not support recognition while a capture is active -
+ // need to abort all active recognitions.
+ for (Session session : mActiveSessions) {
+ session.abortActiveRecognitions();
+ }
+ }
+ for (Session session : mActiveSessions) {
+ session.notifyRecognitionAvailability();
+ }
+ }
+
+ /**
+ * Remove session from the list of active sessions.
+ *
+ * @param session The session to remove.
+ */
+ private void removeSession(@NonNull Session session) {
+ mActiveSessions.remove(session);
+ }
+
+ /** State of a single sound model. */
+ private enum ModelState {
+ /** Initial state, until load() is called. */
+ INIT,
+ /** Model is loaded, but recognition is not active. */
+ LOADED,
+ /** Model is loaded and recognition is active. */
+ ACTIVE
+ }
+
+ /**
+ * A single client session with this module.
+ *
+ * This is the main interface used to interact with this module.
+ */
+ private class Session implements ISoundTriggerModule {
+ private ISoundTriggerCallback mCallback;
+ private Map<Integer, Model> mLoadedModels = new HashMap<>();
+
+ /**
+ * Ctor.
+ *
+ * @param callback The client callback interface.
+ */
+ private Session(@NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ notifyRecognitionAvailability();
+ }
+
+ @Override
+ public void detach() {
+ Log.d(TAG, "detach()");
+ synchronized (SoundTriggerModule.this) {
+ removeSession(this);
+ }
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ Log.d(TAG, String.format("loadModel(model=%s)", model));
+ synchronized (SoundTriggerModule.this) {
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model);
+ ++mNumLoadedModels;
+ return result;
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
+ synchronized (SoundTriggerModule.this) {
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model);
+ ++mNumLoadedModels;
+ Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+ return result;
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).unload();
+ --mNumLoadedModels;
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ Log.d(TAG,
+ String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).startRecognition(config);
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ Log.d(TAG, String.format("stopRecognition(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).stopRecognition();
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).forceRecognitionEvent();
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value)
+ throws RemoteException {
+ Log.d(TAG,
+ String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
+ modelParam, value));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).setParameter(modelParam, value);
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
+ modelParam));
+ synchronized (SoundTriggerModule.this) {
+ return mLoadedModels.get(modelHandle).getParameter(modelParam);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
+ modelParam));
+ synchronized (SoundTriggerModule.this) {
+ return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
+ }
+ }
+
+ /**
+ * Abort all currently active recognitions.
+ */
+ private void abortActiveRecognitions() {
+ for (Model model : mLoadedModels.values()) {
+ model.abortActiveRecognition();
+ }
+ }
+
+ private void notifyRecognitionAvailability() {
+ try {
+ mCallback.onRecognitionAvailabilityChange(mRecognitionAvailable);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+
+ @Override
+ public @NonNull
+ IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not intended to be used directly with Binder.");
+ }
+
+ /**
+ * A single sound model in the system.
+ *
+ * All model-based operations are delegated to this class and implemented here.
+ */
+ private class Model implements ISoundTriggerHw2.Callback {
+ public int mHandle;
+ private ModelState mState = ModelState.INIT;
+ private int mModelType = SoundModelType.UNKNOWN;
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;
+
+ private @NonNull
+ ModelState getState() {
+ return mState;
+ }
+
+ private void setState(@NonNull ModelState state) {
+ mState = state;
+ SoundTriggerModule.this.notifyAll();
+ }
+
+ private void waitStateChange() throws InterruptedException {
+ SoundTriggerModule.this.wait();
+ }
+
+ private int load(@NonNull SoundModel model) {
+ mModelType = model.type;
+ ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
+
+ mSession = mAudioSessionProvider.acquireSession();
+ try {
+ mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
+ } catch (Exception e) {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ throw e;
+ }
+
+ setState(ModelState.LOADED);
+ mLoadedModels.put(mHandle, this);
+ return mHandle;
+ }
+
+ private int load(@NonNull PhraseSoundModel model) {
+ mModelType = model.common.type;
+ ISoundTriggerHw.PhraseSoundModel hidlModel =
+ ConversionUtil.aidl2hidlPhraseSoundModel(model);
+
+ mSession = mAudioSessionProvider.acquireSession();
+ try {
+ mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
+ } catch (Exception e) {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ throw e;
+ }
+
+ setState(ModelState.LOADED);
+ mLoadedModels.put(mHandle, this);
+ return mHandle;
+ }
+
+ private void unload() {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ mHalService.unloadSoundModel(mHandle);
+ mLoadedModels.remove(mHandle);
+ }
+
+ private void startRecognition(@NonNull RecognitionConfig config) {
+ if (!mRecognitionAvailable) {
+ // Recognition is unavailable - send an abort event immediately.
+ notifyAbort();
+ return;
+ }
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+ ConversionUtil.aidl2hidlRecognitionConfig(config);
+ hidlConfig.header.captureDevice = mSession.mDeviceHandle;
+ hidlConfig.header.captureHandle = mSession.mIoHandle;
+ mHalService.startRecognition(mHandle, hidlConfig, this, 0);
+ setState(ModelState.ACTIVE);
+ }
+
+ private void stopRecognition() {
+ if (getState() == ModelState.LOADED) {
+ // This call is idempotent in order to avoid races.
+ return;
+ }
+ mHalService.stopRecognition(mHandle);
+ setState(ModelState.LOADED);
+ }
+
+ /** Request a forced recognition event. Will do nothing if recognition is inactive. */
+ private void forceRecognitionEvent() {
+ if (getState() != ModelState.ACTIVE) {
+ // This call is idempotent in order to avoid races.
+ return;
+ }
+ mHalService.getModelState(mHandle);
+ }
+
+
+ private void setParameter(int modelParam, int value) {
+ mHalService.setModelParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam), value);
+ }
+
+ private int getParameter(int modelParam) {
+ return mHalService.getModelParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam));
+ }
+
+ @Nullable
+ private ModelParameterRange queryModelParameterSupport(int modelParam) {
+ return ConversionUtil.hidl2aidlModelParameterRange(
+ mHalService.queryParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam)));
+ }
+
+ /** Abort the recognition, if active. */
+ private void abortActiveRecognition() {
+ // If we're inactive, do nothing.
+ if (getState() != ModelState.ACTIVE) {
+ return;
+ }
+ // Stop recognition.
+ stopRecognition();
+
+ // Notify the client that recognition has been aborted.
+ notifyAbort();
+ }
+
+ /** Notify the client that recognition has been aborted. */
+ private void notifyAbort() {
+ try {
+ switch (mModelType) {
+ case SoundModelType.GENERIC: {
+ android.media.soundtrigger_middleware.RecognitionEvent event =
+ new android.media.soundtrigger_middleware.RecognitionEvent();
+ event.status =
+ android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+ mCallback.onRecognition(mHandle, event);
+ }
+ break;
+
+ case SoundModelType.KEYPHRASE: {
+ android.media.soundtrigger_middleware.PhraseRecognitionEvent event =
+ new android.media.soundtrigger_middleware.PhraseRecognitionEvent();
+ event.common =
+ new android.media.soundtrigger_middleware.RecognitionEvent();
+ event.common.status =
+ android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+ mCallback.onPhraseRecognition(mHandle, event);
+ }
+ break;
+
+ default:
+ Log.e(TAG, "Unknown model type: " + mModelType);
+
+ }
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+
+ @Override
+ public void recognitionCallback(
+ @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
+ int cookie) {
+ Log.d(TAG, String.format("recognitionCallback_2_1(event=%s, cookie=%d)",
+ recognitionEvent, cookie));
+ synchronized (SoundTriggerModule.this) {
+ android.media.soundtrigger_middleware.RecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
+ aidlEvent.captureSession = mSession.mSessionHandle;
+ try {
+ mCallback.onRecognition(mHandle, aidlEvent);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ if (aidlEvent.status
+ != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+ setState(ModelState.LOADED);
+ }
+ }
+ }
+
+ @Override
+ public void phraseRecognitionCallback(
+ @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
+ int cookie) {
+ Log.d(TAG, String.format("phraseRecognitionCallback_2_1(event=%s, cookie=%d)",
+ phraseRecognitionEvent, cookie));
+ synchronized (SoundTriggerModule.this) {
+ android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
+ aidlEvent.common.captureSession = mSession.mSessionHandle;
+ try {
+ mCallback.onPhraseRecognition(mHandle, aidlEvent);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ if (aidlEvent.common.status
+ != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+ setState(ModelState.LOADED);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
new file mode 100644
index 0000000..9ed894b
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.soundtrigger_middleware"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
new file mode 100644
index 0000000..80f69d0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for representing UUIDs as strings.
+ *
+ * @hide
+ */
+public class UuidUtil {
+ /**
+ * Regex pattern that can be used to validate / extract the various fields of a string-formatted
+ * UUID.
+ */
+ static final Pattern PATTERN = Pattern.compile("^([a-fA-F0-9]{8})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})$");
+
+ /** Printf-style pattern for formatting the various fields of a UUID as a string. */
+ static final String FORMAT = "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x";
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
new file mode 100644
index 0000000..4898e6b
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for asserting the validity of various data types used by this module.
+ * Each of the methods below would throw an {@link IllegalArgumentException} if its input is
+ * invalid. The input's validity is determined irrespective of any context. In cases where the valid
+ * value space is further limited by state, it is the caller's responsibility to assert.
+ *
+ * @hide
+ */
+public class ValidationUtil {
+ static void validateUuid(@Nullable String uuid) {
+ Preconditions.checkNotNull(uuid);
+ Matcher matcher = UuidUtil.PATTERN.matcher(uuid);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(
+ "Illegal format for UUID: " + uuid);
+ }
+ }
+
+ static void validateGenericModel(@Nullable SoundModel model) {
+ validateModel(model, SoundModelType.GENERIC);
+ }
+
+ static void validateModel(@Nullable SoundModel model, int expectedType) {
+ Preconditions.checkNotNull(model);
+ if (model.type != expectedType) {
+ throw new IllegalArgumentException("Invalid type");
+ }
+ validateUuid(model.uuid);
+ validateUuid(model.vendorUuid);
+ Preconditions.checkNotNull(model.data);
+ }
+
+ static void validatePhraseModel(@Nullable PhraseSoundModel model) {
+ Preconditions.checkNotNull(model);
+ validateModel(model.common, SoundModelType.KEYPHRASE);
+ Preconditions.checkNotNull(model.phrases);
+ for (Phrase phrase : model.phrases) {
+ Preconditions.checkNotNull(phrase);
+ if ((phrase.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+ | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+ throw new IllegalArgumentException("Invalid recognitionModes");
+ }
+ Preconditions.checkNotNull(phrase.users);
+ Preconditions.checkNotNull(phrase.locale);
+ Preconditions.checkNotNull(phrase.text);
+ }
+ }
+
+ static void validateRecognitionConfig(@Nullable RecognitionConfig config) {
+ Preconditions.checkNotNull(config);
+ Preconditions.checkNotNull(config.phraseRecognitionExtras);
+ for (PhraseRecognitionExtra extra : config.phraseRecognitionExtras) {
+ Preconditions.checkNotNull(extra);
+ if ((extra.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+ | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+ throw new IllegalArgumentException("Invalid recognitionModes");
+ }
+ if (extra.confidenceLevel < 0 || extra.confidenceLevel > 100) {
+ throw new IllegalArgumentException("Invalid confidenceLevel");
+ }
+ Preconditions.checkNotNull(extra.levels);
+ for (ConfidenceLevel level : extra.levels) {
+ Preconditions.checkNotNull(level);
+ if (level.levelPercent < 0 || level.levelPercent > 100) {
+ throw new IllegalArgumentException("Invalid confidenceLevel");
+ }
+ }
+ }
+ Preconditions.checkNotNull(config.data);
+ }
+
+ static void validateModelParameter(int modelParam) {
+ switch (modelParam) {
+ case ModelParameter.THRESHOLD_FACTOR:
+ return;
+
+ default:
+ throw new IllegalArgumentException("Invalid model parameter");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 677d2a1..6f124ac 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -3594,7 +3594,10 @@
pw.println(prefix + "* " + task);
task.dump(pw, prefix + " ");
final ArrayList<ActivityRecord> activities = new ArrayList<>();
- forAllActivities((Consumer<ActivityRecord>) activities::add);
+ // Add activities by traversing the hierarchy from bottom to top, since activities
+ // are dumped in reverse order in {@link ActivityStackSupervisor#dumpHistoryList()}.
+ forAllActivities((Consumer<ActivityRecord>) activities::add,
+ false /* traverseTopToBottom */);
dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient,
dumpPackage, false, null, task);
});
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 23083c9..674955e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -98,6 +98,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
@@ -1309,9 +1310,11 @@
String resolvedType, int userId) {
if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) {
// request phase two resolution
- mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo(
+ PackageManagerInternal packageManager = mService.getPackageManagerInternalLocked();
+ boolean isRequesterInstantApp = packageManager.isInstantApp(callingPackage, userId);
+ packageManager.requestInstantAppResolutionPhaseTwo(
auxiliaryResponse, originalIntent, resolvedType, callingPackage,
- verificationBundle, userId);
+ isRequesterInstantApp, verificationBundle, userId);
}
return InstantAppResolver.buildEphemeralInstallerIntent(
originalIntent,
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fd8094c..a34b7fd 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -35,6 +35,7 @@
"com_android_server_power_PowerManagerService.cpp",
"com_android_server_security_VerityUtils.cpp",
"com_android_server_SerialService.cpp",
+ "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
"com_android_server_storage_AppFuseBridge.cpp",
"com_android_server_SystemServer.cpp",
"com_android_server_TestNetworkService.cpp",
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 906b568..be11b86 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -24,6 +24,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <linux/fsverity.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
@@ -33,40 +34,6 @@
#include <android-base/unique_fd.h>
-// TODO(112037636): Always include once fsverity.h is upstreamed.
-#if __has_include(<linux/fsverity.h>)
-#include <linux/fsverity.h>
-#else
-
-#include <linux/limits.h>
-#include <linux/ioctl.h>
-#include <linux/types.h>
-
-#define FS_VERITY_HASH_ALG_SHA256 1
-
-struct fsverity_enable_arg {
- __u32 version;
- __u32 hash_algorithm;
- __u32 block_size;
- __u32 salt_size;
- __u64 salt_ptr;
- __u32 sig_size;
- __u32 __reserved1;
- __u64 sig_ptr;
- __u64 __reserved2[11];
-};
-
-struct fsverity_digest {
- __u16 digest_algorithm;
- __u16 digest_size; /* input/output */
- __u8 digest[];
-};
-
-#define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg)
-#define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest)
-
-#endif
-
const int kSha256Bytes = 32;
namespace android {
diff --git a/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
new file mode 100644
index 0000000..774534f
--- /dev/null
+++ b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sstream>
+
+#include "core_jni_helpers.h"
+#include <media/AudioSystem.h>
+
+namespace android {
+
+namespace {
+
+#define PACKAGE "com/android/server/soundtrigger_middleware"
+#define CLASSNAME PACKAGE "/AudioSessionProviderImpl"
+#define SESSION_CLASSNAME PACKAGE "/SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession"
+
+jobject acquireAudioSession(
+ JNIEnv* env,
+ jobject clazz) {
+
+ audio_session_t session;
+ audio_io_handle_t ioHandle;
+ audio_devices_t device;
+
+ status_t status = AudioSystem::acquireSoundTriggerSession(&session,
+ &ioHandle,
+ &device);
+ if (status != 0) {
+ std::ostringstream message;
+ message
+ << "AudioSystem::acquireSoundTriggerSession returned an error code: "
+ << status;
+ env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+ message.str().c_str());
+ return nullptr;
+ }
+
+ jclass cls = FindClassOrDie(env, SESSION_CLASSNAME);
+ jmethodID ctor = GetMethodIDOrDie(env, cls, "<init>", "(III)V");
+ return env->NewObject(cls,
+ ctor,
+ static_cast<int>(session),
+ static_cast<int>(ioHandle),
+ static_cast<int>(device));
+}
+
+void releaseAudioSession(JNIEnv* env, jobject clazz, jint handle) {
+ status_t status =
+ AudioSystem::releaseSoundTriggerSession(static_cast<audio_session_t>(handle));
+
+ if (status != 0) {
+ std::ostringstream message;
+ message
+ << "AudioSystem::releaseAudioSystemSession returned an error code: "
+ << status;
+ env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+ message.str().c_str());
+ }
+}
+
+const JNINativeMethod g_methods[] = {
+ {"acquireSession", "()L" SESSION_CLASSNAME ";",
+ reinterpret_cast<void*>(acquireAudioSession)},
+ {"releaseSession", "(I)V",
+ reinterpret_cast<void*>(releaseAudioSession)},
+};
+
+} // namespace
+
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ JNIEnv* env) {
+ return RegisterMethodsOrDie(env,
+ CLASSNAME,
+ g_methods,
+ NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 692c9d2..165edf1 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,8 @@
int register_android_server_security_VerityUtils(JNIEnv* env);
int register_android_server_am_AppCompactor(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ JNIEnv* env);
};
using namespace android;
@@ -105,5 +107,7 @@
register_android_server_security_VerityUtils(env);
register_android_server_am_AppCompactor(env);
register_android_server_am_LowMemDetector(env);
+ register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 078bab4..d91ec42 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2447,7 +2447,7 @@
Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId) != 0) {
profileOwner.ensureUserRestrictions().putBoolean(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
- saveUserRestrictionsLocked(userId);
+ saveUserRestrictionsLocked(userId, /* parent = */ false);
mInjector.settingsSecurePutIntForUser(
Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId);
}
@@ -2478,7 +2478,7 @@
}
admin.defaultEnabledRestrictionsAlreadySet.addAll(restrictionsToSet);
Slog.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictionsToSet);
- saveUserRestrictionsLocked(userId);
+ saveUserRestrictionsLocked(userId, /* parent = */ false);
}
}
@@ -8039,7 +8039,7 @@
activeAdmin.defaultEnabledRestrictionsAlreadySet.addAll(restrictions);
Slog.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictions);
- saveUserRestrictionsLocked(userId);
+ saveUserRestrictionsLocked(userId, /* parent = */ false);
}
long ident = mInjector.binderClearCallingIdentity();
@@ -10310,24 +10310,33 @@
}
@Override
- public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
+ public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner,
+ boolean parent) {
Preconditions.checkNotNull(who, "ComponentName is null");
if (!UserRestrictionsUtils.isValidRestriction(key)) {
return;
}
- final int userHandle = mInjector.userHandleGetCallingUserId();
+ int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
final ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+
if (isDeviceOwner) {
if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
throw new SecurityException("Device owner cannot set user restriction " + key);
}
- } else { // profile owner
- if (!UserRestrictionsUtils.canProfileOwnerChange(key, userHandle)) {
+ if (parent) {
+ throw new IllegalArgumentException(
+ "Cannot use the parent instance in Device Owner mode");
+ }
+ } else {
+ if (!(UserRestrictionsUtils.canProfileOwnerChange(key, userHandle) || (
+ isProfileOwnerOfOrganizationOwnedDevice(activeAdmin) && parent
+ && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
+ key)))) {
throw new SecurityException("Profile owner cannot set user restriction " + key);
}
}
@@ -10339,7 +10348,7 @@
} else {
restrictions.remove(key);
}
- saveUserRestrictionsLocked(userHandle);
+ saveUserRestrictionsLocked(userHandle, parent);
}
final int eventId = enabledFromThisOwner
? DevicePolicyEnums.ADD_USER_RESTRICTION
@@ -10357,9 +10366,9 @@
}
}
- private void saveUserRestrictionsLocked(int userId) {
+ private void saveUserRestrictionsLocked(int userId, boolean parent) {
saveSettingsLocked(userId);
- pushUserRestrictions(userId);
+ pushUserRestrictions(parent ? getProfileParentId(userId) : userId);
sendChangedNotification(userId);
}
@@ -10368,6 +10377,7 @@
final boolean isDeviceOwner = mOwners.isDeviceOwnerUserId(userId);
Bundle userRestrictions = null;
final int restrictionOwnerType;
+ final int originatingUserId;
if (isDeviceOwner) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -10377,6 +10387,7 @@
userRestrictions = deviceOwner.userRestrictions;
addOrRemoveDisableCameraRestriction(userRestrictions, deviceOwner);
restrictionOwnerType = UserManagerInternal.OWNER_TYPE_DEVICE_OWNER;
+ originatingUserId = deviceOwner.getUserHandle().getIdentifier();
} else {
final ActiveAdmin profileOwnerOfOrganizationOwnedDevice =
getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
@@ -10391,21 +10402,25 @@
userRestrictions = parent.userRestrictions;
userRestrictions = addOrRemoveDisableCameraRestriction(userRestrictions,
parent);
+ originatingUserId =
+ profileOwnerOfOrganizationOwnedDevice.getUserHandle().getIdentifier();
} else {
final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
if (profileOwner != null) {
userRestrictions = profileOwner.userRestrictions;
restrictionOwnerType = UserManagerInternal.OWNER_TYPE_PROFILE_OWNER;
+ originatingUserId = profileOwner.getUserHandle().getIdentifier();
} else {
restrictionOwnerType = UserManagerInternal.OWNER_TYPE_NO_OWNER;
+ originatingUserId = userId;
}
userRestrictions = addOrRemoveDisableCameraRestriction(
userRestrictions, userId);
}
}
- mUserManagerInternal.setDevicePolicyUserRestrictions(userId, userRestrictions,
- restrictionOwnerType);
+ mUserManagerInternal.setDevicePolicyUserRestrictions(originatingUserId,
+ userRestrictions, restrictionOwnerType);
}
}
@@ -10435,14 +10450,18 @@
}
@Override
- public Bundle getUserRestrictions(ComponentName who) {
+ public Bundle getUserRestrictions(ComponentName who, boolean parent) {
if (!mHasFeature) {
return null;
}
Preconditions.checkNotNull(who, "ComponentName is null");
+
synchronized (getLockObject()) {
final ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+ if (parent) {
+ enforceProfileOwnerOfOrganizationOwnedDevice(activeAdmin);
+ }
return activeAdmin.userRestrictions;
}
}
@@ -11241,7 +11260,7 @@
} else {
try {
setUserRestriction(who, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- (Integer.parseInt(value) == 0) ? true : false);
+ (Integer.parseInt(value) == 0) ? true : false, /* parent */ false);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
.setAdmin(who)
@@ -11286,7 +11305,7 @@
Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (getLockObject()) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on);
+ setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on, /* parent */ false);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_MASTER_VOLUME_MUTED)
.setAdmin(who)
@@ -13773,8 +13792,9 @@
} else {
deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
}
+ final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
- intent.setPackage("com.android.systemui");
+ intent.setPackage(pm.getSystemUiServiceComponent().getPackageName());
final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
UserHandle.CURRENT);
Notification notification =
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 401a094..7d6b0c9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -30,12 +30,12 @@
import android.app.AppCompatCallbacks;
import android.app.INotificationManager;
import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
import android.database.sqlite.SQLiteCompatibilityWalFlags;
@@ -148,6 +148,7 @@
import com.android.server.security.KeyChainSystemService;
import com.android.server.signedconfig.SignedConfigService;
import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
@@ -1555,6 +1556,10 @@
}
t.traceEnd();
+ t.traceBegin("StartSoundTriggerMiddlewareService");
+ mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ t.traceEnd();
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BROADCAST_RADIO)) {
t.traceBegin("StartBroadcastRadioService");
mSystemServiceManager.startService(BroadcastRadioService.class);
@@ -2395,9 +2400,9 @@
}
private static void startSystemUi(Context context, WindowManagerService windowManager) {
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
Intent intent = new Intent();
- intent.setComponent(new ComponentName("com.android.systemui",
- "com.android.systemui.SystemUIService"));
+ intent.setComponent(pm.getSystemUiServiceComponent());
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
diff --git a/services/net/java/android/net/TcpKeepalivePacketData.java b/services/net/java/android/net/TcpKeepalivePacketData.java
index 7f2f499..aad75ae 100644
--- a/services/net/java/android/net/TcpKeepalivePacketData.java
+++ b/services/net/java/android/net/TcpKeepalivePacketData.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.net.SocketKeepalive.InvalidPacketException;
import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index cf7919b..3910993 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -40,7 +40,6 @@
"platformprotosnano",
"hamcrest-library",
"servicestests-utils",
- "xml-writer-device-lib",
"service-appsearch",
"service-jobscheduler",
],
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
similarity index 83%
rename from services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
index b707912..538e2d5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
@@ -24,22 +24,21 @@
import android.accessibilityservice.AccessibilityService;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
-import android.util.DisplayMetrics;
-import android.view.GestureDetector;
import android.view.MotionEvent;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
/**
- * Tests for AccessibilityGestureDetector
+ * Tests for GestureManifold
*/
-public class AccessibilityGestureDetectorTest {
+public class GestureManifoldTest {
// Constants for testRecognizeGesturePath()
private static final PointF PATH_START = new PointF(300f, 300f);
@@ -47,24 +46,21 @@
private static final long PATH_STEP_MILLISEC = 100;
// Data used by all tests
- private AccessibilityGestureDetector mDetector;
- private AccessibilityGestureDetector.Listener mResultListener;
+ private GestureManifold mManifold;
+ private TouchState mState;
+ private GestureManifold.Listener mResultListener;
@Before
public void setUp() {
- // Construct a mock Context.
- DisplayMetrics displayMetricsMock = mock(DisplayMetrics.class);
- displayMetricsMock.xdpi = 500;
- displayMetricsMock.ydpi = 500;
- Resources mockResources = mock(Resources.class);
- when(mockResources.getDisplayMetrics()).thenReturn(displayMetricsMock);
- Context contextMock = mock(Context.class);
- when(contextMock.getResources()).thenReturn(mockResources);
+ Context context = InstrumentationRegistry.getContext();
+ // Construct a testable GestureManifold.
+ mResultListener = mock(GestureManifold.Listener.class);
+ mState = new TouchState();
+ mManifold = new GestureManifold(context, mResultListener, mState);
+ // Play the role of touch explorer in updating the shared state.
+ when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted());
- // Construct a testable AccessibilityGestureDetector.
- mResultListener = mock(AccessibilityGestureDetector.Listener.class);
- GestureDetector doubleTapDetectorMock = mock(GestureDetector.class);
- mDetector = new AccessibilityGestureDetector(contextMock, mResultListener, doubleTapDetectorMock);
+
}
@@ -141,8 +137,8 @@
// For each path step from start (non-inclusive) to end ... add a motion point.
for (int step = 1; step < numSteps; ++step) {
path.add(new PointF(
- (start.x + (stepX * (float) step)),
- (start.y + (stepY * (float) step))));
+ (start.x + (stepX * (float) step)),
+ (start.y + (stepY * (float) step))));
}
}
@@ -170,12 +166,22 @@
point.x, point.y, 0);
// Send event.
- mDetector.onMotionEvent(event, event, policyFlags);
+ mState.onReceivedMotionEvent(event);
+ mManifold.onMotionEvent(event, event, policyFlags);
eventTimeMs += PATH_STEP_MILLISEC;
+ if (mState.isClear()) {
+ mState.startTouchInteracting();
+ }
}
+ mState.clear();
// Check that correct gesture was recognized.
verify(mResultListener).onGestureCompleted(
argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId));
}
+
+ private boolean onGestureStarted() {
+ mState.startGestureDetecting();
+ return false;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 4b1ec6f..a4ceadb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -74,7 +74,7 @@
private TouchExplorer mTouchExplorer;
private long mLastDownTime = Integer.MIN_VALUE;
- // mock package-private AccessibilityGestureDetector class
+ // mock package-private GestureManifold class
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
@@ -108,7 +108,7 @@
public void setUp() {
Context context = InstrumentationRegistry.getContext();
AccessibilityManagerService ams = new AccessibilityManagerService(context);
- AccessibilityGestureDetector detector = mock(AccessibilityGestureDetector.class);
+ GestureManifold detector = mock(GestureManifold.class);
mCaptor = new EventCaptor();
mTouchExplorer = new TouchExplorer(context, ams, detector);
mTouchExplorer.setNext(mCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index a47a567..e90cb46 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -16,7 +16,11 @@
package com.android.server.attention;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
import static com.android.server.attention.AttentionManagerService.ATTENTION_CACHE_BUFFER_SIZE;
+import static com.android.server.attention.AttentionManagerService.DEFAULT_STALE_AFTER_MILLIS;
+import static com.android.server.attention.AttentionManagerService.KEY_STALE_AFTER_MILLIS;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +39,7 @@
import android.os.IPowerManager;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.provider.DeviceConfig;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
@@ -180,6 +185,45 @@
assertThat(buffer.get(0)).isEqualTo(cache);
}
+ @Test
+ public void testGetStaleAfterMillis_handlesGoodFlagValue() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "123", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(123);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_1() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "-123", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_2() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "15000", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_3() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "abracadabra", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_4() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "15_000L", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
private class MockIAttentionService implements IAttentionService {
public void checkAttention(IAttentionCallback callback) throws RemoteException {
callback.onSuccess(0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/OWNERS b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 7267976..cb99c11 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -22,15 +22,13 @@
import androidx.test.runner.AndroidJUnit4;
-import com.android.compat.annotation.Change;
-import com.android.compat.annotation.XmlWriter;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@@ -50,18 +48,10 @@
return dir;
}
- private void writeChangesToFile(Change[] changes, File f) {
- XmlWriter writer = new XmlWriter();
- for (Change change: changes) {
- writer.addChange(change);
- }
- try {
- f.createNewFile();
- writer.write(new FileOutputStream(f));
- } catch (IOException e) {
- throw new RuntimeException(
- "Encountered an error while writing compat config file", e);
- }
+ private void writeToFile(File dir, String filename, String content) throws IOException {
+ OutputStream os = new FileOutputStream(new File(dir, filename));
+ os.write(content.getBytes());
+ os.close();
}
@Test
@@ -173,13 +163,15 @@
}
@Test
- public void testReadConfig() {
- Change[] changes = {new Change(1234L, "MY_CHANGE1", false, 2, null), new Change(1235L,
- "MY_CHANGE2", true, null, "description"), new Change(1236L, "MY_CHANGE3", false,
- null, "")};
+ public void testReadConfig() throws IOException {
+ String configXml = "<config>"
+ + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />"
+ + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />"
+ + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />"
+ + "</config>";
File dir = createTempDir();
- writeChangesToFile(changes, new File(dir.getPath() + "/platform_compat_config.xml"));
+ writeToFile(dir, "platform_compat_config.xml", configXml);
CompatConfig pc = new CompatConfig();
pc.initConfigFromLib(dir);
@@ -191,17 +183,18 @@
}
@Test
- public void testReadConfigMultipleFiles() {
- Change[] changes1 = {new Change(1234L, "MY_CHANGE1", false, 2, null)};
- Change[] changes2 = {new Change(1235L, "MY_CHANGE2", true, null, ""), new Change(1236L,
- "MY_CHANGE3", false, null, null)};
+ public void testReadConfigMultipleFiles() throws IOException {
+ String configXml1 = "<config>"
+ + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />"
+ + "</config>";
+ String configXml2 = "<config>"
+ + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />"
+ + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />"
+ + "</config>";
File dir = createTempDir();
- writeChangesToFile(changes1,
- new File(dir.getPath() + "/libcore_platform_compat_config.xml"));
- writeChangesToFile(changes2,
- new File(dir.getPath() + "/frameworks_platform_compat_config.xml"));
-
+ writeToFile(dir, "libcore_platform_compat_config.xml", configXml1);
+ writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2);
CompatConfig pc = new CompatConfig();
pc.initConfigFromLib(dir);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index f54f885..f97c887 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1162,7 +1162,8 @@
MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM), eq(null),
+ eq(UserHandle.USER_SYSTEM),
+ eq(null),
eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
@@ -1966,7 +1967,6 @@
// TODO Make sure restrictions are written to the file.
}
- // TODO: (b/138709470) test addUserRestriction as PO of an organization-owned device
public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
final int MANAGED_PROFILE_ADMIN_UID =
@@ -1979,16 +1979,26 @@
when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID))
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+ parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(MANAGED_PROFILE_USER_ID),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME),
+ eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+ reset(getServices().userManagerInternal);
+
+ parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
+ reset(getServices().userManagerInternal);
+
parentDpm.setCameraDisabled(admin1, true);
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM),
+ eq(MANAGED_PROFILE_USER_ID),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
reset(getServices().userManagerInternal);
parentDpm.setCameraDisabled(admin1, false);
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM),
+ eq(MANAGED_PROFILE_USER_ID),
MockUtils.checkUserRestrictions(),
eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
reset(getServices().userManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 7a2350e..919a3f6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -35,6 +35,7 @@
import android.app.timezonedetector.TimeZoneDetector;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -162,6 +163,8 @@
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
+ when(packageManagerInternal.getSystemUiServiceComponent()).thenReturn(
+ new ComponentName("com.android.systemui", ".Service"));
contentResolver = new MockContentResolver();
contentResolver.addProvider("telephony", new MockContentProvider(realContext) {
diff --git a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
new file mode 100644
index 0000000..0f915db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.om
+
+import org.mockito.Answers
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.stubbing.Answer
+import org.mockito.stubbing.Stubber
+
+// TODO(chiuwinson): Move this entire file to a shared utility module
+// TODO(b/135203078): De-dupe utils added for overlays vs package refactor
+object MockitoUtils {
+ val ANSWER_THROWS = Answer<Any?> {
+ when (val name = it.method.name) {
+ "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+ else -> {
+ val arguments = it.arguments
+ ?.takeUnless { it.isEmpty() }
+ ?.joinToString()
+ ?.let {
+ "with $it"
+ }
+ .orEmpty()
+
+ throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " +
+ "$arguments should not be called")
+ }
+ }
+ }
+}
+
+inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
+
+fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock)
+fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock)
+
+@Suppress("UNCHECKED_CAST")
+fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) =
+ Mockito.`when`(mock).thenAnswer { block(it) }
+
+fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { }
+
+inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T {
+ val swappingAnswer = object : Answer<Any?> {
+ var delegate: Answer<*> = Answers.RETURNS_DEFAULTS
+
+ override fun answer(invocation: InvocationOnMock?): Any? {
+ return delegate.answer(invocation)
+ }
+ }
+
+ return Mockito.mock(T::class.java, swappingAnswer).apply(block)
+ .also {
+ // To allow when() usage inside block, only swap to throwing afterwards
+ swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
new file mode 100644
index 0000000..ef12948
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.om
+
+import android.content.pm.parsing.AndroidPackage
+import android.net.Uri
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.testng.Assert.assertThrows
+
+@RunWith(Parameterized::class)
+class OverlayReferenceMapperTests {
+
+ companion object {
+ private const val TARGET_PACKAGE_NAME = "com.test.target"
+ private const val OVERLAY_PACKAGE_NAME = "com.test.overlay"
+ private const val ACTOR_PACKAGE_NAME = "com.test.actor"
+ private const val ACTOR_NAME = "overlay://test/actorName"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "deferRebuild {0}")
+ fun parameters() = arrayOf(true, false)
+ }
+
+ private lateinit var mapper: OverlayReferenceMapper
+
+ @JvmField
+ @Parameterized.Parameter(0)
+ var deferRebuild = false
+
+ @Before
+ fun initMapper() {
+ mapper = mapper()
+ }
+
+ @Test
+ fun targetWithOverlay() {
+ val target = mockTarget()
+ val overlay = mockOverlay()
+ val existing = mapper.addInOrder(overlay)
+ assertEmpty()
+ mapper.addInOrder(target, existing = existing)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
+ mapper.remove(target)
+ assertEmpty()
+ }
+
+ @Test
+ fun targetWithMultipleOverlays() {
+ val target = mockTarget()
+ val overlay0 = mockOverlay(0)
+ val overlay1 = mockOverlay(1)
+ mapper = mapper(
+ overlayToTargetToOverlayables = mapOf(
+ overlay0.packageName to mapOf(
+ target.packageName to target.overlayables.keys
+ ),
+ overlay1.packageName to mapOf(
+ target.packageName to target.overlayables.keys
+ )
+ )
+ )
+ val existing = mapper.addInOrder(overlay0, overlay1)
+ assertEmpty()
+ mapper.addInOrder(target, existing = existing)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1))
+ mapper.remove(overlay0)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1))
+ mapper.remove(target)
+ assertEmpty()
+ }
+
+ @Test
+ fun targetWithoutOverlay() {
+ val target = mockTarget()
+ mapper.addInOrder(target)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+ mapper.remove(target)
+ assertEmpty()
+ }
+
+ @Test
+ fun overlayWithTarget() {
+ val target = mockTarget()
+ val overlay = mockOverlay()
+ val existing = mapper.addInOrder(target)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+ mapper.addInOrder(overlay, existing = existing)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
+ mapper.remove(overlay)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+ }
+
+ @Test
+ fun overlayWithMultipleTargets() {
+ val target0 = mockTarget(0)
+ val target1 = mockTarget(1)
+ val overlay = mockOverlay()
+ mapper = mapper(
+ overlayToTargetToOverlayables = mapOf(
+ overlay.packageName to mapOf(
+ target0.packageName to target0.overlayables.keys,
+ target1.packageName to target1.overlayables.keys
+ )
+ )
+ )
+ mapper.addInOrder(target0, target1, overlay)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
+ mapper.remove(target0)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
+ mapper.remove(target1)
+ assertEmpty()
+ }
+
+ @Test
+ fun overlayWithoutTarget() {
+ val overlay = mockOverlay()
+ mapper.addInOrder(overlay)
+ // An overlay can only have visibility exposed through its target
+ assertEmpty()
+ mapper.remove(overlay)
+ assertEmpty()
+ }
+
+ private fun OverlayReferenceMapper.addInOrder(
+ vararg pkgs: AndroidPackage,
+ existing: MutableMap<String, AndroidPackage> = mutableMapOf()
+ ) = pkgs.fold(existing) { map, pkg ->
+ addPkg(pkg, map)
+ map[pkg.packageName] = pkg
+ return@fold map
+ }
+
+ private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName)
+
+ private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) {
+ val expected = pairs.associate { it }
+ .mapValues { pair -> pair.value.map { it.packageName }.toSet() }
+
+ // This validates the API exposed for querying the relationships
+ expected.forEach { (actorPkgName, expectedPkgNames) ->
+ expectedPkgNames.forEach { expectedPkgName ->
+ if (deferRebuild) {
+ assertThrows(IllegalStateException::class.java) {
+ mapper.isValidActor(expectedPkgName, actorPkgName)
+ }
+ mapper.rebuildIfDeferred()
+ deferRebuild = false
+ }
+
+ assertThat(mapper.isValidActor(expectedPkgName, actorPkgName)).isTrue()
+ }
+ }
+
+ // This asserts no other relationships are defined besides those tested above
+ assertThat(mapper.actorPkgToPkgs).containsExactlyEntriesIn(expected)
+ }
+
+ private fun assertEmpty() = assertMapping()
+
+ private fun mapper(
+ namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run {
+ mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME))
+ },
+ overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf(
+ mockOverlay().packageName to mapOf(
+ mockTarget().run { packageName to overlayables.keys }
+ )
+ )
+ ) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider {
+ override fun getActorPkg(actor: String?) =
+ OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first
+
+ override fun getTargetToOverlayables(pkg: AndroidPackage) =
+ overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap()
+ })
+
+ private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { "$TARGET_PACKAGE_NAME$increment" }
+ whenever(overlayables) { mapOf("overlayableName$increment" to ACTOR_NAME) }
+ whenever(toString()) { "Package{$packageName}" }
+ }
+
+ private fun mockOverlay(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { "$OVERLAY_PACKAGE_NAME$increment" }
+ whenever(overlayables) { emptyMap<String, String>() }
+ whenever(toString()) { "Package{$packageName}" }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 4fc625a..82bbdcb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -35,6 +35,11 @@
import android.os.Build;
import android.os.Process;
import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.om.OverlayReferenceMapper;
import org.junit.Before;
import org.junit.Test;
@@ -43,11 +48,18 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
@RunWith(JUnit4.class)
public class AppsFilterTest {
private static final int DUMMY_CALLING_UID = 10345;
private static final int DUMMY_TARGET_UID = 10556;
+ private static final int DUMMY_ACTOR_UID = 10656;
+ private static final int DUMMY_OVERLAY_UID = 10756;
+ private static final int DUMMY_ACTOR_TWO_UID = 10856;
@Mock
AppsFilter.FeatureConfig mFeatureConfigMock;
@@ -117,7 +129,7 @@
@Test
public void testSystemReadyPropogates() throws Exception {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
appsFilter.onSystemReady();
verify(mFeatureConfigMock).onSystemReady();
}
@@ -125,7 +137,8 @@
@Test
public void testQueriesAction_FilterMatches() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_UID);
@@ -138,7 +151,8 @@
@Test
public void testQueriesAction_NoMatchingAction_Filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -151,7 +165,8 @@
@Test
public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -169,7 +184,8 @@
@Test
public void testNoQueries_Filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -182,7 +198,8 @@
@Test
public void testForceQueryable_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID);
@@ -195,7 +212,8 @@
@Test
public void testForceQueryableByDevice_SystemCaller_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID,
@@ -209,7 +227,8 @@
@Test
public void testForceQueryableByDevice_NonSystemCaller_Filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -224,7 +243,8 @@
public void testSystemQueryable_DoesntFilter() {
final AppsFilter appsFilter =
new AppsFilter(mFeatureConfigMock, new String[]{},
- true /* system force queryable */);
+ true /* system force queryable */, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID,
@@ -238,7 +258,8 @@
@Test
public void testQueriesPackage_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -253,7 +274,8 @@
when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
.thenReturn(false);
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(
appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -266,20 +288,22 @@
@Test
public void testSystemUid_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
assertFalse(appsFilter.shouldFilterApplication(0, null, target, 0));
- assertFalse(appsFilter.shouldFilterApplication(
- Process.FIRST_APPLICATION_UID - 1, null, target, 0));
+ assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1,
+ null, target, 0));
}
@Test
public void testNonSystemUid_NoCallingSetting_Filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -290,7 +314,8 @@
@Test
public void testNoTargetPackage_filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = new PackageSettingBuilder()
.setName("com.some.package")
@@ -304,6 +329,127 @@
assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
}
+ @Test
+ public void testActsOnTargetOfOverlay() {
+ final String actorName = "overlay://test/actorName";
+
+ ParsingPackage target = pkg("com.some.package.target")
+ .addOverlayable("overlayableName", actorName);
+ ParsingPackage overlay = pkg("com.some.package.overlay")
+ .setIsOverlay(true)
+ .setOverlayTarget(target.getPackageName())
+ .setOverlayTargetName("overlayableName");
+ ParsingPackage actor = pkg("com.some.package.actor");
+
+ final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false,
+ new OverlayReferenceMapper.Provider() {
+ @Nullable
+ @Override
+ public String getActorPkg(String actorString) {
+ if (actorName.equals(actorString)) {
+ return actor.getPackageName();
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Set<String>> getTargetToOverlayables(
+ @NonNull AndroidPackage pkg) {
+ if (overlay.getPackageName().equals(pkg.getPackageName())) {
+ Map<String, Set<String>> map = new ArrayMap<>();
+ Set<String> set = new ArraySet<>();
+ set.add(overlay.getOverlayTargetName());
+ map.put(overlay.getOverlayTarget(), set);
+ return map;
+ }
+ return Collections.emptyMap();
+ }
+ });
+ appsFilter.onSystemReady();
+
+ PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID);
+ PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID);
+ PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_UID);
+
+ // Actor can see both target and overlay
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting,
+ targetSetting, 0));
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting,
+ overlaySetting, 0));
+
+ // But target/overlay can't see each other
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting,
+ overlaySetting, 0));
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting,
+ targetSetting, 0));
+
+ // And can't see the actor
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting,
+ actorSetting, 0));
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting,
+ actorSetting, 0));
+ }
+
+ @Test
+ public void testActsOnTargetOfOverlayThroughSharedUser() {
+ final String actorName = "overlay://test/actorName";
+
+ ParsingPackage target = pkg("com.some.package.target")
+ .addOverlayable("overlayableName", actorName);
+ ParsingPackage overlay = pkg("com.some.package.overlay")
+ .setIsOverlay(true)
+ .setOverlayTarget(target.getPackageName())
+ .setOverlayTargetName("overlayableName");
+ ParsingPackage actorOne = pkg("com.some.package.actor.one");
+ ParsingPackage actorTwo = pkg("com.some.package.actor.two");
+
+ final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false,
+ new OverlayReferenceMapper.Provider() {
+ @Nullable
+ @Override
+ public String getActorPkg(String actorString) {
+ // Only actorOne is mapped as a valid actor
+ if (actorName.equals(actorString)) {
+ return actorOne.getPackageName();
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Set<String>> getTargetToOverlayables(
+ @NonNull AndroidPackage pkg) {
+ if (overlay.getPackageName().equals(pkg.getPackageName())) {
+ Map<String, Set<String>> map = new ArrayMap<>();
+ Set<String> set = new ArraySet<>();
+ set.add(overlay.getOverlayTargetName());
+ map.put(overlay.getOverlayTarget(), set);
+ return map;
+ }
+ return Collections.emptyMap();
+ }
+ });
+ appsFilter.onSystemReady();
+
+ PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID);
+ PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID);
+ PackageSetting actorOneSetting = simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_UID);
+ PackageSetting actorTwoSetting = simulateAddPackage(appsFilter, actorTwo,
+ DUMMY_ACTOR_TWO_UID);
+
+ SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser",
+ actorOneSetting.pkgFlags, actorOneSetting.pkgPrivateFlags);
+ actorSharedSetting.addPackage(actorOneSetting);
+ actorSharedSetting.addPackage(actorTwoSetting);
+
+ // actorTwo can see both target and overlay
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting,
+ targetSetting, 0));
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting,
+ overlaySetting, 0));
+ }
+
private interface WithSettingBuilder {
PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index b751308..c478ec4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -173,6 +173,7 @@
/* createdMillis */ 0L,
/* stageDir */ mTmpDir,
/* stageCid */ null,
+ /* files */ null,
/* prepared */ true,
/* committed */ true,
/* sealed */ false, // Setting to true would trigger some PM logic.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 5baeede..2473997 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -22,7 +22,7 @@
import java.io.File;
-class PackageSettingBuilder {
+public class PackageSettingBuilder {
private String mName;
private String mRealName;
private String mCodePath;
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
new file mode 100644
index 0000000..5a2ce45
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.audio.common.V2_0.Uuid;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversionUtilTest {
+ private static final String TAG = "ConversionUtilTest";
+
+ @Test
+ public void testUuidRoundTrip() {
+ Uuid hidl = new Uuid();
+ hidl.timeLow = 0xFEDCBA98;
+ hidl.timeMid = (short) 0xEDCB;
+ hidl.versionAndTimeHigh = (short) 0xDCBA;
+ hidl.variantAndClockSeqHigh = (short) 0xCBA9;
+ hidl.node = new byte[] { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
+
+ String aidl = ConversionUtil.hidl2aidlUuid(hidl);
+ assertEquals("fedcba98-edcb-dcba-cba9-111213141516", aidl);
+
+ Uuid reconstructed = ConversionUtil.aidl2hidlUuid(aidl);
+ assertEquals(hidl, reconstructed);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
new file mode 100644
index 0000000..82f32f8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -0,0 +1,1306 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.audio.common.V2_0.AudioConfig;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
+import android.media.audio.common.AudioChannelMask;
+import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+import android.os.HwParcel;
+import android.os.IHwBinder;
+import android.os.IHwInterface;
+import android.os.RemoteException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+@RunWith(Parameterized.class)
+public class SoundTriggerMiddlewareImplTest {
+ private static final String TAG = "SoundTriggerMiddlewareImplTest";
+
+ // We run the test once for every version of the underlying driver.
+ @Parameterized.Parameters
+ public static Object[] data() {
+ return new Object[]{
+ mock(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_2.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_3.ISoundTriggerHw.class),
+ };
+ }
+
+ @Mock
+ @Parameterized.Parameter
+ public android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
+
+ @Mock
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider = mock(
+ SoundTriggerMiddlewareImpl.AudioSessionProvider.class);
+
+ private SoundTriggerMiddlewareImpl mService;
+
+ private static ISoundTriggerCallback createCallbackMock() {
+ return mock(ISoundTriggerCallback.Stub.class, Mockito.CALLS_REAL_METHODS);
+ }
+
+ private static SoundModel createGenericSoundModel() {
+ return createSoundModel(SoundModelType.GENERIC);
+ }
+
+ private static SoundModel createSoundModel(int type) {
+ SoundModel model = new SoundModel();
+ model.type = type;
+ model.uuid = "12345678-2345-3456-4567-abcdef987654";
+ model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
+ model.data = new byte[]{91, 92, 93, 94, 95};
+ return model;
+ }
+
+ private static PhraseSoundModel createPhraseSoundModel() {
+ PhraseSoundModel model = new PhraseSoundModel();
+ model.common = createSoundModel(SoundModelType.KEYPHRASE);
+ model.phrases = new Phrase[1];
+ model.phrases[0] = new Phrase();
+ model.phrases[0].id = 123;
+ model.phrases[0].users = new int[]{5, 6, 7};
+ model.phrases[0].locale = "locale";
+ model.phrases[0].text = "text";
+ model.phrases[0].recognitionModes =
+ RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
+ return model;
+ }
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties(
+ boolean supportConcurrentCapture) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
+ properties.implementor = "implementor";
+ properties.description = "description";
+ properties.version = 123;
+ properties.uuid = new Uuid();
+ properties.uuid.timeLow = 1;
+ properties.uuid.timeMid = 2;
+ properties.uuid.versionAndTimeHigh = 3;
+ properties.uuid.variantAndClockSeqHigh = 4;
+ properties.uuid.node = new byte[]{5, 6, 7, 8, 9, 10};
+
+ properties.maxSoundModels = 456;
+ properties.maxKeyPhrases = 567;
+ properties.maxUsers = 678;
+ properties.recognitionModes = 789;
+ properties.captureTransition = true;
+ properties.maxBufferMs = 321;
+ properties.concurrentCapture = supportConcurrentCapture;
+ properties.triggerInEvent = true;
+ properties.powerConsumptionMw = 432;
+ return properties;
+ }
+
+ private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+ boolean supportConcurrentCapture) {
+ assertEquals("implementor", properties.implementor);
+ assertEquals("description", properties.description);
+ assertEquals(123, properties.version);
+ assertEquals("00000001-0002-0003-0004-05060708090a", properties.uuid);
+ assertEquals(456, properties.maxSoundModels);
+ assertEquals(567, properties.maxKeyPhrases);
+ assertEquals(678, properties.maxUsers);
+ assertEquals(789, properties.recognitionModes);
+ assertTrue(properties.captureTransition);
+ assertEquals(321, properties.maxBufferMs);
+ assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+ assertTrue(properties.triggerInEvent);
+ assertEquals(432, properties.powerConsumptionMw);
+ }
+
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
+ int hwHandle,
+ int status) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent();
+ halEvent.status = status;
+ halEvent.type = SoundModelType.GENERIC;
+ halEvent.model = hwHandle;
+ halEvent.captureAvailable = true;
+ // This field is ignored.
+ halEvent.captureSession = 123;
+ halEvent.captureDelayMs = 234;
+ halEvent.capturePreambleMs = 345;
+ halEvent.triggerInData = true;
+ halEvent.audioConfig = new AudioConfig();
+ halEvent.audioConfig.sampleRateHz = 456;
+ halEvent.audioConfig.channelMask = AudioChannelMask.IN_LEFT;
+ halEvent.audioConfig.format = AudioFormat.MP3;
+ // hwEvent.audioConfig.offloadInfo is irrelevant.
+ halEvent.data.add((byte) 31);
+ halEvent.data.add((byte) 32);
+ halEvent.data.add((byte) 33);
+ return halEvent;
+ }
+
+ private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_1(
+ int hwHandle,
+ int status) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+ halEvent.header = createRecognitionEvent_2_0(hwHandle, status);
+ halEvent.header.data.clear();
+ halEvent.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[]{31, 32, 33});
+ return halEvent;
+ }
+
+ private static void validateRecognitionEvent(RecognitionEvent event, int status) {
+ assertEquals(status, event.status);
+ assertEquals(SoundModelType.GENERIC, event.type);
+ assertTrue(event.captureAvailable);
+ assertEquals(101, event.captureSession);
+ assertEquals(234, event.captureDelayMs);
+ assertEquals(345, event.capturePreambleMs);
+ assertTrue(event.triggerInData);
+ assertEquals(456, event.audioConfig.sampleRateHz);
+ assertEquals(AudioChannelMask.IN_LEFT, event.audioConfig.channelMask);
+ assertEquals(AudioFormat.MP3, event.audioConfig.format);
+ }
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_0(
+ int hwHandle, int status) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ halEvent.common = createRecognitionEvent_2_0(hwHandle, status);
+
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ halExtra.id = 123;
+ halExtra.confidenceLevel = 52;
+ halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ halLevel.userId = 31;
+ halLevel.levelPercent = 43;
+ halExtra.levels.add(halLevel);
+ halEvent.phraseExtras.add(halExtra);
+ return halEvent;
+ }
+
+ private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_1(
+ int hwHandle, int status) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ halEvent.common = createRecognitionEvent_2_1(hwHandle, status);
+
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ halExtra.id = 123;
+ halExtra.confidenceLevel = 52;
+ halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ halLevel.userId = 31;
+ halLevel.levelPercent = 43;
+ halExtra.levels.add(halLevel);
+ halEvent.phraseExtras.add(halExtra);
+ return halEvent;
+ }
+
+ private static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event, int status) {
+ validateRecognitionEvent(event.common, status);
+
+ assertEquals(1, event.phraseExtras.length);
+ assertEquals(123, event.phraseExtras[0].id);
+ assertEquals(52, event.phraseExtras[0].confidenceLevel);
+ assertEquals(RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER,
+ event.phraseExtras[0].recognitionModes);
+ assertEquals(1, event.phraseExtras[0].levels.length);
+ assertEquals(31, event.phraseExtras[0].levels[0].userId);
+ assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
+ }
+
+ private void initService(boolean supportConcurrentCapture) throws RemoteException {
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+ createDefaultProperties(
+ supportConcurrentCapture);
+ ((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
+ 0)).onValues(0,
+ properties);
+ return null;
+ }).when(mHalDriver).getProperties(any());
+ mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+ }
+
+ private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ SoundModel model = createGenericSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+ invocation.getArgument(1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadSoundModelCallback
+ resultCallback = invocation.getArgument(3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.status =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.model = hwHandle;
+ callback.soundModelCallback(modelEvent, callbackCookie);
+ return null;
+ }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadModel(model);
+ verify(mHalDriver).loadSoundModel(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel hidlModel =
+ modelCaptor.getValue();
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+ hidlModel.type);
+ assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.uuid));
+ assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
+ assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
+
+ return handle;
+ }
+
+ private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+ SoundModel model = createGenericSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+ invocation.getArgument(1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback
+ resultCallback = invocation.getArgument(3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.header.status =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.header.model = hwHandle;
+ callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+ return null;
+ }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadModel(model);
+ verify(driver).loadSoundModel_2_1(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel hidlModel =
+ modelCaptor.getValue();
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+ hidlModel.header.type);
+ assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.uuid));
+ assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.vendorUuid));
+ assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+ HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
+
+ return handle;
+ }
+
+ private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return loadGenericModel_2_1(module, hwHandle);
+ } else {
+ return loadGenericModel_2_0(module, hwHandle);
+ }
+ }
+
+ private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ PhraseSoundModel model = createPhraseSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
+ modelCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+ invocation.getArgument(
+ 1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadPhraseSoundModelCallback
+ resultCallback =
+ invocation.getArgument(
+ 3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.status =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.model = hwHandle;
+ callback.soundModelCallback(modelEvent, callbackCookie);
+ return null;
+ }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadPhraseModel(model);
+ verify(mHalDriver).loadPhraseSoundModel(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel hidlModel =
+ modelCaptor.getValue();
+
+ // Validate common part.
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+ hidlModel.common.type);
+ assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.uuid));
+ assertEquals(model.common.vendorUuid,
+ ConversionUtil.hidl2aidlUuid(hidlModel.common.vendorUuid));
+ assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.common.data.toArray());
+
+ // Validate phrase part.
+ assertEquals(1, hidlModel.phrases.size());
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Phrase hidlPhrase =
+ hidlModel.phrases.get(0);
+ assertEquals(123, hidlPhrase.id);
+ assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+ assertEquals("locale", hidlPhrase.locale);
+ assertEquals("text", hidlPhrase.text);
+ assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+ hidlPhrase.recognitionModes);
+
+ return handle;
+ }
+
+ private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+ PhraseSoundModel model = createPhraseSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
+ modelCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+ invocation.getArgument(
+ 1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback
+ resultCallback =
+ invocation.getArgument(
+ 3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.header.status =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.header.model = hwHandle;
+ callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+ return null;
+ }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadPhraseModel(model);
+ verify(driver).loadPhraseSoundModel_2_1(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel hidlModel =
+ modelCaptor.getValue();
+
+ // Validate common part.
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+ hidlModel.common.header.type);
+ assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.header.uuid));
+ assertEquals(model.common.vendorUuid,
+ ConversionUtil.hidl2aidlUuid(hidlModel.common.header.vendorUuid));
+ assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+ HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.common.data));
+
+ // Validate phrase part.
+ assertEquals(1, hidlModel.phrases.size());
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Phrase hidlPhrase =
+ hidlModel.phrases.get(0);
+ assertEquals(123, hidlPhrase.id);
+ assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+ assertEquals("locale", hidlPhrase.locale);
+ assertEquals("text", hidlPhrase.text);
+ assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+ hidlPhrase.recognitionModes);
+
+ return handle;
+ }
+
+ private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return loadPhraseModel_2_1(module, hwHandle);
+ } else {
+ return loadPhraseModel_2_0(module, hwHandle);
+ }
+ }
+
+ private void unloadModel(ISoundTriggerModule module, int handle, int hwHandle)
+ throws RemoteException {
+ module.unloadModel(handle);
+ verify(mHalDriver).unloadSoundModel(hwHandle);
+ verify(mAudioSessionProvider).releaseSession(101);
+ }
+
+ private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
+ callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(mHalDriver).startRecognition(eq(hwHandle), any(), any(), anyInt());
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig halConfig =
+ configCaptor.getValue();
+ assertTrue(halConfig.captureRequested);
+ assertEquals(102, halConfig.captureHandle);
+ assertEquals(103, halConfig.captureDevice);
+ assertEquals(1, halConfig.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
+
+ return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+ }
+
+ private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
+ callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(driver).startRecognition_2_1(eq(hwHandle), any(), any(), anyInt());
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig =
+ configCaptor.getValue();
+ assertTrue(halConfig.header.captureRequested);
+ assertEquals(102, halConfig.header.captureHandle);
+ assertEquals(103, halConfig.header.captureDevice);
+ assertEquals(1, halConfig.header.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig.header.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+ HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
+
+ return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+ }
+
+ private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return startRecognition_2_1(module, handle, hwHandle);
+ } else {
+ return startRecognition_2_0(module, handle, hwHandle);
+ }
+ }
+
+ private RecognitionConfig createRecognitionConfig() {
+ RecognitionConfig config = new RecognitionConfig();
+ config.captureRequested = true;
+ config.phraseRecognitionExtras = new PhraseRecognitionExtra[]{new PhraseRecognitionExtra()};
+ config.phraseRecognitionExtras[0].id = 123;
+ config.phraseRecognitionExtras[0].confidenceLevel = 4;
+ config.phraseRecognitionExtras[0].recognitionModes = 5;
+ config.phraseRecognitionExtras[0].levels = new ConfidenceLevel[]{new ConfidenceLevel()};
+ config.phraseRecognitionExtras[0].levels[0].userId = 234;
+ config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
+ config.data = new byte[]{5, 4, 3, 2, 1};
+ return config;
+ }
+
+ private void stopRecognition(ISoundTriggerModule module, int handle, int hwHandle)
+ throws RemoteException {
+ when(mHalDriver.stopRecognition(hwHandle)).thenReturn(0);
+ module.stopRecognition(handle);
+ verify(mHalDriver).stopRecognition(hwHandle);
+ }
+
+ private void verifyNotStartRecognition() throws RemoteException {
+ verify(mHalDriver, never()).startRecognition(anyInt(), any(), any(), anyInt());
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
+ never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+ }
+ }
+
+
+ @Before
+ public void setUp() throws Exception {
+ clearInvocations(mHalDriver);
+ clearInvocations(mAudioSessionProvider);
+
+ // This binder is associated with the mock, so it can be cast to either version of the
+ // HAL interface.
+ final IHwBinder binder = new IHwBinder() {
+ @Override
+ public void transact(int code, HwParcel request, HwParcel reply, int flags)
+ throws RemoteException {
+ // This is a little hacky, but a very easy way to gracefully reject a request for
+ // an unsupported interface (after queryLocalInterface() returns null, the client
+ // will attempt a remote transaction to obtain the interface. RemoteException will
+ // cause it to give up).
+ throw new RemoteException();
+ }
+
+ @Override
+ public IHwInterface queryLocalInterface(String descriptor) {
+ if (descriptor.equals("android.hardware.soundtrigger@2.0::ISoundTriggerHw")
+ || descriptor.equals("android.hardware.soundtrigger@2.1::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw
+ || descriptor.equals("android.hardware.soundtrigger@2.2::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw
+ || descriptor.equals("android.hardware.soundtrigger@2.3::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ return mHalDriver;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean unlinkToDeath(DeathRecipient recipient) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ when(mHalDriver.asBinder()).thenReturn(binder);
+ }
+
+ @Test
+ public void testSetUpAndTearDown() {
+ }
+
+ @Test
+ public void testListModules() throws Exception {
+ initService(true);
+ // Note: input and output properties are NOT the same type, even though they are in any way
+ // equivalent. One is a type that's exposed by the HAL and one is a type that's exposed by
+ // the service. The service actually performs a (trivial) conversion between the two.
+ SoundTriggerModuleDescriptor[] allDescriptors = mService.listModules();
+ assertEquals(1, allDescriptors.length);
+
+ SoundTriggerModuleProperties properties = allDescriptors[0].properties;
+
+ validateDefaultProperties(properties, true);
+ }
+
+ @Test
+ public void testAttachDetach() throws Exception {
+ // Normal attachment / detachment.
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testAttachDetachNotAvailable() throws Exception {
+ // Attachment / detachment during external capture, with a module not supporting concurrent
+ // capture.
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(false);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testAttachDetachAvailable() throws Exception {
+ // Attachment / detachment during external capture, with a module supporting concurrent
+ // capture.
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testLoadUnloadModel() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testLoadUnloadPhraseModel() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ final int hwHandle = 73;
+ int handle = loadPhraseModel(module, hwHandle);
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testStartStopRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testStartStopPhraseRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 67;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Signal a capture from the driver.
+ hwCallback.sendRecognitionEvent(hwHandle,
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testPhraseRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Signal a capture from the driver.
+ hwCallback.sendPhraseRecognitionEvent(hwHandle,
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testForceRecognition() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 17;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Force a trigger.
+ module.forceRecognitionEvent(handle);
+ verify(driver).getModelState(hwHandle);
+
+ // Signal a capture from the driver.
+ // '3' means 'forced', there's no constant for that in the HAL.
+ hwCallback.sendRecognitionEvent(hwHandle, 3);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testForcePhraseRecognition() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 17;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Force a trigger.
+ module.forceRecognitionEvent(handle);
+ verify(driver).getModelState(hwHandle);
+
+ // Signal a capture from the driver.
+ // '3' means 'forced', there's no constant for that in the HAL.
+ hwCallback.sendPhraseRecognitionEvent(hwHandle, 3);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testAbortRecognition() throws Exception {
+ // Make sure the HAL doesn't support concurrent capture.
+ initService(false);
+ mService.setExternalCaptureState(false);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Load the model.
+ final int hwHandle = 11;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Abort.
+ mService.setExternalCaptureState(true);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+
+ // Make sure we are notified of the lost availability.
+ verify(callback).onRecognitionAvailabilityChange(false);
+
+ // Attempt to start a new recognition - should get an abort event immediately, without
+ // involving the HAL.
+ clearInvocations(callback);
+ clearInvocations(mHalDriver);
+ module.startRecognition(handle, createRecognitionConfig());
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+ verifyNotStartRecognition();
+
+ // Now enable it and make sure we are notified.
+ mService.setExternalCaptureState(false);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testAbortPhraseRecognition() throws Exception {
+ // Make sure the HAL doesn't support concurrent capture.
+ initService(false);
+ mService.setExternalCaptureState(false);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Load the model.
+ final int hwHandle = 11;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Abort.
+ mService.setExternalCaptureState(true);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+
+ // Make sure we are notified of the lost availability.
+ verify(callback).onRecognitionAvailabilityChange(false);
+
+ // Attempt to start a new recognition - should get an abort event immediately, without
+ // involving the HAL.
+ clearInvocations(callback);
+ clearInvocations(mHalDriver);
+ module.startRecognition(handle, createRecognitionConfig());
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+ verifyNotStartRecognition();
+
+ // Now enable it and make sure we are notified.
+ mService.setExternalCaptureState(false);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testNotAbortRecognitionConcurrent() throws Exception {
+ // Make sure the HAL supports concurrent capture.
+ initService(true);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ clearInvocations(callback);
+
+ // Load the model.
+ final int hwHandle = 13;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Signal concurrent capture. Shouldn't abort.
+ mService.setExternalCaptureState(true);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Initiating a new one should work fine.
+ clearInvocations(mHalDriver);
+ startRecognition(module, handle, hwHandle);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ module.unloadModel(handle);
+ module.detach();
+ }
+
+ @Test
+ public void testNotAbortPhraseRecognitionConcurrent() throws Exception {
+ // Make sure the HAL supports concurrent capture.
+ initService(true);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ clearInvocations(callback);
+
+ // Load the model.
+ final int hwHandle = 13;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Signal concurrent capture. Shouldn't abort.
+ mService.setExternalCaptureState(true);
+ verify(callback, never()).onPhraseRecognition(anyInt(), any());
+ verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Initiating a new one should work fine.
+ clearInvocations(mHalDriver);
+ startRecognition(module, handle, hwHandle);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ module.unloadModel(handle);
+ module.detach();
+ }
+
+ @Test
+ public void testParameterSupported() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 12;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer((Answer<Void>) invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+ resultCallback = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_3.ModelParameterRange range =
+ new android.hardware.soundtrigger.V2_3.ModelParameterRange();
+ range.start = 23;
+ range.end = 45;
+ OptionalModelParameterRange optionalRange = new OptionalModelParameterRange();
+ optionalRange.range(range);
+ resultCallback.onValues(0, optionalRange);
+ return null;
+ }).when(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertEquals(23, range.minInclusive);
+ assertEquals(45, range.maxInclusive);
+ }
+
+ @Test
+ public void testParameterNotSupportedOld() throws Exception {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ return;
+ }
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 13;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ assertNull(range);
+ }
+
+ @Test
+ public void testParameterNotSupported() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 13;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+ resultCallback = invocation.getArgument(2);
+ // This is the return of this method.
+ resultCallback.onValues(0, new OptionalModelParameterRange());
+ return null;
+ }).when(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertNull(range);
+ }
+
+ @Test
+ public void testGetParameter() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 14;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
+ resultCallback = invocation.getArgument(2);
+ // This is the return of this method.
+ resultCallback.onValues(0, 234);
+ return null;
+ }).when(driver).getParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ int value = module.getModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).getParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertEquals(234, value);
+ }
+
+ @Test
+ public void testSetParameter() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 17;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ when(driver.setParameter(hwHandle,
+ android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
+ 456)).thenReturn(0);
+
+ module.setModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR, 456);
+
+ verify(driver).setParameter(hwHandle,
+ android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR, 456);
+ }
+
+ private static class SoundTriggerHwCallback {
+ private final android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback mCallback;
+ private final int mCookie;
+
+ SoundTriggerHwCallback(android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback,
+ int cookie) {
+ mCallback = callback;
+ mCookie = cookie;
+ }
+
+ private void sendRecognitionEvent(int hwHandle, int status) throws RemoteException {
+ if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+ ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).recognitionCallback_2_1(
+ createRecognitionEvent_2_1(hwHandle, status), mCookie);
+ } else {
+ mCallback.recognitionCallback(createRecognitionEvent_2_0(hwHandle, status),
+ mCookie);
+ }
+ }
+
+ private void sendPhraseRecognitionEvent(int hwHandle, int status) throws RemoteException {
+ if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+ ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).phraseRecognitionCallback_2_1(
+ createPhraseRecognitionEvent_2_1(hwHandle, status), mCookie);
+ } else {
+ mCallback.phraseRecognitionCallback(
+ createPhraseRecognitionEvent_2_0(hwHandle, status), mCookie);
+ }
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 0b4760d..a328568 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.spy;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -271,6 +272,18 @@
}
@Test
+ public void testRankingScoreOverrides() {
+ NotificationComparator comp = new NotificationComparator(mContext);
+ NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
+
+ when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
+ assertTrue(comp.compare(mRecordCheater, recordMinCallNonInterruptive) > 0);
+ assertTrue(comp.compare(mRecordColorizedCall, recordMinCallNonInterruptive) < 0);
+ }
+
+ @Test
public void testMessaging() {
NotificationComparator comp = new NotificationComparator(mContext);
assertTrue(comp.isImportantMessaging(mRecordInlineReply));
diff --git a/services/usb/java/com/android/server/usb/UsbHandlerManager.java b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
index 1730d8f..f311274 100644
--- a/services/usb/java/com/android/server/usb/UsbHandlerManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
@@ -59,8 +60,9 @@
if (uri != null && uri.length() > 0) {
// display URI to user
Intent dialogIntent = createDialogIntent();
- dialogIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbAccessoryUriActivity");
+ dialogIntent.setComponent(ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_usbAccessoryUriActivity)));
dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
dialogIntent.putExtra("uri", uri);
try {
@@ -84,8 +86,9 @@
@Nullable UsbAccessory accessory) {
Intent resolverIntent = createDialogIntent();
// start UsbConfirmActivity if there is only one choice
- resolverIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbConfirmActivity");
+ resolverIntent.setComponent(ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_usbConfirmActivity)));
resolverIntent.putExtra("rinfo", rInfo);
UserHandle user =
UserHandle.getUserHandleForUid(rInfo.activityInfo.applicationInfo.uid);
@@ -115,8 +118,9 @@
void selectUsbHandler(@NonNull ArrayList<ResolveInfo> matches,
@NonNull UserHandle user, @NonNull Intent intent) {
Intent resolverIntent = createDialogIntent();
- resolverIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbResolverActivity");
+ resolverIntent.setComponent(ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_usbResolverActivity)));
resolverIntent.putParcelableArrayListExtra("rlist", matches);
resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 749258e..c3e2013 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -34,6 +34,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -225,8 +226,8 @@
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbContaminantActivity");
+ intent.setComponent(ComponentName.unflattenFromString(r.getString(
+ com.android.internal.R.string.config_usbContaminantActivity)));
intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort));
PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 86016bb..095e8e9 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -75,12 +75,14 @@
if (uid != Process.SYSTEM_UID) {
enforcePackageBelongsToUid(uid, packageName);
+ UserHandle user = Binder.getCallingUserHandle();
int packageTargetSdkVersion;
long token = Binder.clearCallingIdentity();
try {
PackageInfo pkg;
try {
- pkg = mContext.getPackageManager().getPackageInfo(packageName, 0);
+ pkg = mContext.getPackageManager()
+ .getPackageInfoAsUser(packageName, 0, user.getIdentifier());
} catch (PackageManager.NameNotFoundException e) {
throw new RemoteException("package " + packageName + " cannot be found");
}
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 58f5484..2a94393 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -515,8 +516,8 @@
intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault);
intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbPermissionActivity");
+ intent.setComponent(ComponentName.unflattenFromString(userContext.getResources().getString(
+ com.android.internal.R.string.config_usbPermissionActivity)));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index c063279..0becaf2 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -695,6 +695,15 @@
public static final String EVENT_CALL_HOLD_FAILED = "android.telecom.event.CALL_HOLD_FAILED";
/**
+ * Connection event used to inform Telecom when a switch operation on a call has failed.
+ * <p>
+ * Sent via {@link #sendConnectionEvent(String, Bundle)}. The {@link Bundle} parameter is
+ * expected to be null when this connection event is used.
+ */
+ public static final String EVENT_CALL_SWITCH_FAILED =
+ "android.telecom.event.CALL_SWITCH_FAILED";
+
+ /**
* Connection event used to inform {@link InCallService}s when the process of merging a
* Connection into a conference has begun.
* <p>
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 001888c..7381acb 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -419,12 +419,33 @@
KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
/**
- * Override the device's configuration for the ImsService to use for this SIM card.
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support both IMS MMTEL and RCS feature functionality instead of the device default
+ * ImsService for this subscription.
+ * @deprecated Use {@link #KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING} and
+ * {@link #KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING} instead to configure these values
+ * separately. If any of those values are not empty, they will override this value.
*/
public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
"config_ims_package_override_string";
/**
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support IMS MMTEL feature functionality instead of the device default ImsService for this
+ * subscription.
+ */
+ public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING =
+ "config_ims_mmtel_package_override_string";
+
+ /**
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support IMS RCS feature functionality instead of the device default ImsService for this
+ * subscription.
+ */
+ public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING =
+ "config_ims_rcs_package_override_string";
+
+ /**
* Override the package that will manage {@link SubscriptionPlan}
* information instead of the {@link CarrierService} that defines this
* value.
@@ -1946,6 +1967,12 @@
"allow_add_call_during_video_call";
/**
+ * When false, indicates that holding a video call is disabled
+ */
+ public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL =
+ "allow_holding_video_call";
+
+ /**
* When true, indicates that the HD audio icon in the in-call screen should not be shown for
* VoWifi calls.
* @hide
@@ -2176,7 +2203,7 @@
* the start of the next month.
* <p>
* This setting may be still overridden by explicit user choice. By default,
- * the platform value will be used.
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT =
"monthly_data_cycle_day_int";
@@ -2185,10 +2212,7 @@
* When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG},
* or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default
* value will be used for that key.
- *
- * @hide
*/
- @Deprecated
public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1;
/**
@@ -2212,8 +2236,8 @@
* If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will
* be disabled.
* <p>
- * This setting may be overridden by explicit user choice. By default, the platform value
- * will be used.
+ * This setting may be overridden by explicit user choice. By default,
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG =
"data_warning_threshold_bytes_long";
@@ -2221,8 +2245,7 @@
/**
* Controls if the device should automatically notify the user as they reach
* their cellular data warning. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL =
"data_warning_notification_bool";
@@ -2244,8 +2267,8 @@
* phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be
* disabled.
* <p>
- * This setting may be overridden by explicit user choice. By default, the platform value
- * will be used.
+ * This setting may be overridden by explicit user choice. By default,
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG =
"data_limit_threshold_bytes_long";
@@ -2253,8 +2276,7 @@
/**
* Controls if the device should automatically notify the user as they reach
* their cellular data limit. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL =
"data_limit_notification_bool";
@@ -2262,8 +2284,7 @@
/**
* Controls if the device should automatically notify the user when rapid
* cellular data usage is observed. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL =
"data_rapid_notification_bool";
@@ -3486,6 +3507,8 @@
sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+ sDefaults.putString(KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING, null);
+ sDefaults.putString(KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING, null);
sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
@@ -3629,6 +3652,7 @@
sDefaults.putBoolean(KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL, true);
sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL, true);
sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO, false);
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 02d8be3..39af34c 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -16,7 +16,10 @@
package android.telephony.ims;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.Context;
import android.telephony.SubscriptionManager;
@@ -25,12 +28,15 @@
*
* @hide
*/
+@SystemApi
+@TestApi
@SystemService(Context.TELEPHONY_IMS_SERVICE)
public class ImsManager {
private Context mContext;
- public ImsManager(Context context) {
+ /** @hide */
+ public ImsManager(@NonNull Context context) {
mContext = context;
}
@@ -41,6 +47,7 @@
* @throws IllegalArgumentException if the subscription is invalid.
* @return a ImsRcsManager instance with the specific subscription ID.
*/
+ @NonNull
public ImsRcsManager getImsRcsManager(int subscriptionId) {
if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId);
@@ -56,6 +63,7 @@
* @throws IllegalArgumentException if the subscription is invalid.
* @return a ImsMmTelManager instance with the specific subscription ID.
*/
+ @NonNull
public ImsMmTelManager getImsMmTelManager(int subscriptionId) {
if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId);
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index 0ceb103..c8b8ffb 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -222,9 +222,13 @@
private NetworkScanRequest(Parcel in) {
mScanType = in.readInt();
Parcelable[] tempSpecifiers = in.readParcelableArray(Object.class.getClassLoader());
- mSpecifiers = new RadioAccessSpecifier[tempSpecifiers.length];
- for (int i = 0; i < tempSpecifiers.length; i++) {
- mSpecifiers[i] = (RadioAccessSpecifier) tempSpecifiers[i];
+ if (tempSpecifiers != null) {
+ mSpecifiers = new RadioAccessSpecifier[tempSpecifiers.length];
+ for (int i = 0; i < tempSpecifiers.length; i++) {
+ mSpecifiers[i] = (RadioAccessSpecifier) tempSpecifiers[i];
+ }
+ } else {
+ mSpecifiers = null;
}
mSearchPeriodicity = in.readInt();
mMaxSearchTime = in.readInt();
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 257d634..78ad5c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -19,8 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.net.LinkProperties;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.ApnType;
@@ -29,6 +31,8 @@
import android.telephony.Annotation.NetworkType;
import android.telephony.data.ApnSetting;
+import dalvik.system.VMRuntime;
+
import java.util.Objects;
@@ -46,35 +50,62 @@
* <li>Data connection fail cause.
* </ul>
*
- * @hide
*/
-@SystemApi
public final class PreciseDataConnectionState implements Parcelable {
private @DataState int mState = TelephonyManager.DATA_UNKNOWN;
private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
private @DataFailureCause int mFailCause = DataFailCause.NONE;
- private @ApnType int mAPNTypes = ApnSetting.TYPE_NONE;
- private String mAPN = "";
+ private @ApnType int mApnTypes = ApnSetting.TYPE_NONE;
+ private String mApn = "";
private LinkProperties mLinkProperties = null;
+ private ApnSetting mApnSetting = null;
/**
* Constructor
*
+ * @deprecated this constructor has been superseded and should not be used.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @Deprecated
+ @UnsupportedAppUsage // (maxTargetSdk = Build.VERSION_CODES.Q)
+ // FIXME: figure out how to remove the UnsupportedAppUsage and delete this constructor
public PreciseDataConnectionState(@DataState int state,
@NetworkType int networkType,
- @ApnType int apnTypes, String apn,
- LinkProperties linkProperties,
+ @ApnType int apnTypes, @NonNull String apn,
+ @Nullable LinkProperties linkProperties,
@DataFailureCause int failCause) {
+ this(state, networkType, apnTypes, apn, linkProperties, failCause, null);
+ }
+
+
+ /**
+ * Constructor
+ *
+ * @param state the state of the data connection
+ * @param networkType the access network that is/would carry this data connection
+ * @param apnTypes the APN types that this data connection carries
+ * @param apnSetting if there is a valid APN for this Data Connection, then the APN Settings;
+ * if there is no valid APN setting for the specific type, then this will be null
+ * @param linkProperties if the data connection is connected, the properties of the connection
+ * @param failCause in case a procedure related to this data connection fails, a non-zero error
+ * code indicating the cause of the failure.
+ * @hide
+ */
+ public PreciseDataConnectionState(@DataState int state,
+ @NetworkType int networkType,
+ @ApnType int apnTypes, @NonNull String apn,
+ @Nullable LinkProperties linkProperties,
+ @DataFailureCause int failCause,
+ @Nullable ApnSetting apnSetting) {
mState = state;
mNetworkType = networkType;
- mAPNTypes = apnTypes;
- mAPN = apn;
+ mApnTypes = apnTypes;
+ mApn = apn;
mLinkProperties = linkProperties;
mFailCause = failCause;
+ mApnSetting = apnSetting;
}
/**
@@ -93,76 +124,160 @@
private PreciseDataConnectionState(Parcel in) {
mState = in.readInt();
mNetworkType = in.readInt();
- mAPNTypes = in.readInt();
- mAPN = in.readString();
- mLinkProperties = (LinkProperties)in.readParcelable(null);
+ mApnTypes = in.readInt();
+ mApn = in.readString();
+ mLinkProperties = (LinkProperties) in.readParcelable(null);
mFailCause = in.readInt();
+ mApnSetting = (ApnSetting) in.readParcelable(null);
}
/**
* Returns the state of data connection that supported the apn types returned by
* {@link #getDataConnectionApnTypeBitMask()}
+ *
+ * @deprecated use {@link #getState()}
+ * @hide
*/
+ @Deprecated
+ @SystemApi
public @DataState int getDataConnectionState() {
+ if (mState == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ return TelephonyManager.DATA_CONNECTED;
+ }
+
+ return mState;
+ }
+
+ /**
+ * Returns the high-level state of this data connection.
+ */
+ public @DataState int getState() {
return mState;
}
/**
* Returns the network type associated with this data connection.
+ *
+ * @deprecated use {@link getNetworkType()}
* @hide
*/
+ @Deprecated
+ @SystemApi
public @NetworkType int getDataConnectionNetworkType() {
return mNetworkType;
}
/**
- * Returns the data connection APN types supported by this connection and triggers
- * {@link PreciseDataConnectionState} change.
+ * Returns the network type associated with this data connection.
+ *
+ * Return the current/latest (radio) bearer technology that carries this data connection.
+ * For a variety of reasons, the network type can change during the life of the data
+ * connection, and this information is not reliable unless the physical link is currently
+ * active; (there is currently no mechanism to know whether the physical link is active at
+ * any given moment). Thus, this value is generally correct but may not be relied-upon to
+ * represent the status of the radio bearer at any given moment.
*/
- public @ApnType int getDataConnectionApnTypeBitMask() {
- return mAPNTypes;
+ public @NetworkType int getNetworkType() {
+ return mNetworkType;
}
/**
- * Returns APN {@link ApnSetting} of this data connection.
+ * Returns the APN types mapped to this data connection.
+ *
+ * @deprecated use {@link #getApnSetting()}
+ * @hide
*/
- @Nullable
+ @Deprecated
+ @SystemApi
+ public @ApnType int getDataConnectionApnTypeBitMask() {
+ return mApnTypes;
+ }
+
+ /**
+ * Returns APN of this data connection.
+ *
+ * @deprecated use {@link #getApnSetting()}
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @Deprecated
public String getDataConnectionApn() {
- return mAPN;
+ return mApn;
}
/**
* Get the properties of the network link {@link LinkProperties}.
+ *
+ * @deprecated use {@link #getLinkProperties()}
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @SystemApi
+ @Nullable
public LinkProperties getDataConnectionLinkProperties() {
return mLinkProperties;
}
/**
- * Returns data connection fail cause, in case there was a failure.
+ * Get the properties of the network link {@link LinkProperties}.
*/
- public @Annotation.DataFailureCause int getDataConnectionFailCause() {
+ @Nullable
+ public LinkProperties getLinkProperties() {
+ return mLinkProperties;
+ }
+
+ /**
+ * Returns the cause code generated by the most recent state change.
+ *
+ * @deprecated use {@link #getLastCauseCode()}
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public int getDataConnectionFailCause() {
return mFailCause;
}
+ /**
+ * Returns the cause code generated by the most recent state change.
+ *
+ * Return the cause code for the most recent change in {@link #getState}. In the event of an
+ * error, this cause code will be non-zero.
+ */
+ // FIXME(b144774287): some of these cause codes should have a prescribed meaning.
+ public int getLastCauseCode() {
+ return mFailCause;
+ }
+
+ /**
+ * Return the APN Settings for this data connection.
+ *
+ * Returns the ApnSetting that was used to configure this data connection.
+ */
+ // FIXME: This shouldn't be nullable; update once the ApnSetting is supplied correctly
+ @Nullable ApnSetting getApnSetting() {
+ return mApnSetting;
+ }
+
@Override
public int describeContents() {
return 0;
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(mState);
out.writeInt(mNetworkType);
- out.writeInt(mAPNTypes);
- out.writeString(mAPN);
+ out.writeInt(mApnTypes);
+ out.writeString(mApn);
out.writeParcelable(mLinkProperties, flags);
out.writeInt(mFailCause);
+ out.writeParcelable(mApnSetting, flags);
}
- public static final @android.annotation.NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
+ public static final @NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
= new Parcelable.Creator<PreciseDataConnectionState>() {
public PreciseDataConnectionState createFromParcel(Parcel in) {
@@ -176,8 +291,8 @@
@Override
public int hashCode() {
- return Objects.hash(mState, mNetworkType, mAPNTypes, mAPN, mLinkProperties,
- mFailCause);
+ return Objects.hash(mState, mNetworkType, mApnTypes, mApn, mLinkProperties,
+ mFailCause, mApnSetting);
}
@Override
@@ -188,11 +303,12 @@
}
PreciseDataConnectionState other = (PreciseDataConnectionState) obj;
- return Objects.equals(mAPN, other.mAPN) && mAPNTypes == other.mAPNTypes
+ return Objects.equals(mApn, other.mApn) && mApnTypes == other.mApnTypes
&& mFailCause == other.mFailCause
&& Objects.equals(mLinkProperties, other.mLinkProperties)
&& mNetworkType == other.mNetworkType
- && mState == other.mState;
+ && mState == other.mState
+ && Objects.equals(mApnSetting, other.mApnSetting);
}
@NonNull
@@ -202,10 +318,11 @@
sb.append("Data Connection state: " + mState);
sb.append(", Network type: " + mNetworkType);
- sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mAPNTypes));
- sb.append(", APN: " + mAPN);
+ sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mApnTypes));
+ sb.append(", APN: " + mApn);
sb.append(", Link properties: " + mLinkProperties);
sb.append(", Fail cause: " + DataFailCause.toString(mFailCause));
+ sb.append(", Apn Setting: " + mApnSetting);
return sb.toString();
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4fba625..78c5e43 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -38,6 +38,9 @@
import android.annotation.WorkerThread;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -142,6 +145,14 @@
private static final String TAG = "TelephonyManager";
/**
+ * To expand the error codes for {@link TelephonyManager#updateAvailableNetworks} and
+ * {@link TelephonyManager#setPreferredOpportunisticDataSubscription}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
+
+ /**
* The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
* into the ResultReceiver Bundle.
* @hide
@@ -790,18 +801,6 @@
public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY;
/**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for an String representation of the data interface.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getParcelableExtra(String name)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_LINK_PROPERTIES_KEY = PhoneConstants.DATA_LINK_PROPERTIES_KEY;
-
- /**
* Broadcast intent action for letting the default dialer to know to show voicemail
* notification.
*
@@ -2864,6 +2863,24 @@
//
//
+ /** @hide */
+ @IntDef(prefix = {"SIM_STATE_"},
+ value = {
+ SIM_STATE_UNKNOWN,
+ SIM_STATE_ABSENT,
+ SIM_STATE_PIN_REQUIRED,
+ SIM_STATE_PUK_REQUIRED,
+ SIM_STATE_NETWORK_LOCKED,
+ SIM_STATE_READY,
+ SIM_STATE_NOT_READY,
+ SIM_STATE_PERM_DISABLED,
+ SIM_STATE_CARD_IO_ERROR,
+ SIM_STATE_CARD_RESTRICTED,
+ SIM_STATE_LOADED,
+ SIM_STATE_PRESENT,
+ })
+ public @interface SimState {}
+
/**
* SIM card state: Unknown. Signifies that the SIM is in transition
* between states. For example, when the user inputs the SIM pin
@@ -3069,7 +3086,7 @@
* @see #SIM_STATE_CARD_IO_ERROR
* @see #SIM_STATE_CARD_RESTRICTED
*/
- public int getSimState() {
+ public @SimState int getSimState() {
int simState = getSimStateIncludingLoaded();
if (simState == SIM_STATE_LOADED) {
simState = SIM_STATE_READY;
@@ -3077,7 +3094,7 @@
return simState;
}
- private int getSimStateIncludingLoaded() {
+ private @SimState int getSimStateIncludingLoaded() {
int slotIndex = getSlotIndex();
// slotIndex may be invalid due to sim being absent. In that case query all slots to get
// sim state
@@ -3111,7 +3128,7 @@
* @hide
*/
@SystemApi
- public int getSimCardState() {
+ public @SimState int getSimCardState() {
int simState = getSimState();
return getSimCardStateFromSimState(simState);
}
@@ -3131,7 +3148,7 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public int getSimCardState(int physicalSlotIndex) {
+ public @SimState int getSimCardState(int physicalSlotIndex) {
int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex));
return getSimCardStateFromSimState(simState);
}
@@ -3141,7 +3158,7 @@
* @param simState
* @return SIM card state
*/
- private int getSimCardStateFromSimState(int simState) {
+ private @SimState int getSimCardStateFromSimState(int simState) {
switch (simState) {
case SIM_STATE_UNKNOWN:
case SIM_STATE_ABSENT:
@@ -3181,7 +3198,7 @@
* @hide
*/
@SystemApi
- public int getSimApplicationState() {
+ public @SimState int getSimApplicationState() {
int simState = getSimStateIncludingLoaded();
return getSimApplicationStateFromSimState(simState);
}
@@ -3204,7 +3221,7 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public int getSimApplicationState(int physicalSlotIndex) {
+ public @SimState int getSimApplicationState(int physicalSlotIndex) {
int simState =
SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex));
return getSimApplicationStateFromSimState(simState);
@@ -3215,7 +3232,7 @@
* @param simState
* @return SIM application state
*/
- private int getSimApplicationStateFromSimState(int simState) {
+ private @SimState int getSimApplicationStateFromSimState(int simState) {
switch (simState) {
case SIM_STATE_UNKNOWN:
case SIM_STATE_ABSENT:
@@ -3272,7 +3289,7 @@
* @see #SIM_STATE_CARD_IO_ERROR
* @see #SIM_STATE_CARD_RESTRICTED
*/
- public int getSimState(int slotIndex) {
+ public @SimState int getSimState(int slotIndex) {
int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex);
if (simState == SIM_STATE_LOADED) {
simState = SIM_STATE_READY;
@@ -5100,6 +5117,7 @@
DATA_CONNECTING,
DATA_CONNECTED,
DATA_SUSPENDED,
+ DATA_DISCONNECTING,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DataState{}
@@ -5116,6 +5134,12 @@
* traffic is temporarily unavailable. For example, in a 2G network,
* data activity may be suspended when a voice call arrives. */
public static final int DATA_SUSPENDED = 3;
+ /**
+ * Data connection state: Disconnecting.
+ *
+ * IP traffic may be available but will cease working imminently.
+ */
+ public static final int DATA_DISCONNECTING = 4;
/**
* Returns a constant indicating the current data connection state
@@ -5125,14 +5149,21 @@
* @see #DATA_CONNECTING
* @see #DATA_CONNECTED
* @see #DATA_SUSPENDED
+ * @see #DATA_DISCONNECTING
*/
public int getDataState() {
try {
ITelephony telephony = getITelephony();
if (telephony == null)
return DATA_DISCONNECTED;
- return telephony.getDataStateForSubId(
+ int state = telephony.getDataStateForSubId(
getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
+ if (state == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ return TelephonyManager.DATA_CONNECTED;
+ }
+
+ return state;
} catch (RemoteException ex) {
// the phone process is restarting.
return DATA_DISCONNECTED;
@@ -5153,6 +5184,7 @@
case DATA_CONNECTING: return "CONNECTING";
case DATA_CONNECTED: return "CONNECTED";
case DATA_SUSPENDED: return "SUSPENDED";
+ case DATA_DISCONNECTING: return "DISCONNECTING";
}
return "UNKNOWN(" + state + ")";
}
@@ -6804,7 +6836,8 @@
* If the list is longer than the size of EFfplmn, then the file will be written from the
* beginning of the list up to the file size.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * <p>Requires Permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE
+ * MODIFY_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @param fplmns a list of PLMNs to be forbidden.
@@ -6818,7 +6851,7 @@
public int setForbiddenPlmns(@NonNull List<String> fplmns) {
try {
ITelephony telephony = getITelephony();
- if (telephony == null) return 0;
+ if (telephony == null) return -1;
return telephony.setForbiddenPlmns(
getSubId(), APPTYPE_USIM, fplmns, getOpPackageName(), getFeatureId());
} catch (RemoteException ex) {
@@ -6827,7 +6860,7 @@
// This could happen before phone starts
Rlog.e(TAG, "setForbiddenPlmns NullPointerException: " + ex.getMessage());
}
- return 0;
+ return -1;
}
/**
@@ -10266,19 +10299,25 @@
}
/**
- * Action set from carrier signalling broadcast receivers to enable/disable radio
- * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
- * @param subId the subscription ID that this action applies to.
+ * Carrier action to enable or disable the radio.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ *
* @param enabled control enable or disable radio.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void carrierActionSetRadioEnabled(int subId, boolean enabled) {
+ public void setRadioEnabled(boolean enabled) {
try {
ITelephony service = getITelephony();
if (service != null) {
- service.carrierActionSetRadioEnabled(subId, enabled);
+ service.carrierActionSetRadioEnabled(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#carrierActionSetRadioEnabled", e);
@@ -10286,20 +10325,25 @@
}
/**
- * Action set from carrier signalling broadcast receivers to start/stop reporting default
- * network available events
- * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
- * @param subId the subscription ID that this action applies to.
+ * Carrier action to start or stop reporting default network available events.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ *
* @param report control start/stop reporting network status.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) {
+ public void reportDefaultNetworkStatus(boolean report) {
try {
ITelephony service = getITelephony();
if (service != null) {
- service.carrierActionReportDefaultNetworkStatus(subId, report);
+ service.carrierActionReportDefaultNetworkStatus(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), report);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#carrierActionReportDefaultNetworkStatus", e);
@@ -10307,18 +10351,24 @@
}
/**
- * Action set from carrier signalling broadcast receivers to reset all carrier actions
- * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
- * @param subId the subscription ID that this action applies to.
+ * Reset all carrier actions previously set by {@link #setRadioEnabled},
+ * {@link #reportDefaultNetworkStatus} and {@link #setCarrierDataEnabled}.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void carrierActionResetAll(int subId) {
+ public void resetAllCarrierActions() {
try {
ITelephony service = getITelephony();
if (service != null) {
- service.carrierActionResetAll(subId);
+ service.carrierActionResetAll(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#carrierActionResetAll", e);
@@ -11331,7 +11381,11 @@
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> {
- callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+ if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+ callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+ } else {
+ callback.accept(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+ }
});
} finally {
Binder.restoreCallingIdentity(identity);
@@ -11428,7 +11482,11 @@
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> {
- callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
+ if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+ callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
+ } else {
+ callback.accept(UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE);
+ }
});
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index b37d7c7..d3fb37f 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -20,6 +20,8 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -30,6 +32,7 @@
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
import java.util.concurrent.Executor;
@@ -42,6 +45,8 @@
* Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this manager.
* @hide
*/
+@SystemApi
+@TestApi
public class ImsRcsManager implements RegistrationManager {
private static final String TAG = "ImsRcsManager";
@@ -105,7 +110,7 @@
*
* @param capabilities The new availability of the capabilities.
*/
- public void onAvailabilityChanged(RcsFeature.RcsImsCapabilities capabilities) {
+ public void onAvailabilityChanged(@NonNull RcsFeature.RcsImsCapabilities capabilities) {
}
/**@hide*/
@@ -142,6 +147,7 @@
/**{@inheritDoc}*/
@Override
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerImsRegistrationCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull RegistrationCallback c)
@@ -159,6 +165,7 @@
/**{@inheritDoc}*/
@Override
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void unregisterImsRegistrationCallback(
@NonNull RegistrationManager.RegistrationCallback c) {
if (c == null) {
@@ -170,6 +177,7 @@
/**{@inheritDoc}*/
@Override
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
@NonNull @ImsRegistrationState Consumer<Integer> stateCallback) {
if (stateCallback == null) {
@@ -184,6 +192,7 @@
/**{@inheritDoc}*/
@Override
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
@NonNull @AccessNetworkConstants.TransportType
Consumer<Integer> transportTypeCallback) {
@@ -219,7 +228,7 @@
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public void registerRcsAvailabilityCallback(@CallbackExecutor Executor executor,
+ public void registerRcsAvailabilityCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull AvailabilityCallback c) throws ImsException {
if (c == null) {
throw new IllegalArgumentException("Must include a non-null AvailabilityCallback.");
@@ -231,13 +240,13 @@
IImsRcsController imsRcsController = getIImsRcsController();
if (imsRcsController == null) {
Log.e(TAG, "Register availability callback: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
+ throw new ImsException("Cannot find remote IMS service",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
c.setExecutor(executor);
try {
- imsRcsController.registerRcsAvailabilityCallback(c.getBinder());
+ imsRcsController.registerRcsAvailabilityCallback(mSubId, c.getBinder());
} catch (RemoteException e) {
Log.e(TAG, "Error calling IImsRcsController#registerRcsAvailabilityCallback", e);
throw new ImsException("Remote IMS Service is not available",
@@ -267,12 +276,12 @@
IImsRcsController imsRcsController = getIImsRcsController();
if (imsRcsController == null) {
Log.e(TAG, "Unregister availability callback: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
+ throw new ImsException("Cannot find remote IMS service",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
try {
- imsRcsController.unregisterRcsAvailabilityCallback(c.getBinder());
+ imsRcsController.unregisterRcsAvailabilityCallback(mSubId, c.getBinder());
} catch (RemoteException e) {
Log.e(TAG, "Error calling IImsRcsController#unregisterRcsAvailabilityCallback", e);
throw new ImsException("Remote IMS Service is not available",
@@ -287,6 +296,9 @@
* RCS capabilities provided over-the-top by applications.
*
* @param capability The RCS capability to query.
+ * @param radioTech The radio tech that this capability failed for, defined as
+ * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or
+ * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}.
* @return true if the RCS capability is capable for this subscription, false otherwise. This
* does not necessarily mean that we are registered for IMS and the capability is available, but
* rather the subscription is capable of this service over IMS.
@@ -297,17 +309,17 @@
* See {@link ImsException#getCode()} for more information on the error codes.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public boolean isCapable(@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability)
- throws ImsException {
+ public boolean isCapable(@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) throws ImsException {
IImsRcsController imsRcsController = getIImsRcsController();
if (imsRcsController == null) {
Log.e(TAG, "isCapable: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
+ throw new ImsException("Cannot find remote IMS service",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
try {
- return imsRcsController.isCapable(mSubId, capability);
+ return imsRcsController.isCapable(mSubId, capability, radioTech);
} catch (RemoteException e) {
Log.e(TAG, "Error calling IImsRcsController#isCapable", e);
throw new ImsException("Remote IMS Service is not available",
@@ -336,7 +348,7 @@
IImsRcsController imsRcsController = getIImsRcsController();
if (imsRcsController == null) {
Log.e(TAG, "isAvailable: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
+ throw new ImsException("Cannot find remote IMS service",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index b379bd0..e81bac0 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -26,9 +26,9 @@
* {@hide}
*/
interface IImsRcsController {
- void registerRcsAvailabilityCallback(IImsCapabilityCallback c);
- void unregisterRcsAvailabilityCallback(IImsCapabilityCallback c);
- boolean isCapable(int subId, int capability);
+ void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
+ void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
+ boolean isCapable(int subId, int capability, int radioTech);
boolean isAvailable(int subId, int capability);
// ImsUceAdapter specific
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 0eaf8dc..884a0bc 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -193,7 +193,6 @@
* of the capability and notify the capability status as true using
* {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the
* framework that the capability is available for usage.
- * @hide
*/
public static class RcsImsCapabilities extends Capabilities {
/** @hide*/
@@ -207,7 +206,6 @@
/**
* Undefined capability type for initialization
- * @hide
*/
public static final int CAPABILITY_TYPE_NONE = 0;
@@ -215,7 +213,6 @@
* This carrier supports User Capability Exchange using SIP OPTIONS as defined by the
* framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
* If not set, this RcsFeature should not service capability requests.
- * @hide
*/
public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1 << 0;
@@ -224,33 +221,27 @@
* framework. If set, the RcsFeature should support capability exchange using a presence
* server. If not set, this RcsFeature should not publish capabilities or service capability
* requests using presence.
- * @hide
*/
public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1;
- /**@hide*/
public RcsImsCapabilities(@RcsImsCapabilityFlag int capabilities) {
super(capabilities);
}
- /**@hide*/
private RcsImsCapabilities(Capabilities c) {
super(c.getMask());
}
- /**@hide*/
@Override
public void addCapabilities(@RcsImsCapabilityFlag int capabilities) {
super.addCapabilities(capabilities);
}
- /**@hide*/
@Override
public void removeCapabilities(@RcsImsCapabilityFlag int capabilities) {
super.removeCapabilities(capabilities);
}
- /**@hide*/
@Override
public boolean isCapable(@RcsImsCapabilityFlag int capabilities) {
return super.isCapable(capabilities);
@@ -295,10 +286,9 @@
* set, the {@link RcsFeature} has brought up the capability and is ready for framework
* requests. To change the status of the capabilities
* {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called.
- * @hide
*/
@Override
- public final RcsImsCapabilities queryCapabilityStatus() {
+ public @NonNull final RcsImsCapabilities queryCapabilityStatus() {
return new RcsImsCapabilities(super.queryCapabilityStatus());
}
@@ -306,7 +296,6 @@
* Notify the framework that the capabilities status has changed. If a capability is enabled,
* this signals to the framework that the capability has been initialized and is ready.
* Call {@link #queryCapabilityStatus()} to return the current capability status.
- * @hide
*/
public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities c) {
if (c == null) {
@@ -321,7 +310,6 @@
* {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} to
* enable or disable capability A, this method should return the correct configuration for
* capability A afterwards (until it has changed).
- * @hide
*/
public boolean queryCapabilityConfiguration(
@RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@@ -343,11 +331,10 @@
* If for some reason one or more of these capabilities can not be enabled/disabled,
* {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError(int, int, int)} should
* be called for each capability change that resulted in an error.
- * @hide
*/
@Override
- public void changeEnabledCapabilities(CapabilityChangeRequest request,
- CapabilityCallbackProxy c) {
+ public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request,
+ @NonNull CapabilityCallbackProxy c) {
// Base Implementation - Override to provide functionality
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ec54ec..97b24ae 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -888,12 +888,13 @@
/**
* @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
*/
- boolean setImsService(int slotId, boolean isCarrierImsService, String packageName);
+ boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+ in int[] featureTypes, in String packageName);
/**
* @return the package name of the carrier/device ImsService associated with this slot.
*/
- String getImsService(int slotId, boolean isCarrierImsService);
+ String getBoundImsServicePackage(int slotIndex, boolean isCarrierImsService, int featureType);
/**
* Get the MmTelFeature state attached to this subscription id.
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
index f7f0f29..8640acc 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
@@ -16,14 +16,10 @@
package com.android.internal.telephony;
-import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
-import android.telephony.PreciseCallState;
import com.android.internal.telephony.PhoneConstants;
-import java.util.List;
-
public class PhoneConstantConversions {
/**
* Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
@@ -67,6 +63,8 @@
return TelephonyManager.DATA_CONNECTED;
case SUSPENDED:
return TelephonyManager.DATA_SUSPENDED;
+ case DISCONNECTING:
+ return TelephonyManager.DATA_DISCONNECTING;
default:
return TelephonyManager.DATA_DISCONNECTED;
}
@@ -84,6 +82,8 @@
return PhoneConstants.DataState.CONNECTED;
case TelephonyManager.DATA_SUSPENDED:
return PhoneConstants.DataState.SUSPENDED;
+ case TelephonyManager.DATA_DISCONNECTING:
+ return PhoneConstants.DataState.DISCONNECTING;
default:
return PhoneConstants.DataState.DISCONNECTED;
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index fde2c5a..fadb573 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -46,6 +46,7 @@
* <ul>
* <li>CONNECTED = IP traffic should be available</li>
* <li>CONNECTING = Currently setting up data connection</li>
+ * <li>DISCONNECTING = IP temporarily available</li>
* <li>DISCONNECTED = IP not available</li>
* <li>SUSPENDED = connection is created but IP traffic is
* temperately not available. i.e. voice call is in place
@@ -55,10 +56,15 @@
@UnsupportedAppUsage(implicitMember =
"values()[Lcom/android/internal/telephony/PhoneConstants$DataState;")
public enum DataState {
- @UnsupportedAppUsage CONNECTED,
- @UnsupportedAppUsage CONNECTING,
- @UnsupportedAppUsage DISCONNECTED,
- @UnsupportedAppUsage SUSPENDED;
+ @UnsupportedAppUsage
+ CONNECTED,
+ @UnsupportedAppUsage
+ CONNECTING,
+ @UnsupportedAppUsage
+ DISCONNECTED,
+ @UnsupportedAppUsage
+ SUSPENDED,
+ DISCONNECTING;
};
public static final String STATE_KEY = "state";
@@ -91,20 +97,12 @@
public static final String PHONE_NAME_KEY = "phoneName";
public static final String DATA_NETWORK_TYPE_KEY = "networkType";
- public static final String DATA_FAILURE_CAUSE_KEY = "failCause";
public static final String DATA_APN_TYPE_KEY = "apnType";
public static final String DATA_APN_KEY = "apn";
- public static final String DATA_LINK_PROPERTIES_KEY = "linkProperties";
- public static final String DATA_NETWORK_CAPABILITIES_KEY = "networkCapabilities";
- public static final String DATA_IFACE_NAME_KEY = "iface";
- public static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
- public static final String DATA_NETWORK_ROAMING_KEY = "networkRoaming";
public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
- public static final String REASON_LINK_PROPERTIES_CHANGED = "linkPropertiesChanged";
-
/**
* Return codes for supplyPinReturnResult and
* supplyPukReturnResult APIs
diff --git a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
index e323592..026677e 100644
--- a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
+++ b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
@@ -53,6 +53,8 @@
*/
@Test
public void testCreateStartDelete() throws Exception {
+ // Disable package verifier for ADB installs.
+ getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
int iteration = 0;
final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(TIME_LIMIT_MINUTES);
while (System.nanoTime() < deadline) {
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index 5cb0d7e..e632aaf 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -22,8 +22,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import android.net.SocketKeepalive.InvalidPacketException;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
index 6b4c346..ca1847a 100755
--- a/tools/localedata/extract_icu_data.py
+++ b/tools/localedata/extract_icu_data.py
@@ -176,6 +176,9 @@
dump_representative_locales(representative_locales)
return likely_script_dict
+def escape_script_variable_name(script):
+ """Escape characters, e.g. '~', in a C++ variable name"""
+ return script.replace("~", "_")
def read_parent_data(icu_data_dir):
"""Read locale parent data from ICU data files."""
@@ -221,7 +224,7 @@
for script in sorted_scripts:
parent_dict = script_organized_dict[script]
print ('const std::unordered_map<uint32_t, uint32_t> %s_PARENTS({'
- % script.upper())
+ % escape_script_variable_name(script.upper()))
for locale in sorted(parent_dict.keys()):
parent = parent_dict[locale]
print ' {0x%08Xu, 0x%08Xu}, // %s -> %s' % (
@@ -239,7 +242,7 @@
for script in sorted_scripts:
print " {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % (
script[0], script[1], script[2], script[3],
- script.upper())
+ escape_script_variable_name(script.upper()))
print '};'
diff --git a/wifi/Android.bp b/wifi/Android.bp
new file mode 100644
index 0000000..08115ec
--- /dev/null
+++ b/wifi/Android.bp
@@ -0,0 +1,87 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+filegroup {
+ name: "framework-wifi-updatable-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ exclude_srcs: [
+ ":framework-wifi-non-updatable-sources"
+ ],
+ path: "java",
+}
+
+filegroup {
+ name: "framework-wifi-non-updatable-sources",
+ srcs: [
+ // TODO(b/146011398) package android.net.wifi is now split amongst 2 jars: framework.jar and
+ // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
+ // to a separate package.
+ "java/android/net/wifi/WifiNetworkScoreCache.java",
+ "java/android/net/wifi/WifiCondManager.java",
+ "java/android/net/wifi/wificond/*.java",
+ ":libwificond_ipc_aidl",
+ ],
+}
+
+filegroup {
+ name: "framework-wifi-annotations",
+ srcs: ["java/android/net/wifi/WifiAnnotations.java"],
+}
+
+java_library {
+ name: "framework-wifi",
+ sdk_version: "core_platform", // TODO(b/140299412) should be core_current
+ libs: [
+ "framework-minus-apex", // TODO(b/140299412) should be framework-system-stubs
+ ],
+ srcs: [
+ ":framework-wifi-updatable-sources",
+ ],
+ installable: true,
+ optimize: {
+ enabled: false
+ }
+}
+
+droidstubs {
+ name: "framework-wifi-stubs-srcs",
+ srcs: [
+ ":framework-annotations",
+ ":framework-wifi-updatable-sources",
+ ],
+ aidl: {
+ include_dirs: ["frameworks/base/core/java"],
+ },
+ defaults: [ "framework-module-stubs-defaults-systemapi" ],
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+}
+
+java_library {
+ name: "framework-wifi-stubs",
+ srcs: [":framework-wifi-stubs-srcs"],
+ aidl: {
+ export_include_dirs: [
+ "java",
+ ],
+ },
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+ installable: false,
+}
+
diff --git a/wifi/java/android/net/wifi/WifiAnnotations.java b/wifi/java/android/net/wifi/WifiAnnotations.java
new file mode 100644
index 0000000..4a7dee1
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiAnnotations.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Wifi annotations meant to be statically linked into client modules, since they cannot be
+ * exposed as @SystemApi.
+ *
+ * e.g. {@link IntDef}, {@link StringDef}
+ *
+ * @hide
+ */
+public final class WifiAnnotations {
+ private WifiAnnotations() {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SCAN_TYPE_"}, value = {
+ WifiScanner.SCAN_TYPE_LOW_LATENCY,
+ WifiScanner.SCAN_TYPE_LOW_POWER,
+ WifiScanner.SCAN_TYPE_HIGH_ACCURACY})
+ public @interface ScanType {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"WIFI_BAND_"}, value = {
+ WifiScanner.WIFI_BAND_UNSPECIFIED,
+ WifiScanner.WIFI_BAND_24_GHZ,
+ WifiScanner.WIFI_BAND_5_GHZ,
+ WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY,
+ WifiScanner.WIFI_BAND_6_GHZ})
+ public @interface WifiBandBasic {}
+}
diff --git a/wifi/java/android/net/wifi/WifiCondManager.java b/wifi/java/android/net/wifi/WifiCondManager.java
index 9ae7e3a..c05ba34 100644
--- a/wifi/java/android/net/wifi/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/WifiCondManager.java
@@ -725,7 +725,7 @@
/**
* Return scan type for the parcelable {@link SingleScanSettings}
*/
- private static int getScanType(@WifiScanner.ScanType int scanType) {
+ private static int getScanType(@WifiAnnotations.ScanType int scanType) {
switch (scanType) {
case WifiScanner.SCAN_TYPE_LOW_LATENCY:
return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN;
@@ -746,7 +746,7 @@
* @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
* @return Returns true on success.
*/
- public boolean scan(@NonNull String ifaceName, @WifiScanner.ScanType int scanType,
+ public boolean scan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
Set<Integer> freqs, List<byte[]> hiddenNetworkSSIDs) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
@@ -868,7 +868,7 @@
* @return frequencies vector of valid frequencies (MHz), or null for error.
* @throws IllegalArgumentException if band is not recognized.
*/
- public int [] getChannelsForBand(@WifiScanner.WifiBandBasic int band) {
+ public int [] getChannelsForBand(@WifiAnnotations.WifiBandBasic int band) {
if (mWificond == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
return null;
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 760497b..8fedda4 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -89,16 +89,6 @@
/** 6 GHz band */
public static final int WIFI_BAND_6_GHZ = 1 << WIFI_BAND_INDEX_6_GHZ;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"WIFI_BAND_"}, value = {
- WIFI_BAND_UNSPECIFIED,
- WIFI_BAND_24_GHZ,
- WIFI_BAND_5_GHZ,
- WIFI_BAND_5_GHZ_DFS_ONLY,
- WIFI_BAND_6_GHZ})
- public @interface WifiBandBasic {}
-
/**
* Combination of bands
* Note that those are only the common band combinations,
@@ -249,14 +239,6 @@
*/
public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"SCAN_TYPE_"}, value = {
- SCAN_TYPE_LOW_LATENCY,
- SCAN_TYPE_LOW_POWER,
- SCAN_TYPE_HIGH_ACCURACY})
- public @interface ScanType {}
-
/**
* Optimize the scan for lower latency.
* @see ScanSettings#type
@@ -354,7 +336,7 @@
* {@link #SCAN_TYPE_HIGH_ACCURACY}.
* Default value: {@link #SCAN_TYPE_LOW_LATENCY}.
*/
- @ScanType
+ @WifiAnnotations.ScanType
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public int type = SCAN_TYPE_LOW_LATENCY;
/**
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
index 3453d6e..d2c385b4 100644
--- a/wifi/tests/Android.mk
+++ b/wifi/tests/Android.mk
@@ -59,6 +59,8 @@
LOCAL_PACKAGE_NAME := FrameworksWifiApiTests
LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_COMPATIBILITY_SUITE := \
+ device-tests \
+ mts \
include $(BUILD_PACKAGE)
diff --git a/wifi/tests/AndroidTest.xml b/wifi/tests/AndroidTest.xml
index cae19e4..987fee7 100644
--- a/wifi/tests/AndroidTest.xml
+++ b/wifi/tests/AndroidTest.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
<configuration description="Runs Frameworks Wifi API Tests.">
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="FrameworksWifiApiTests.apk" />
</target_preparer>
diff --git a/wifi/tests/runtests.sh b/wifi/tests/runtests.sh
index 7a0dfb0..4024371 100755
--- a/wifi/tests/runtests.sh
+++ b/wifi/tests/runtests.sh
@@ -16,7 +16,6 @@
set -x # print commands
-adb root
adb wait-for-device
TARGET_ARCH=$($ANDROID_BUILD_TOP/build/soong/soong_ui.bash --dumpvar-mode TARGET_ARCH)