Merge "Use UnlockMethodCache#canSkipBouncer in user switcher"
diff --git a/api/system-current.txt b/api/system-current.txt
index eb7ed6e..a8098fb 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -139,6 +139,7 @@
field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+ field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
@@ -5471,7 +5472,7 @@
public class UpdateEngine {
ctor public UpdateEngine();
method public void applyPayload(String, long, long, String[]);
- method public void applyPayload(java.io.FileDescriptor, long, long, String[]);
+ method public void applyPayload(@NonNull java.io.FileDescriptor, long, long, @NonNull String[]);
method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler);
method public boolean bind(android.os.UpdateEngineCallback);
method public void cancel();
@@ -7758,6 +7759,8 @@
field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
+ field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 268435456; // 0x10000000
+ field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 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
@@ -8129,7 +8132,9 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
method public int getSimApplicationState();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int);
method public int getSimCardState();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
@@ -8142,6 +8147,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
method public boolean isDataConnectivityPossible();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -9238,7 +9244,7 @@
field public static final int STATE_UNAVAILABLE = 0; // 0x0
}
- public static class ImsFeature.Capabilities {
+ @Deprecated public static class ImsFeature.Capabilities {
field @Deprecated protected int mCapabilities;
}
@@ -9272,7 +9278,7 @@
public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
ctor public MmTelFeature.MmTelCapabilities();
ctor @Deprecated public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
- ctor public MmTelFeature.MmTelCapabilities(int);
+ ctor public MmTelFeature.MmTelCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
method public final void addCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
method public final boolean isCapable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
method public final void removeCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
@@ -9416,7 +9422,8 @@
method public void onReady();
method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
method public final void onSmsReceived(int, String, byte[]) throws java.lang.RuntimeException;
- method public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+ method @Deprecated public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+ method public final void onSmsStatusReportReceived(int, String, byte[]) throws java.lang.RuntimeException;
method public void sendSms(int, int, String, String, boolean, byte[]);
field public static final int DELIVER_STATUS_ERROR_GENERIC = 2; // 0x2
field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index f925023..8be95e4 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -22,7 +22,6 @@
"libcutils",
"libdl",
"libhidlbase",
- "libhwbinder",
"liblog",
"libnativeloader",
"libutils",
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 6df0a8e..8944ded 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -132,8 +132,6 @@
"libhardware",
"libhardware_legacy",
"libhidlbase",
- "libhidltransport",
- "libhwbinder",
"android.frameworks.stats@1.0",
"android.hardware.health@2.0",
"android.hardware.power@1.0",
@@ -209,6 +207,10 @@
],
srcs: [
+ // atom_field_options.proto needs field_options.proto, but that is
+ // not included in libprotobuf-cpp-lite, so compile it here.
+ ":libprotobuf-internal-protos",
+
"src/atom_field_options.proto",
"src/atoms.proto",
"src/stats_log.proto",
@@ -270,11 +272,11 @@
],
proto: {
- type: "full",
+ type: "lite",
include_dirs: ["external/protobuf/src"],
},
- shared_libs: ["libprotobuf-cpp-full"],
+ shared_libs: ["libprotobuf-cpp-lite"],
}
@@ -287,6 +289,10 @@
defaults: ["statsd_defaults"],
srcs: [
+ // atom_field_options.proto needs field_options.proto, but that is
+ // not included in libprotobuf-cpp-lite, so compile it here.
+ ":libprotobuf-internal-protos",
+
"src/atom_field_options.proto",
"src/atoms.proto",
"src/stats_log.proto",
@@ -301,7 +307,7 @@
],
proto: {
- type: "full",
+ type: "lite",
include_dirs: ["external/protobuf/src"],
},
@@ -323,7 +329,7 @@
shared_libs: [
"libgtest_prod",
"libstatslog",
- "libprotobuf-cpp-full",
+ "libprotobuf-cpp-lite",
],
}
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index d1dcb5df..7ace44e 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -24,6 +24,7 @@
#include "subscriber/IncidentdReporter.h"
#include "subscriber/SubscriberReporter.h"
+#include <inttypes.h>
#include <statslog.h>
#include <time.h>
@@ -224,7 +225,7 @@
}
if (!mSubscriptions.empty()) {
- ALOGI("An anomaly (%lld) %s has occurred! Informing subscribers.",
+ ALOGI("An anomaly (%" PRId64 ") %s has occurred! Informing subscribers.",
mAlert.id(), key.toString().c_str());
informSubscribers(key, metricId, metricValue);
} else {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 52b7d4b..bf71998 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -319,6 +319,9 @@
217 [(log_from_module) = "permissioncontroller"];
PermissionAppsFragmentViewed permission_apps_fragment_viewed =
218 [(log_from_module) = "permissioncontroller"];
+
+ AppCompatibilityChangeReported app_compatibility_change_reported =
+ 228 [(allow_from_any_uid) = true];
}
// Pulled events will start at field 10000.
@@ -6779,3 +6782,39 @@
}
optional Category category = 6;
}
+
+/**
+ * Logs when a compatibility change is affecting an app.
+ *
+ * Logged from:
+ * frameworks/base/core/java/android/app/AppCompatCallbacks.java and
+ * frameworks/base/services/core/java/com/android/server/compat/PlatformCompat.java
+ */
+message AppCompatibilityChangeReported {
+ // The UID of the app being affected by the compatibilty change.
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // The ID of the change affecting the app.
+ optional int64 change_id = 2;
+
+ enum State {
+ UNKNOWN_STATE = 0;
+ ENABLED = 1;
+ DISABLED = 2;
+ LOGGED = 3;
+ }
+
+ // The state of the change - if logged from gating whether it was enabled or disabled, or just
+ // logged otherwise.
+ optional State state = 3;
+
+ enum Source {
+ UNKNOWN_SOURCE = 0;
+ APP_PROCESS = 1;
+ SYSTEM_SERVER = 2;
+ }
+
+ // Where it was logged from.
+ optional Source source = 4;
+
+}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index dd32c08..5cfb123 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -35,6 +35,8 @@
#include "stats_util.h"
#include "statslog.h"
+#include <inttypes.h>
+
using std::set;
using std::string;
using std::unordered_map;
@@ -593,7 +595,7 @@
for (int i = 0; i < config.no_report_metric_size(); ++i) {
const auto no_report_metric = config.no_report_metric(i);
if (metricMap.find(no_report_metric) == metricMap.end()) {
- ALOGW("no_report_metric %lld not exist", no_report_metric);
+ ALOGW("no_report_metric %" PRId64 " not exist", no_report_metric);
return false;
}
noReportMetricIds.insert(no_report_metric);
diff --git a/config/preloaded-classes-blacklist b/config/preloaded-classes-blacklist
index b5f9950..7cfde8a 100644
--- a/config/preloaded-classes-blacklist
+++ b/config/preloaded-classes-blacklist
@@ -5,4 +5,3 @@
android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
android.widget.Magnifier
sun.nio.fs.UnixChannelFactory
-android.permission.PermissionManager
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 17697db..08c97eb 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -19,6 +19,9 @@
import android.compat.Compatibility;
import android.os.Process;
import android.util.Log;
+import android.util.StatsLog;
+
+import com.android.internal.compat.ChangeReporter;
import java.util.Arrays;
@@ -28,10 +31,10 @@
* @hide
*/
public final class AppCompatCallbacks extends Compatibility.Callbacks {
-
private static final String TAG = "Compatibility";
private final long[] mDisabledChanges;
+ private final ChangeReporter mChangeReporter;
/**
* Install this class into the current process.
@@ -45,20 +48,29 @@
private AppCompatCallbacks(long[] disabledChanges) {
mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
Arrays.sort(mDisabledChanges);
+ mChangeReporter = new ChangeReporter();
}
protected void reportChange(long changeId) {
- Log.d(TAG, "Compat change reported: " + changeId + "; UID " + Process.myUid());
- // TODO log via StatsLog
+ reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
}
protected boolean isChangeEnabled(long changeId) {
if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
// Not present in the disabled array
- reportChange(changeId);
+ reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
return true;
}
+ reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
return false;
}
+ private void reportChange(long changeId, int state) {
+ int uid = Process.myUid();
+ //TODO(b/138374585): Implement rate limiting for the logs.
+ Log.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+ mChangeReporter.reportChange(uid, changeId,
+ state, /* source */StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS);
+ }
+
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8987b23..f0b3546 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -883,48 +883,6 @@
}
}
- // /apex/com.android.art/lib, /vendor/lib, /odm/lib and /product/lib
- // are added to the native lib search paths of the classloader.
- // Note that this is done AFTER the classloader is
- // created by ApplicationLoaders.getDefault().getClassLoader(...). The
- // reason is because if we have added the paths when creating the classloader
- // above, the paths are also added to the search path of the linker namespace
- // 'classloader-namespace', which will allow ALL libs in the paths to apps.
- // Since only the libs listed in <partition>/etc/public.libraries.txt can be
- // available to apps, we shouldn't add the paths then.
- //
- // However, we need to add the paths to the classloader (Java) though. This
- // is because when a native lib is requested via System.loadLibrary(), the
- // classloader first tries to find the requested lib in its own native libs
- // search paths. If a lib is not found in one of the paths, dlopen() is not
- // called at all. This can cause a problem that a vendor public native lib
- // is accessible when directly opened via dlopen(), but inaccesible via
- // System.loadLibrary(). In order to prevent the problem, we explicitly
- // add the paths only to the classloader, and not to the native loader
- // (linker namespace).
- List<String> extraLibPaths = new ArrayList<>(4);
- String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
- if (!defaultSearchPaths.contains("/apex/com.android.art/lib")) {
- extraLibPaths.add("/apex/com.android.art/lib" + abiSuffix);
- }
- if (!defaultSearchPaths.contains("/vendor/lib")) {
- extraLibPaths.add("/vendor/lib" + abiSuffix);
- }
- if (!defaultSearchPaths.contains("/odm/lib")) {
- extraLibPaths.add("/odm/lib" + abiSuffix);
- }
- if (!defaultSearchPaths.contains("/product/lib")) {
- extraLibPaths.add("/product/lib" + abiSuffix);
- }
- if (!extraLibPaths.isEmpty()) {
- StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
- try {
- ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, extraLibPaths);
- } finally {
- setThreadPolicy(oldPolicy);
- }
- }
-
if (addedPaths != null && addedPaths.size() > 0) {
final String add = TextUtils.join(File.pathSeparator, addedPaths);
ApplicationLoaders.getDefault().addPath(mDefaultClassLoader, add);
diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java
index dfec4e1..a0aa2de 100644
--- a/core/java/android/bluetooth/BluetoothOutputStream.java
+++ b/core/java/android/bluetooth/BluetoothOutputStream.java
@@ -75,16 +75,4 @@
}
mSocket.write(b, offset, count);
}
-
- /**
- * Wait until the data in sending queue is emptied. A polling version
- * for flush implementation. Use it to ensure the writing data afterwards will
- * be packed in the new RFCOMM frame.
- *
- * @throws IOException if an i/o error occurs.
- * @since Android 4.2.3
- */
- public void flush() throws IOException {
- mSocket.flush();
- }
}
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index a6e3153..760166b 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -515,20 +515,6 @@
return mSocketIS.available();
}
- /**
- * Wait until the data in sending queue is emptied. A polling version
- * for flush implementation. Used to ensure the writing data afterwards will
- * be packed in new RFCOMM frame.
- *
- * @throws IOException if an i/o error occurs.
- */
- @UnsupportedAppUsage
- /*package*/ void flush() throws IOException {
- if (mSocketOS == null) throw new IOException("flush is called on null OutputStream");
- if (VDBG) Log.d(TAG, "flush: " + mSocketOS);
- mSocketOS.flush();
- }
-
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
int ret = 0;
if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index d05ba79..d5dadbf 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -685,6 +685,28 @@
mHotplugEventListeners = new ArrayMap<>();
/**
+ * Listener used to get HDMI Control (CEC) status (enabled/disabled) and the connected display
+ * status.
+ * @hide
+ */
+ public interface HdmiControlStatusChangeListener {
+ /**
+ * Called when HDMI Control (CEC) is enabled/disabled.
+ *
+ * @param isCecEnabled status of HDMI Control
+ * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
+ * @param isCecAvailable status of CEC support of the connected display (the TV).
+ * {@code true} if supported.
+ *
+ * Note: Value of isCecAvailable is only valid when isCecEnabled is true.
+ **/
+ void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
+ }
+
+ private final ArrayMap<HdmiControlStatusChangeListener, IHdmiControlStatusChangeListener>
+ mHdmiControlStatusChangeListeners = new ArrayMap<>();
+
+ /**
* Listener used to get vendor-specific commands.
*/
public interface VendorCommandListener {
@@ -777,4 +799,73 @@
}
};
}
+
+ /**
+ * Adds a listener to get informed of {@link HdmiControlStatusChange}.
+ *
+ * <p>To stop getting the notification,
+ * use {@link #removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener)}.
+ *
+ * @param listener {@link HdmiControlStatusChangeListener} instance
+ * @see HdmiControlManager#removeHdmiControlStatusChangeListener(
+ * HdmiControlStatusChangeListener)
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void addHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
+ if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ return;
+ }
+ if (mHdmiControlStatusChangeListeners.containsKey(listener)) {
+ Log.e(TAG, "listener is already registered");
+ return;
+ }
+ IHdmiControlStatusChangeListener wrappedListener =
+ getHdmiControlStatusChangeListenerWrapper(listener);
+ mHdmiControlStatusChangeListeners.put(listener, wrappedListener);
+ try {
+ mService.addHdmiControlStatusChangeListener(wrappedListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a listener to stop getting informed of {@link HdmiControlStatusChange}.
+ *
+ * @param listener {@link HdmiControlStatusChangeListener} instance to be removed
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
+ if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ return;
+ }
+ IHdmiControlStatusChangeListener wrappedListener =
+ mHdmiControlStatusChangeListeners.remove(listener);
+ if (wrappedListener == null) {
+ Log.e(TAG, "tried to remove not-registered listener");
+ return;
+ }
+ try {
+ mService.removeHdmiControlStatusChangeListener(wrappedListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private IHdmiControlStatusChangeListener getHdmiControlStatusChangeListenerWrapper(
+ final HdmiControlStatusChangeListener listener) {
+ return new IHdmiControlStatusChangeListener.Stub() {
+ @Override
+ public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
+ listener.onStatusChange(isCecEnabled, isCecAvailable);
+ }
+ };
+ }
+
}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 95eaf75..a8fed2b 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -19,6 +19,7 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiDeviceEventListener;
import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.hardware.hdmi.IHdmiInputChangeListener;
@@ -41,6 +42,8 @@
HdmiDeviceInfo getActiveSource();
void oneTouchPlay(IHdmiControlCallback callback);
void queryDisplayStatus(IHdmiControlCallback callback);
+ void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
+ void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
void addHotplugEventListener(IHdmiHotplugEventListener listener);
void removeHotplugEventListener(IHdmiHotplugEventListener listener);
void addDeviceEventListener(IHdmiDeviceEventListener listener);
diff --git a/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl b/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl
new file mode 100644
index 0000000..1407821
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.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.
+ */
+
+package android.hardware.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+
+/**
+ * Callback interface definition for HDMI client to get informed of
+ * the CEC availability change event.
+ *
+ * @hide
+ */
+oneway interface IHdmiControlStatusChangeListener {
+
+ /**
+ * Called when HDMI Control (CEC) is enabled/disabled.
+ *
+ * @param isCecEnabled status of HDMI Control
+ * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
+ * @param isCecAvailable status of CEC support of the connected display (the TV).
+ * {@code true} if supported.
+ *
+ * Note: Value of isCecAvailable is only valid when isCecEnabled is true.
+ **/
+ void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
+}
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index fe7632c..b066a15 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -157,40 +157,6 @@
write_native(b, myFd);
}
}
-
- /**
- * Wait until the data in sending queue is emptied. A polling version
- * for flush implementation.
- * @throws IOException
- * if an i/o error occurs.
- */
- @Override
- public void flush() throws IOException {
- FileDescriptor myFd = fd;
- if (myFd == null) throw new IOException("socket closed");
-
- // Loop until the output buffer is empty.
- Int32Ref pending = new Int32Ref(0);
- while (true) {
- try {
- // See linux/net/unix/af_unix.c
- Os.ioctlInt(myFd, OsConstants.TIOCOUTQ, pending);
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
-
- if (pending.value <= 0) {
- // The output buffer is empty.
- break;
- }
-
- try {
- Thread.sleep(10);
- } catch (InterruptedException ie) {
- break;
- }
- }
- }
}
private native int read_native(FileDescriptor fd) throws IOException;
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 8355e08..2a4576a 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -581,12 +581,16 @@
public static ParcelFileDescriptor fromData(byte[] data, String name) throws IOException {
if (data == null) return null;
MemoryFile file = new MemoryFile(name, data.length);
- if (data.length > 0) {
- file.writeBytes(data, 0, 0, data.length);
+ try {
+ if (data.length > 0) {
+ file.writeBytes(data, 0, 0, data.length);
+ }
+ file.deactivate();
+ FileDescriptor fd = file.getFileDescriptor();
+ return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+ } finally {
+ file.close();
}
- file.deactivate();
- FileDescriptor fd = file.getFileDescriptor();
- return fd != null ? ParcelFileDescriptor.dup(fd) : null;
}
/**
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 29af17a..dd5e20e 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.IUpdateEngine;
import android.os.IUpdateEngineCallback;
@@ -320,8 +321,8 @@
* <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
* {@code headerKeyValuePairs} parameters.
*/
- public void applyPayload(FileDescriptor fd, long offset, long size,
- String[] headerKeyValuePairs) {
+ public void applyPayload(@NonNull FileDescriptor fd, long offset, long size,
+ @NonNull String[] headerKeyValuePairs) {
try {
mUpdateEngine.applyPayloadFd(fd, offset, size, headerKeyValuePairs);
} catch (RemoteException e) {
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 23c54f4..1c78b08 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -72,6 +72,8 @@
* ["android.hidl.manager@1.0", "android.hardware.camera.device@1.0",
* "android.hardware.camera.device@3.2"]. There are no duplicates.
*
+ * For AIDL HALs, the version is stripped away
+ * (e.g. "android.hardware.light").
* @hide
*/
@TestApi
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 383d8db..26cfcd1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14279,8 +14279,7 @@
}
/**
- * Subscription to be used for voice call on a multi sim device. The supported values
- * are 0 = SUB1, 1 = SUB2 and etc.
+ * Subscription Id to be used for voice call on a multi sim device.
* @hide
*/
public static final String MULTI_SIM_VOICE_CALL_SUBSCRIPTION = "multi_sim_voice_call";
@@ -14294,15 +14293,13 @@
public static final String MULTI_SIM_VOICE_PROMPT = "multi_sim_voice_prompt";
/**
- * Subscription to be used for data call on a multi sim device. The supported values
- * are 0 = SUB1, 1 = SUB2 and etc.
+ * Subscription Id to be used for data call on a multi sim device.
* @hide
*/
public static final String MULTI_SIM_DATA_CALL_SUBSCRIPTION = "multi_sim_data_call";
/**
- * Subscription to be used for SMS on a multi sim device. The supported values
- * are 0 = SUB1, 1 = SUB2 and etc.
+ * Subscription Id to be used for SMS on a multi sim device.
* @hide
*/
public static final String MULTI_SIM_SMS_SUBSCRIPTION = "multi_sim_sms";
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index f8b38e9..74cff20 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -62,19 +62,25 @@
}
/**
- * Tries to return a frozen ICU time zone that would have had the specified offset
- * and DST value at the specified moment in the specified country.
- * Returns null if no suitable zone could be found.
+ * Returns a frozen ICU time zone that has / would have had the specified offset and DST value
+ * at the specified moment in the specified country. Returns null if no suitable zone could be
+ * found.
*/
private static android.icu.util.TimeZone getIcuTimeZone(
- int offset, boolean dst, long when, String country) {
- if (country == null) {
+ int offsetMillis, boolean isDst, long whenMillis, String countryIso) {
+ if (countryIso == null) {
return null;
}
android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
- return TimeZoneFinder.getInstance()
- .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias);
+ CountryTimeZones countryTimeZones =
+ TimeZoneFinder.getInstance().lookupCountryTimeZones(countryIso);
+ if (countryTimeZones == null) {
+ return null;
+ }
+ CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias(
+ offsetMillis, isDst, null /* dstOffsetMillis */, whenMillis, bias);
+ return offsetResult != null ? offsetResult.mTimeZone : null;
}
/**
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
new file mode 100644
index 0000000..1ce071b
--- /dev/null
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.compat;
+
+import android.util.StatsLog;
+
+/**
+ * A helper class to report changes to stats log.
+ *
+ * @hide
+ */
+public final class ChangeReporter {
+
+ /**
+ * Transforms StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE enum to a string.
+ *
+ * @param state to transform
+ * @return a string representing the state
+ */
+ private static String stateToString(int state) {
+ switch (state) {
+ case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED:
+ return "LOGGED";
+ case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED:
+ return "ENABLED";
+ case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED:
+ return "DISABLED";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Constructs and returns a string to be logged to logcat when a change is reported.
+ *
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @return string to log
+ */
+ public static String createLogString(int uid, long changeId, int state) {
+ return String.format("Compat change id reported: %d; UID %d; state: %s", changeId, uid,
+ stateToString(state));
+ }
+
+ /**
+ * Report the change to stats log.
+ *
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @param source of the logging - app process or system server
+ */
+ public void reportChange(int uid, long changeId, int state, int source) {
+ //TODO(b/138374585): Implement rate limiting for stats log.
+ StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
+ state, source);
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ad53eb9..c24ffb0 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -479,6 +479,10 @@
closeSocket();
+ if (parsedArgs.mNiceName != null) {
+ Process.setArgV0(parsedArgs.mNiceName);
+ }
+
// End of the postFork event.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
if (parsedArgs.mInvokeWith != null) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2d7ce1a..ea4b63a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -37,6 +37,7 @@
import android.os.UserHandle;
import android.os.ZygoteProcess;
import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
import android.security.keystore.AndroidKeyStoreProvider;
import android.system.ErrnoException;
import android.system.Os;
@@ -459,6 +460,16 @@
ZygoteHooks.gcAndFinalize();
}
+ private static boolean shouldProfileSystemServer() {
+ boolean defaultValue = SystemProperties.getBoolean("dalvik.vm.profilesystemserver",
+ /*default=*/ false);
+ // Can't use DeviceConfig since it's not initialized at this point.
+ return SystemProperties.getBoolean(
+ "persist.device_config." + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT
+ + ".profilesystemserver",
+ defaultValue);
+ }
+
/**
* Finish remaining work for the newly forked system server process.
*/
@@ -481,10 +492,9 @@
}
// Capturing profiles is only supported for debug or eng builds since selinux normally
// prevents it.
- boolean profileSystemServer = SystemProperties.getBoolean(
- "dalvik.vm.profilesystemserver", false);
- if (profileSystemServer && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+ if (shouldProfileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) {
try {
+ Log.d(TAG, "Preparing system server profile");
prepareSystemServerProfile(systemServerClasspath);
} catch (Exception e) {
Log.wtf(TAG, "Failed to set up system server profile", e);
@@ -739,7 +749,7 @@
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
- + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
+ + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
@@ -755,9 +765,7 @@
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);
- boolean profileSystemServer = SystemProperties.getBoolean(
- "dalvik.vm.profilesystemserver", false);
- if (profileSystemServer) {
+ if (shouldProfileSystemServer()) {
parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 0e31ab9..6dd6d45 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -218,10 +218,6 @@
],
include_dirs: [
- // we need to access the private Bionic header
- // <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
- "bionic/libc/private",
-
"external/skia/include/private",
"external/skia/src/codec",
"external/skia/src/core",
@@ -233,6 +229,8 @@
"system/media/private/camera/include",
],
+ header_libs: ["bionic_libc_platform_headers"],
+
static_libs: [
"libasync_safe",
"libgif",
diff --git a/core/jni/android_app_ActivityThread.cpp b/core/jni/android_app_ActivityThread.cpp
index 93f2525..fc88193 100644
--- a/core/jni/android_app_ActivityThread.cpp
+++ b/core/jni/android_app_ActivityThread.cpp
@@ -24,7 +24,7 @@
#include "core_jni_helpers.h"
#include <unistd.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
namespace android {
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
index 076e99d..2ca4500 100644
--- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -23,7 +23,7 @@
#include "core_jni_helpers.h"
#include <android-base/logging.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
#include <utils/Log.h>
#include <utils/String8.h>
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 8a89ed9..f6385cb 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -34,7 +34,7 @@
#include <vector>
#include <android-base/logging.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
#include <debuggerd/client.h>
#include <log/log.h>
#include <utils/misc.h>
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index cbae2da..b6427c9a 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -152,7 +152,7 @@
uint32_t flags,
TransactCallback callback) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- bool isOneway = (flags & TF_ONE_WAY) != 0;
+ bool isOneway = (flags & IBinder::FLAG_ONEWAY) != 0;
ScopedLocalRef<jobject> replyObj(env, nullptr);
sp<JHwParcel> replyContext = nullptr;
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 2d7069c..f929dde 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -644,6 +644,12 @@
return si.totalram;
}
+/*
+ * The outFields array is initialized to -1 to allow the caller to identify
+ * when the status file (and therefore the process) they specified is invalid.
+ * This array should not be overwritten or cleared before we know that the
+ * status file can be read.
+ */
void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileStr,
jobjectArray reqFields, jlongArray outFields)
{
@@ -692,14 +698,14 @@
return;
}
- //ALOGI("Clearing %" PRId32 " sizes", count);
- for (i=0; i<count; i++) {
- sizesArray[i] = 0;
- }
-
int fd = open(file.string(), O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
+ //ALOGI("Clearing %" PRId32 " sizes", count);
+ for (i=0; i<count; i++) {
+ sizesArray[i] = 0;
+ }
+
const size_t BUFFER_SIZE = 4096;
char* buffer = (char*)malloc(BUFFER_SIZE);
int len = read(fd, buffer, BUFFER_SIZE-1);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 82c27f0..f7318b6 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -73,7 +73,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
#include <cutils/ashmem.h>
#include <cutils/fs.h>
#include <cutils/multiuser.h>
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 02eed9a7..6b4c757 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1968,6 +1968,12 @@
<permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows read access to emergency number information for ongoing calls or SMS
+ sessions.
+ @hide Used internally. -->
+ <permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Protects the ability to register any PhoneAccount with
PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. This capability indicates that the PhoneAccount
corresponds to a device SIM.
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index a92c500..f71c8b0 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -70,7 +70,7 @@
<shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" />
<!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf -->
- <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075" />
+ <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075|30047" />
<!-- Chile: 4-5 digits (not confirmed), known premium codes listed -->
<shortcode country="cl" pattern="\\d{4,5}" free="9963|9240" />
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index b749e71..6b02764 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -17,13 +17,12 @@
package android.os;
-import androidx.test.filters.MediumTest;
-
import junit.framework.TestCase;
public class ProcessTest extends TestCase {
- @MediumTest
+ private static final int BAD_PID = 0;
+
public void testProcessGetUidFromName() throws Exception {
assertEquals(android.os.Process.SYSTEM_UID, Process.getUidForName("system"));
assertEquals(Process.BLUETOOTH_UID, Process.getUidForName("bluetooth"));
@@ -35,7 +34,6 @@
Process.getUidForName("u3_a100"));
}
- @MediumTest
public void testProcessGetUidFromNameFailure() throws Exception {
// Failure cases
assertEquals(-1, Process.getUidForName("u2a_foo"));
@@ -47,4 +45,29 @@
assertEquals(-1, Process.getUidForName("u2jhsajhfkjhsafkhskafhkashfkjashfkjhaskjfdhakj3"));
}
+ /**
+ * Tests getUidForPid() by ensuring that it returns the correct value when the process queried
+ * doesn't exist.
+ */
+ public void testGetUidForPidInvalidPid() {
+ assertEquals(-1, Process.getUidForPid(BAD_PID));
+ }
+
+ /**
+ * Tests getParentPid() by ensuring that it returns the correct value when the process queried
+ * doesn't exist.
+ */
+ public void testGetParentPidInvalidPid() {
+ assertEquals(-1, Process.getParentPid(BAD_PID));
+ }
+
+ /**
+ * Tests getThreadGroupLeader() by ensuring that it returns the correct value when the
+ * thread queried doesn't exist.
+ */
+ public void testGetThreadGroupLeaderInvalidTid() {
+ // This function takes a TID instead of a PID but BAD_PID should also be a bad TID.
+ assertEquals(-1, Process.getThreadGroupLeader(BAD_PID));
+ }
+
}
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 0a729a9..2141b81 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -194,6 +194,16 @@
}
@Override
+ public void addHdmiControlStatusChangeListener(
+ final IHdmiControlStatusChangeListener listener) {
+ }
+
+ @Override
+ public void removeHdmiControlStatusChangeListener(
+ final IHdmiControlStatusChangeListener listener) {
+ }
+
+ @Override
public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
}
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 8a16b20..f79c8d3 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -234,9 +234,9 @@
return;
}
dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
- dprintf(fd, "\nVersion: %lld", proto->version_code());
- dprintf(fd, "\nStats since: %lldns", proto->stats_start());
- dprintf(fd, "\nStats end: %lldns", proto->stats_end());
+ dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
+ dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
+ dprintf(fd, "\nStats end: %" PRId64 "ns", proto->stats_end());
auto summary = proto->summary();
dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
index a6af475..42bf03e 100644
--- a/libs/protoutil/include/android/util/ProtoOutputStream.h
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -98,6 +98,7 @@
bool write(uint64_t fieldId, double val);
bool write(uint64_t fieldId, float val);
bool write(uint64_t fieldId, int val);
+ bool write(uint64_t fieldId, long val);
bool write(uint64_t fieldId, long long val);
bool write(uint64_t fieldId, bool val);
bool write(uint64_t fieldId, std::string val);
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
index 6cfa357..ea9b79a 100644
--- a/libs/protoutil/src/ProtoOutputStream.cpp
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -116,6 +116,34 @@
}
bool
+ProtoOutputStream::write(uint64_t fieldId, long val)
+{
+ if (mCompact) return false;
+ const uint32_t id = (uint32_t)fieldId;
+ switch (fieldId & FIELD_TYPE_MASK) {
+ case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
+ case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
+ case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
+ case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
+ case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_ENUM: writeEnumImpl(id, (int)val); break;
+ case FIELD_TYPE_BOOL: writeBoolImpl(id, val != 0); break;
+ default:
+ ALOGW("Field type %d is not supported when writing long val.",
+ (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+ return false;
+ }
+ return true;
+}
+
+bool
ProtoOutputStream::write(uint64_t fieldId, long long val)
{
return internalWrite(fieldId, val, "long long");
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index c49b1e4..84fe27d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -52,7 +52,6 @@
"libandroidfw",
"libhidlallocatorutils",
"libhidlbase",
- "libhidltransport",
"android.hardware.cas@1.0",
"android.hardware.cas.native@1.0",
"android.hidl.memory@1.0",
@@ -147,7 +146,6 @@
"android.hidl.memory@1.0",
"libhidlbase",
"libhidlmemory",
- "libhidltransport",
"libbinderthreadstate",
// MediaPlayer2 implementation
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 4906695..102bbf0 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -951,6 +951,8 @@
ALOGV("process %p channel %d event %s",
this, mChannelID, (event == AudioTrack::EVENT_UNDERRUN) ? "UNDERRUN" :
"BUFFER_END");
+ // Only BUFFER_END should happen as we use static tracks.
+ setVolume_l(0.f, 0.f); // set volume to 0 to indicate no need to ramp volume down.
mSoundPool->addToStopList(this);
} else if (event == AudioTrack::EVENT_LOOP_END) {
ALOGV("End loop %p channel %d", this, mChannelID);
@@ -966,14 +968,18 @@
bool SoundChannel::doStop_l()
{
if (mState != IDLE) {
- setVolume_l(0, 0);
ALOGV("stop");
- // Since we're forcibly halting the previously playing content,
- // we sleep here to ensure the volume is ramped down before we stop the track.
- // Ideally the sleep time is the mixer period, or an approximation thereof
- // (Fast vs Normal tracks are different).
- // TODO: consider pausing instead of stop here.
- std::this_thread::sleep_for(std::chrono::milliseconds(20));
+ if (mLeftVolume != 0.f || mRightVolume != 0.f) {
+ setVolume_l(0.f, 0.f);
+ if (mSoundPool->attributes()->usage != AUDIO_USAGE_GAME) {
+ // Since we're forcibly halting the previously playing content,
+ // we sleep here to ensure the volume is ramped down before we stop the track.
+ // Ideally the sleep time is the mixer period, or an approximation thereof
+ // (Fast vs Normal tracks are different).
+ ALOGV("sleeping: ChannelID:%d SampleID:%d", mChannelID, mSample->sampleID());
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
+ }
+ }
mAudioTrack->stop();
mPrevSampleID = mSample->sampleID();
mSample.clear();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index b324235..1c4b009 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -71,6 +71,7 @@
}
@Test
+ @Ignore
public void testExpansionAndCollapse() throws InterruptedException {
Runnable afterExpand = Mockito.mock(Runnable.class);
mExpandedController.expandFromStack(afterExpand);
@@ -88,6 +89,7 @@
}
@Test
+ @Ignore
public void testOnChildAdded() throws InterruptedException {
expand();
@@ -100,6 +102,7 @@
}
@Test
+ @Ignore
public void testOnChildRemoved() throws InterruptedException {
expand();
@@ -111,6 +114,7 @@
}
@Test
+ @Ignore
public void testBubbleDraggedNotDismissedSnapsBack() throws InterruptedException {
expand();
@@ -128,6 +132,7 @@
}
@Test
+ @Ignore
public void testBubbleDismissed() throws InterruptedException {
expand();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
index f8b32c2..5650e1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
@@ -38,6 +38,7 @@
import com.google.android.collect.Sets;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
@@ -76,6 +77,7 @@
}
@Test
+ @Ignore
public void testHierarchyChanges() throws InterruptedException {
mLayout.setActiveController(mTestableController);
addOneMoreThanBubbleLimitBubbles();
@@ -101,6 +103,7 @@
}
@Test
+ @Ignore
public void testUpdateValueNotChained() throws InterruptedException {
mLayout.setActiveController(mTestableController);
addOneMoreThanBubbleLimitBubbles();
@@ -127,11 +130,13 @@
}
@Test
+ @Ignore
public void testUpdateValueXChained() throws InterruptedException {
testChainedTranslationAnimations();
}
@Test
+ @Ignore
public void testSetEndActions() throws InterruptedException {
mLayout.setActiveController(mTestableController);
addOneMoreThanBubbleLimitBubbles();
@@ -175,6 +180,7 @@
}
@Test
+ @Ignore
public void testRemoveEndListeners() throws InterruptedException {
mLayout.setActiveController(mTestableController);
addOneMoreThanBubbleLimitBubbles();
@@ -213,6 +219,7 @@
}
@Test
+ @Ignore
public void testSetController() throws InterruptedException {
// Add the bubbles, then set the controller, to make sure that a controller added to an
// already-initialized view works correctly.
@@ -269,6 +276,7 @@
}
@Test
+ @Ignore
public void testArePropertiesAnimating() throws InterruptedException {
mLayout.setActiveController(mTestableController);
addOneMoreThanBubbleLimitBubbles();
@@ -293,6 +301,7 @@
}
@Test
+ @Ignore
public void testCancelAllAnimations() throws InterruptedException {
mLayout.setActiveController(mTestableController);
addOneMoreThanBubbleLimitBubbles();
@@ -346,6 +355,7 @@
}
@Test
+ @Ignore
public void testPhysicsAnimator() throws InterruptedException {
mLayout.setActiveController(mTestableController);
addOneMoreThanBubbleLimitBubbles();
@@ -390,6 +400,7 @@
}
@Test
+ @Ignore
public void testAnimationsForChildrenFromIndex() throws InterruptedException {
// Don't chain since we're going to invoke each animation independently.
mTestableController.setChainedProperties(new HashSet<>());
@@ -415,6 +426,7 @@
}
@Test
+ @Ignore
public void testAnimationsForChildrenFromIndex_noChildren() {
mLayout.setActiveController(mTestableController);
diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java
index 1517887..997178e 100644
--- a/services/core/java/com/android/server/RecoverySystemService.java
+++ b/services/core/java/com/android/server/RecoverySystemService.java
@@ -25,15 +25,12 @@
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.system.ErrnoException;
-import android.system.Os;
import android.util.Slog;
import libcore.io.IoUtils;
import java.io.DataInputStream;
import java.io.DataOutputStream;
-import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
@@ -288,7 +285,6 @@
byte[] cmdUtf8 = command.getBytes("UTF-8");
dos.writeInt(cmdUtf8.length);
dos.write(cmdUtf8, 0, cmdUtf8.length);
- dos.flush();
}
// Read the status from the socket.
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 2c2013c..884e7a5 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -19,9 +19,6 @@
"exclude-filter": "android.app.cts.AlarmManagerTest#testSetRepeating"
},
{
- "exclude-filter": "android.app.cts.ServiceTest#testAppZygoteServices"
- },
- {
"exclude-filter": "android.app.cts.SystemFeaturesTest#testLocationFeatures"
},
{
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 884ecba..6010b1dc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -15,9 +15,6 @@
*/
package com.android.server.audio;
-import static com.android.server.audio.AudioService.CONNECTION_STATE_CONNECTED;
-import static com.android.server.audio.AudioService.CONNECTION_STATE_DISCONNECTED;
-
import android.annotation.NonNull;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothDevice;
@@ -40,9 +37,11 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
+import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;
+import java.io.PrintWriter;
/** @hide */
/*package*/ final class AudioDeviceBroker {
@@ -93,13 +92,28 @@
/*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
mContext = context;
mAudioService = service;
- setupMessaging(context);
mBtHelper = new BtHelper(this);
mDeviceInventory = new AudioDeviceInventory(this);
+ init();
+ }
+
+ /** for test purposes only, inject AudioDeviceInventory */
+ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
+ @NonNull AudioDeviceInventory mockDeviceInventory) {
+ mContext = context;
+ mAudioService = service;
+ mBtHelper = new BtHelper(this);
+ mDeviceInventory = mockDeviceInventory;
+
+ init();
+ }
+
+ private void init() {
+ setupMessaging(mContext);
+
mForcedUseForComm = AudioSystem.FORCE_NONE;
mForcedUseForCommExt = mForcedUseForComm;
-
}
/*package*/ Context getContext() {
@@ -123,6 +137,8 @@
/*package*/ void onAudioServerDied() {
// Restore forced usage for communications and record
synchronized (mDeviceStateLock) {
+ AudioSystem.setParameters(
+ "BT_SCO=" + (mForcedUseForComm == AudioSystem.FORCE_BT_SCO ? "on" : "off"));
onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
}
@@ -228,17 +244,42 @@
mSupprNoisy = suppressNoisyIntent;
mVolume = vol;
}
+
+ // redefine equality op so we can match messages intended for this device
+ @Override
+ public boolean equals(Object o) {
+ return mDevice.equals(o);
+ }
}
+
/*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
int profile, boolean suppressNoisyIntent, int a2dpVolume) {
final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile,
suppressNoisyIntent, a2dpVolume);
- // TODO add a check to try to remove unprocessed messages for the same device (the old
- // check didn't work), and make sure it doesn't conflict with config change message
- sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
+ // when receiving a request to change the connection state of a device, this last request
+ // is the source of truth, so cancel all previous requests
+ removeAllA2dpConnectionEvents(device);
+
+ sendLMsgNoDelay(
+ state == BluetoothProfile.STATE_CONNECTED
+ ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
+ : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
+ SENDMSG_QUEUE, info);
+ }
+
+ /** remove all previously scheduled connection and disconnection events for the given device */
+ private void removeAllA2dpConnectionEvents(@NonNull BluetoothDevice device) {
+ mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
+ device);
+ mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
+ device);
+ mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
+ device);
+ mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+ device);
}
private static final class HearingAidDeviceConnectionInfo {
@@ -426,13 +467,16 @@
sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
}
- /*package*/ void postA2dpSinkConnection(int state,
+ /*package*/ void postA2dpSinkConnection(@AudioService.BtProfileConnectionState int state,
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
- sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+ sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
+ ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
+ : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+ SENDMSG_QUEUE,
state, btDeviceInfo, delay);
}
- /*package*/ void postA2dpSourceConnection(int state,
+ /*package*/ void postA2dpSourceConnection(@AudioService.BtProfileConnectionState int state,
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
state, btDeviceInfo, delay);
@@ -518,25 +562,6 @@
}
}
- @GuardedBy("mDeviceStateLock")
- /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state,
- @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
- final int intState = (state == BluetoothA2dp.STATE_CONNECTED)
- ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED;
- final int delay = mDeviceInventory.checkSendBecomingNoisyIntent(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
- AudioSystem.DEVICE_NONE);
- final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress();
-
- if (AudioService.DEBUG_DEVICES) {
- Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo
- + " state= " + state
- + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock());
- }
- sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
- state, btDeviceInfo, delay);
- }
-
/*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
@@ -571,8 +596,10 @@
// must be called synchronized on mConnectedDevices
/*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
- return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
- new BtHelper.BluetoothA2dpDeviceInfo(btDevice));
+ return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
+ new BtHelper.BluetoothA2dpDeviceInfo(btDevice))
+ || mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+ new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
}
/*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
@@ -597,6 +624,15 @@
}
}
+ /*package*/ void dump(PrintWriter pw, String prefix) {
+ if (mBrokerHandler != null) {
+ pw.println(prefix + "Message handler (watch for unhandled messages):");
+ mBrokerHandler.dump(new PrintWriterPrinter(pw), prefix + " ");
+ } else {
+ pw.println("Message handler is null");
+ }
+ }
+
//---------------------------------------------------------------------
// Internal handling of messages
// These methods are ALL synchronous, in response to message handling in BrokerHandler
@@ -698,7 +734,8 @@
mDeviceInventory.onReportNewRoutes();
}
break;
- case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
synchronized (mDeviceStateLock) {
mDeviceInventory.onSetA2dpSinkConnectionState(
(BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
@@ -823,7 +860,8 @@
}
}
break;
- case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: {
+ case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
+ case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: {
final BtDeviceConnectionInfo info = (BtDeviceConnectionInfo) msg.obj;
AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
"setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent "
@@ -874,7 +912,7 @@
private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
private static final int MSG_IIL_SET_FORCE_USE = 4;
private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
- private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6;
+ private static final int MSG_TOGGLE_HDMI = 6;
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;
@@ -885,7 +923,6 @@
private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
private static final int MSG_I_DISCONNECT_BT_SCO = 16;
- private static final int MSG_TOGGLE_HDMI = 17;
private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
private static final int MSG_DISCONNECT_A2DP = 19;
private static final int MSG_DISCONNECT_A2DP_SINK = 20;
@@ -895,25 +932,30 @@
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK = 24;
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID = 25;
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET = 26;
+ private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED = 27;
+ private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED = 28;
// process external command to (dis)connect an A2DP device
- private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 27;
+ private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION = 29;
+ private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION = 30;
// process external command to (dis)connect a hearing aid device
- private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 28;
+ private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31;
// a ScoClient died in BtHelper
- private static final int MSG_L_SCOCLIENT_DIED = 29;
+ private static final int MSG_L_SCOCLIENT_DIED = 32;
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
- case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
+ 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_L_A2DP_DEVICE_CONFIG_CHANGE:
case MSG_TOGGLE_HDMI:
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
- case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
+ case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
+ case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION:
case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
return true;
default:
@@ -994,7 +1036,8 @@
switch (msg) {
case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
- case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
+ 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:
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index a9a8ef2..90973a8 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -41,14 +41,16 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
/**
* Class to manage the inventory of all connected devices.
* This class is thread-safe.
+ * (non final for mocking/spying)
*/
-public final class AudioDeviceInventory {
+public class AudioDeviceInventory {
private static final String TAG = "AS.AudioDeviceInventory";
@@ -56,11 +58,7 @@
// Key for map created from DeviceInfo.makeDeviceListKey()
private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
- private final @NonNull AudioDeviceBroker mDeviceBroker;
-
- AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
- mDeviceBroker = broker;
- }
+ private @NonNull AudioDeviceBroker mDeviceBroker;
// cache of the address of the last dock the device was connected to
private String mDockAddress;
@@ -70,6 +68,20 @@
final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
new RemoteCallbackList<IAudioRoutesObserver>();
+ /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
+ mDeviceBroker = broker;
+ }
+
+ //-----------------------------------------------------------
+ /** for mocking only */
+ /*package*/ AudioDeviceInventory() {
+ mDeviceBroker = null;
+ }
+
+ /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
+ mDeviceBroker = broker;
+ }
+
//------------------------------------------------------------
/**
* Class to store info about connected devices.
@@ -146,8 +158,10 @@
}
}
+ // only public for mocking/spying
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
+ @VisibleForTesting
+ public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
@AudioService.BtProfileConnectionState int state) {
final BluetoothDevice btDevice = btInfo.getBtDevice();
int a2dpVolume = btInfo.getVolume();
@@ -159,30 +173,40 @@
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = "";
}
- AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
- "A2DP sink connected: device addr=" + address + " state=" + state
- + " vol=" + a2dpVolume));
final int a2dpCodec = btInfo.getCodec();
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2DP sink connected: device addr=" + address + " state=" + state
+ + " codec=" + a2dpCodec
+ + " vol=" + a2dpVolume));
+
synchronized (mConnectedDevices) {
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
btDevice.getAddress());
final DeviceInfo di = mConnectedDevices.get(key);
boolean isConnected = di != null;
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- if (btDevice.isBluetoothDock()) {
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- // introduction of a delay for transient disconnections of docks when
- // power is rapidly turned off/on, this message will be canceled if
- // we reconnect the dock under a preset delay
- makeA2dpDeviceUnavailableLater(address,
- AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
- // the next time isConnected is evaluated, it will be false for the dock
+ if (isConnected) {
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ // device is already connected, but we are receiving a connection again,
+ // it could be for a codec change
+ if (a2dpCodec != di.mDeviceCodecFormat) {
+ mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice);
}
} else {
- makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+ if (btDevice.isBluetoothDock()) {
+ if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ // introduction of a delay for transient disconnections of docks when
+ // power is rapidly turned off/on, this message will be canceled if
+ // we reconnect the dock under a preset delay
+ makeA2dpDeviceUnavailableLater(address,
+ AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
+ // the next time isConnected is evaluated, it will be false for the dock
+ }
+ } else {
+ makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+ }
}
} else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
if (btDevice.isBluetoothDock()) {
@@ -282,11 +306,9 @@
+ " event=" + BtHelper.a2dpDeviceEventToString(event)));
synchronized (mConnectedDevices) {
- //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
- // for this type of message
if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
- "A2dp config change ignored"));
+ "A2dp config change ignored (scheduled connection change)"));
return;
}
final String key = DeviceInfo.makeDeviceListKey(
@@ -534,8 +556,10 @@
return mCurAudioRoutes;
}
+ // only public for mocking/spying
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- /*package*/ void setBluetoothA2dpDeviceConnectionState(
+ @VisibleForTesting
+ public void setBluetoothA2dpDeviceConnectionState(
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
int delay;
@@ -544,9 +568,12 @@
}
synchronized (mConnectedDevices) {
if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
- int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+ @AudioService.ConnectionState int asState =
+ (state == BluetoothA2dp.STATE_CONNECTED)
+ ? AudioService.CONNECTION_STATE_CONNECTED
+ : AudioService.CONNECTION_STATE_DISCONNECTED;
delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- intState, musicDevice);
+ asState, musicDevice);
} else {
delay = 0;
}
@@ -785,7 +812,7 @@
return 0;
}
mDeviceBroker.postBroadcastBecomingNoisy();
- delay = 1000;
+ delay = AudioService.BECOMING_NOISY_DELAY_MS;
}
return delay;
@@ -943,4 +970,21 @@
intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
}
}
+
+ //----------------------------------------------------------
+ // For tests only
+
+ /**
+ * Check if device is in the list of connected devices
+ * @param device
+ * @return true if connected
+ */
+ @VisibleForTesting
+ public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
+ final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ device.getAddress());
+ synchronized (mConnectedDevices) {
+ return (mConnectedDevices.get(key) != null);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5628f94..066e765 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -54,8 +54,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.hardware.hdmi.HdmiAudioSystemClient;
import android.hardware.hdmi.HdmiControlManager;
@@ -82,11 +80,7 @@
import android.media.IVolumeController;
import android.media.MediaExtractor;
import android.media.MediaFormat;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
import android.media.PlayerBase;
-import android.media.SoundPool;
import android.media.VolumePolicy;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
@@ -102,7 +96,6 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -127,6 +120,7 @@
import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
+import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseIntArray;
import android.view.KeyEvent;
@@ -134,9 +128,9 @@
import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -145,15 +139,11 @@
import com.android.server.pm.UserManagerService;
import com.android.server.wm.ActivityTaskManagerInternal;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -201,6 +191,13 @@
private static final int UNMUTE_STREAM_DELAY = 350;
/**
+ * Delay before disconnecting a device that would cause BECOMING_NOISY intent to be sent,
+ * to give a chance to applications to pause.
+ */
+ @VisibleForTesting
+ public static final int BECOMING_NOISY_DELAY_MS = 1000;
+
+ /**
* Only used in the result from {@link #checkForRingerModeChange(int, int, int)}
*/
private static final int FLAG_ADJUST_VOLUME = 1;
@@ -293,19 +290,6 @@
// protects mRingerMode
private final Object mSettingsLock = new Object();
- private SoundPool mSoundPool;
- private final Object mSoundEffectsLock = new Object();
- private static final int NUM_SOUNDPOOL_CHANNELS = 4;
-
- /* Sound effect file names */
- private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
- private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>();
-
- /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to
- * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect
- * uses soundpool (second column) */
- private final int[][] SOUND_EFFECT_FILES_MAP = new int[AudioManager.NUM_SOUND_EFFECTS][2];
-
/** Maximum volume index values for audio streams */
protected static int[] MAX_STREAM_VOLUME = new int[] {
5, // STREAM_VOICE_CALL
@@ -452,6 +436,9 @@
* @see System#MUTE_STREAMS_AFFECTED */
private int mMuteAffectedStreams;
+ @NonNull
+ private SoundEffectsHelper mSfxHelper;
+
/**
* NOTE: setVibrateSetting(), getVibrateSetting(), shouldVibrate() are deprecated.
* mVibrateSetting is just maintained during deprecation period but vibration policy is
@@ -492,14 +479,6 @@
private boolean mSystemReady;
// true if Intent.ACTION_USER_SWITCHED has ever been received
private boolean mUserSwitchedReceived;
- // listener for SoundPool sample load completion indication
- private SoundPoolCallback mSoundPoolCallBack;
- // thread for SoundPool listener
- private SoundPoolListenerThread mSoundPoolListenerThread;
- // message looper for SoundPool listener
- private Looper mSoundPoolLooper = null;
- // volume applied to sound played with playSoundEffect()
- private static int sSoundEffectVolumeDb;
// previous volume adjustment direction received by checkForRingerModeChange()
private int mPrevVolDirection = AudioManager.ADJUST_SAME;
// mVolumeControlStream is set by VolumePanel to temporarily force the stream type which volume
@@ -641,6 +620,8 @@
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
+ mSfxHelper = new SoundEffectsHelper(mContext);
+
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
@@ -731,9 +712,6 @@
MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM];
}
- sSoundEffectVolumeDb = context.getResources().getInteger(
- com.android.internal.R.integer.config_soundEffectVolumeDb);
-
createAudioSystemThread();
AudioSystem.setErrorCallback(mAudioSystemCallback);
@@ -867,7 +845,12 @@
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
synchronized (mHdmiClientLock) {
+ mHdmiCecSink = false;
mHdmiManager = mContext.getSystemService(HdmiControlManager.class);
+ if (mHdmiManager != null) {
+ mHdmiManager.addHdmiControlStatusChangeListener(
+ mHdmiControlStatusChangeListenerCallback);
+ }
mHdmiTvClient = mHdmiManager.getTvClient();
if (mHdmiTvClient != null) {
mFixedVolumeDevices &= ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER;
@@ -878,7 +861,6 @@
mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
}
- mHdmiCecSink = false;
mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient();
}
}
@@ -1135,8 +1117,7 @@
checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, caller);
synchronized (mHdmiClientLock) {
if (mHdmiManager != null && mHdmiPlaybackClient != null) {
- mHdmiCecSink = false;
- mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+ updateHdmiCecSinkLocked(mHdmiCecSink | false);
}
}
}
@@ -1146,7 +1127,7 @@
if (isPlatformTelevision()) {
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
- mHdmiCecSink = false;
+ updateHdmiCecSinkLocked(mHdmiCecSink | false);
}
}
}
@@ -1916,24 +1897,17 @@
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
final long ident = Binder.clearCallingIdentity();
try {
- mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
- mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
+ mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, true);
+ mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, false);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
- if (mHdmiAudioSystemClient != null &&
- mHdmiSystemAudioSupported &&
- streamTypeAlias == AudioSystem.STREAM_MUSIC &&
- (oldIndex != newIndex || isMuteAdjust)) {
- final long identity = Binder.clearCallingIdentity();
- mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
- isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC),
- getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
- isStreamMute(AudioSystem.STREAM_MUSIC));
- Binder.restoreCallingIdentity(identity);
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+ && (oldIndex != newIndex || isMuteAdjust)) {
+ maybeSendSystemAudioStatusCommand(isMuteAdjust);
}
}
}
@@ -1944,12 +1918,35 @@
// Called after a delay when volume down is pressed while muted
private void onUnmuteStream(int stream, int flags) {
- VolumeStreamState streamState = mStreamStates[stream];
- streamState.mute(false);
+ boolean wasMuted;
+ synchronized (VolumeStreamState.class) {
+ final VolumeStreamState streamState = mStreamStates[stream];
+ wasMuted = streamState.mute(false); // if unmuting causes a change, it was muted
- final int device = getDeviceForStream(stream);
- final int index = mStreamStates[stream].getIndex(device);
- sendVolumeUpdate(stream, index, index, flags, device);
+ final int device = getDeviceForStream(stream);
+ final int index = streamState.getIndex(device);
+ sendVolumeUpdate(stream, index, index, flags, device);
+ }
+ if (stream == AudioSystem.STREAM_MUSIC && wasMuted) {
+ synchronized (mHdmiClientLock) {
+ maybeSendSystemAudioStatusCommand(true);
+ }
+ }
+ }
+
+ @GuardedBy("mHdmiClientLock")
+ private void maybeSendSystemAudioStatusCommand(boolean isMuteAdjust) {
+ if (mHdmiAudioSystemClient == null
+ || !mHdmiSystemAudioSupported) {
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
+ isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC),
+ getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
+ isStreamMute(AudioSystem.STREAM_MUSIC));
+ Binder.restoreCallingIdentity(identity);
}
private void setSystemAudioVolume(int oldVolume, int newVolume, int maxVolume, int flags) {
@@ -2362,17 +2359,9 @@
}
}
synchronized (mHdmiClientLock) {
- if (mHdmiManager != null &&
- mHdmiAudioSystemClient != null &&
- mHdmiSystemAudioSupported &&
- streamTypeAlias == AudioSystem.STREAM_MUSIC &&
- (oldIndex != index)) {
- final long identity = Binder.clearCallingIdentity();
- mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
- false, getStreamVolume(AudioSystem.STREAM_MUSIC),
- getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
- isStreamMute(AudioSystem.STREAM_MUSIC));
- Binder.restoreCallingIdentity(identity);
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+ && (oldIndex != index)) {
+ maybeSendSystemAudioStatusCommand(false);
}
}
sendVolumeUpdate(streamType, oldIndex, index, flags, device);
@@ -2542,15 +2531,11 @@
mVolumeController.postVolumeChanged(streamType, flags);
}
- // If Hdmi-CEC system audio mode is on, we show volume bar only when TV
- // receives volume notification from Audio Receiver.
+ // If Hdmi-CEC system audio mode is on and we are a TV panel, never show volume bar.
private int updateFlagsForTvPlatform(int flags) {
synchronized (mHdmiClientLock) {
- if (mHdmiTvClient != null) {
- if (mHdmiSystemAudioSupported &&
- ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) {
- flags &= ~AudioManager.FLAG_SHOW_UI;
- }
+ if (mHdmiTvClient != null && mHdmiSystemAudioSupported) {
+ flags &= ~AudioManager.FLAG_SHOW_UI;
}
}
return flags;
@@ -3372,104 +3357,30 @@
//==========================================================================================
// Sound Effects
//==========================================================================================
+ private static final class LoadSoundEffectReply
+ implements SoundEffectsHelper.OnEffectsLoadCompleteHandler {
+ private static final int SOUND_EFFECTS_LOADING = 1;
+ private static final int SOUND_EFFECTS_LOADED = 0;
+ private static final int SOUND_EFFECTS_ERROR = -1;
+ private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 5000;
- private static final String TAG_AUDIO_ASSETS = "audio_assets";
- private static final String ATTR_VERSION = "version";
- private static final String TAG_GROUP = "group";
- private static final String ATTR_GROUP_NAME = "name";
- private static final String TAG_ASSET = "asset";
- private static final String ATTR_ASSET_ID = "id";
- private static final String ATTR_ASSET_FILE = "file";
+ private int mStatus = SOUND_EFFECTS_LOADING;
- private static final String ASSET_FILE_VERSION = "1.0";
- private static final String GROUP_TOUCH_SOUNDS = "touch_sounds";
-
- private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 5000;
-
- class LoadSoundEffectReply {
- public int mStatus = 1;
- };
-
- private void loadTouchSoundAssetDefaults() {
- SOUND_EFFECT_FILES.add("Effect_Tick.ogg");
- for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) {
- SOUND_EFFECT_FILES_MAP[i][0] = 0;
- SOUND_EFFECT_FILES_MAP[i][1] = -1;
- }
- }
-
- private void loadTouchSoundAssets() {
- XmlResourceParser parser = null;
-
- // only load assets once.
- if (!SOUND_EFFECT_FILES.isEmpty()) {
- return;
+ @Override
+ public synchronized void run(boolean success) {
+ mStatus = success ? SOUND_EFFECTS_LOADED : SOUND_EFFECTS_ERROR;
+ notify();
}
- loadTouchSoundAssetDefaults();
-
- try {
- parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets);
-
- XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS);
- String version = parser.getAttributeValue(null, ATTR_VERSION);
- boolean inTouchSoundsGroup = false;
-
- if (ASSET_FILE_VERSION.equals(version)) {
- while (true) {
- XmlUtils.nextElement(parser);
- String element = parser.getName();
- if (element == null) {
- break;
- }
- if (element.equals(TAG_GROUP)) {
- String name = parser.getAttributeValue(null, ATTR_GROUP_NAME);
- if (GROUP_TOUCH_SOUNDS.equals(name)) {
- inTouchSoundsGroup = true;
- break;
- }
- }
- }
- while (inTouchSoundsGroup) {
- XmlUtils.nextElement(parser);
- String element = parser.getName();
- if (element == null) {
- break;
- }
- if (element.equals(TAG_ASSET)) {
- String id = parser.getAttributeValue(null, ATTR_ASSET_ID);
- String file = parser.getAttributeValue(null, ATTR_ASSET_FILE);
- int fx;
-
- try {
- Field field = AudioManager.class.getField(id);
- fx = field.getInt(null);
- } catch (Exception e) {
- Log.w(TAG, "Invalid touch sound ID: "+id);
- continue;
- }
-
- int i = SOUND_EFFECT_FILES.indexOf(file);
- if (i == -1) {
- i = SOUND_EFFECT_FILES.size();
- SOUND_EFFECT_FILES.add(file);
- }
- SOUND_EFFECT_FILES_MAP[fx][0] = i;
- } else {
- break;
- }
+ public synchronized boolean waitForLoaded(int attempts) {
+ while ((mStatus == SOUND_EFFECTS_LOADING) && (attempts-- > 0)) {
+ try {
+ wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting sound pool loaded.");
}
}
- } catch (Resources.NotFoundException e) {
- Log.w(TAG, "audio assets file not found", e);
- } catch (XmlPullParserException e) {
- Log.w(TAG, "XML parser exception reading touch sound assets", e);
- } catch (IOException e) {
- Log.w(TAG, "I/O exception reading touch sound assets", e);
- } finally {
- if (parser != null) {
- parser.close();
- }
+ return mStatus == SOUND_EFFECTS_LOADED;
}
}
@@ -3499,20 +3410,9 @@
* This method must be called at first when sound effects are enabled
*/
public boolean loadSoundEffects() {
- int attempts = 3;
LoadSoundEffectReply reply = new LoadSoundEffectReply();
-
- synchronized (reply) {
- sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
- while ((reply.mStatus == 1) && (attempts-- > 0)) {
- try {
- reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
- } catch (InterruptedException e) {
- Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");
- }
- }
- }
- return (reply.mStatus == 0);
+ sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
+ return reply.waitForLoaded(3 /*attempts*/);
}
/**
@@ -3532,61 +3432,6 @@
sendMsg(mAudioHandler, MSG_UNLOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0);
}
- class SoundPoolListenerThread extends Thread {
- public SoundPoolListenerThread() {
- super("SoundPoolListenerThread");
- }
-
- @Override
- public void run() {
-
- Looper.prepare();
- mSoundPoolLooper = Looper.myLooper();
-
- synchronized (mSoundEffectsLock) {
- if (mSoundPool != null) {
- mSoundPoolCallBack = new SoundPoolCallback();
- mSoundPool.setOnLoadCompleteListener(mSoundPoolCallBack);
- }
- mSoundEffectsLock.notify();
- }
- Looper.loop();
- }
- }
-
- private final class SoundPoolCallback implements
- android.media.SoundPool.OnLoadCompleteListener {
-
- int mStatus = 1; // 1 means neither error nor last sample loaded yet
- List<Integer> mSamples = new ArrayList<Integer>();
-
- public int status() {
- return mStatus;
- }
-
- public void setSamples(int[] samples) {
- for (int i = 0; i < samples.length; i++) {
- // do not wait ack for samples rejected upfront by SoundPool
- if (samples[i] > 0) {
- mSamples.add(samples[i]);
- }
- }
- }
-
- public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
- synchronized (mSoundEffectsLock) {
- int i = mSamples.indexOf(sampleId);
- if (i >= 0) {
- mSamples.remove(i);
- }
- if ((status != 0) || mSamples. isEmpty()) {
- mStatus = status;
- mSoundEffectsLock.notify();
- }
- }
- }
- }
-
/** @see AudioManager#reloadAudioSettings() */
public void reloadAudioSettings() {
readAudioSettings(false /*userSwitch*/);
@@ -4124,7 +3969,9 @@
|| adjust == AudioManager.ADJUST_TOGGLE_MUTE;
}
- /*package*/ boolean isInCommunication() {
+ /** only public for mocking/spying, do not call outside of AudioService */
+ @VisibleForTesting
+ public boolean isInCommunication() {
boolean IsInCall = false;
TelecomManager telecomManager =
@@ -4293,7 +4140,9 @@
return false;
}
- /*package*/ int getDeviceForStream(int stream) {
+ /** only public for mocking/spying, do not call outside of AudioService */
+ @VisibleForTesting
+ public int getDeviceForStream(int stream) {
int device = getDevicesForStream(stream);
if ((device & (device - 1)) != 0) {
// Multiple device selection is either:
@@ -4338,7 +4187,9 @@
}
}
- /*package*/ void postObserveDevicesForAllStreams() {
+ /** only public for mocking/spying, do not call outside of AudioService */
+ @VisibleForTesting
+ public void postObserveDevicesForAllStreams() {
sendMsg(mAudioHandler,
MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS,
SENDMSG_QUEUE, 0 /*arg1*/, 0 /*arg2*/, null /*obj*/,
@@ -4449,7 +4300,9 @@
AudioSystem.DEVICE_OUT_ALL_USB |
AudioSystem.DEVICE_OUT_HDMI;
- /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) {
+ /** only public for mocking/spying, do not call outside of AudioService */
+ @VisibleForTesting
+ public void postAccessoryPlugMediaUnmute(int newDevice) {
sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
newDevice, 0, null, 0);
}
@@ -4838,7 +4691,12 @@
}
}
- public void mute(boolean state) {
+ /**
+ * Mute/unmute the stream
+ * @param state the new mute state
+ * @return true if the mute state was changed
+ */
+ public boolean mute(boolean state) {
boolean changed = false;
synchronized (VolumeStreamState.class) {
if (state != mIsMuted) {
@@ -4863,6 +4721,7 @@
intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
sendBroadcastToAll(intent);
}
+ return changed;
}
public int getStreamType() {
@@ -4999,7 +4858,9 @@
}
}
- /*package*/ void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device,
+ /** only public for mocking/spying, do not call outside of AudioService */
+ @VisibleForTesting
+ public void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device,
String caller) {
sendMsg(mAudioHandler,
MSG_SET_DEVICE_STREAM_VOLUME,
@@ -5107,230 +4968,6 @@
Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode);
}
- private String getSoundEffectFilePath(int effectType) {
- String filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH
- + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
- if (!new File(filePath).isFile()) {
- filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH
- + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
- }
- return filePath;
- }
-
- private boolean onLoadSoundEffects() {
- int status;
-
- synchronized (mSoundEffectsLock) {
- if (!mSystemReady) {
- Log.w(TAG, "onLoadSoundEffects() called before boot complete");
- return false;
- }
-
- if (mSoundPool != null) {
- return true;
- }
-
- loadTouchSoundAssets();
-
- mSoundPool = new SoundPool.Builder()
- .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
- .setAudioAttributes(new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build())
- .build();
- mSoundPoolCallBack = null;
- mSoundPoolListenerThread = new SoundPoolListenerThread();
- mSoundPoolListenerThread.start();
- int attempts = 3;
- while ((mSoundPoolCallBack == null) && (attempts-- > 0)) {
- try {
- // Wait for mSoundPoolCallBack to be set by the other thread
- mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
- } catch (InterruptedException e) {
- Log.w(TAG, "Interrupted while waiting sound pool listener thread.");
- }
- }
-
- if (mSoundPoolCallBack == null) {
- Log.w(TAG, "onLoadSoundEffects() SoundPool listener or thread creation error");
- if (mSoundPoolLooper != null) {
- mSoundPoolLooper.quit();
- mSoundPoolLooper = null;
- }
- mSoundPoolListenerThread = null;
- mSoundPool.release();
- mSoundPool = null;
- return false;
- }
- /*
- * poolId table: The value -1 in this table indicates that corresponding
- * file (same index in SOUND_EFFECT_FILES[] has not been loaded.
- * Once loaded, the value in poolId is the sample ID and the same
- * sample can be reused for another effect using the same file.
- */
- int[] poolId = new int[SOUND_EFFECT_FILES.size()];
- for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
- poolId[fileIdx] = -1;
- }
- /*
- * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.
- * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:
- * this indicates we have a valid sample loaded for this effect.
- */
-
- int numSamples = 0;
- for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
- // Do not load sample if this effect uses the MediaPlayer
- if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
- continue;
- }
- if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
- String filePath = getSoundEffectFilePath(effect);
- int sampleId = mSoundPool.load(filePath, 0);
- if (sampleId <= 0) {
- Log.w(TAG, "Soundpool could not load file: "+filePath);
- } else {
- SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
- poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
- numSamples++;
- }
- } else {
- SOUND_EFFECT_FILES_MAP[effect][1] =
- poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
- }
- }
- // wait for all samples to be loaded
- if (numSamples > 0) {
- mSoundPoolCallBack.setSamples(poolId);
-
- attempts = 3;
- status = 1;
- while ((status == 1) && (attempts-- > 0)) {
- try {
- mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
- status = mSoundPoolCallBack.status();
- } catch (InterruptedException e) {
- Log.w(TAG, "Interrupted while waiting sound pool callback.");
- }
- }
- } else {
- status = -1;
- }
-
- if (mSoundPoolLooper != null) {
- mSoundPoolLooper.quit();
- mSoundPoolLooper = null;
- }
- mSoundPoolListenerThread = null;
- if (status != 0) {
- Log.w(TAG,
- "onLoadSoundEffects(), Error "+status+ " while loading samples");
- for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
- if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) {
- SOUND_EFFECT_FILES_MAP[effect][1] = -1;
- }
- }
-
- mSoundPool.release();
- mSoundPool = null;
- }
- }
- return (status == 0);
- }
-
- /**
- * Unloads samples from the sound pool.
- * This method can be called to free some memory when
- * sound effects are disabled.
- */
- private void onUnloadSoundEffects() {
- synchronized (mSoundEffectsLock) {
- if (mSoundPool == null) {
- return;
- }
-
- int[] poolId = new int[SOUND_EFFECT_FILES.size()];
- for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
- poolId[fileIdx] = 0;
- }
-
- for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
- if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) {
- continue;
- }
- if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) {
- mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]);
- SOUND_EFFECT_FILES_MAP[effect][1] = -1;
- poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1;
- }
- }
- mSoundPool.release();
- mSoundPool = null;
- }
- }
-
- private void onPlaySoundEffect(int effectType, int volume) {
- synchronized (mSoundEffectsLock) {
-
- onLoadSoundEffects();
-
- if (mSoundPool == null) {
- return;
- }
- float volFloat;
- // use default if volume is not specified by caller
- if (volume < 0) {
- volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
- } else {
- volFloat = volume / 1000.0f;
- }
-
- if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
- mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
- volFloat, volFloat, 0, 0, 1.0f);
- } else {
- MediaPlayer mediaPlayer = new MediaPlayer();
- try {
- String filePath = getSoundEffectFilePath(effectType);
- mediaPlayer.setDataSource(filePath);
- mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
- mediaPlayer.prepare();
- mediaPlayer.setVolume(volFloat);
- mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
- public void onCompletion(MediaPlayer mp) {
- cleanupPlayer(mp);
- }
- });
- mediaPlayer.setOnErrorListener(new OnErrorListener() {
- public boolean onError(MediaPlayer mp, int what, int extra) {
- cleanupPlayer(mp);
- return true;
- }
- });
- mediaPlayer.start();
- } catch (IOException ex) {
- Log.w(TAG, "MediaPlayer IOException: "+ex);
- } catch (IllegalArgumentException ex) {
- Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
- } catch (IllegalStateException ex) {
- Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
- }
- }
- }
- }
-
- private void cleanupPlayer(MediaPlayer mp) {
- if (mp != null) {
- try {
- mp.stop();
- mp.release();
- } catch (IllegalStateException ex) {
- Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
- }
- }
- }
-
private void onPersistSafeVolumeState(int state) {
Settings.Global.putInt(mContentResolver,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
@@ -5377,24 +5014,25 @@
break;
case MSG_UNLOAD_SOUND_EFFECTS:
- onUnloadSoundEffects();
+ mSfxHelper.unloadSoundEffects();
break;
case MSG_LOAD_SOUND_EFFECTS:
- //FIXME: onLoadSoundEffects() should be executed in a separate thread as it
- // can take several dozens of milliseconds to complete
- boolean loaded = onLoadSoundEffects();
- if (msg.obj != null) {
- LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj;
- synchronized (reply) {
- reply.mStatus = loaded ? 0 : -1;
- reply.notify();
+ {
+ LoadSoundEffectReply reply = (LoadSoundEffectReply) msg.obj;
+ if (mSystemReady) {
+ mSfxHelper.loadSoundEffects(reply);
+ } else {
+ Log.w(TAG, "[schedule]loadSoundEffects() called before boot complete");
+ if (reply != null) {
+ reply.run(false);
}
}
+ }
break;
case MSG_PLAY_SOUND_EFFECT:
- onPlaySoundEffect(msg.arg1, msg.arg2);
+ mSfxHelper.playSoundEffect(msg.arg1, msg.arg2);
break;
case MSG_SET_FORCE_USE:
@@ -5578,15 +5216,19 @@
/**
* @return true if there is currently a registered dynamic mixing policy that affects media
+ * and is not a render + loopback policy
*/
- /*package*/ boolean hasMediaDynamicPolicy() {
+ // only public for mocking/spying
+ @VisibleForTesting
+ public boolean hasMediaDynamicPolicy() {
synchronized (mAudioPolicies) {
if (mAudioPolicies.isEmpty()) {
return false;
}
final Collection<AudioPolicyProxy> appColl = mAudioPolicies.values();
for (AudioPolicyProxy app : appColl) {
- if (app.hasMixAffectingUsage(AudioAttributes.USAGE_MEDIA)) {
+ if (app.hasMixAffectingUsage(AudioAttributes.USAGE_MEDIA,
+ AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER)) {
return true;
}
}
@@ -5911,7 +5553,9 @@
return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
}
- /*package*/ boolean hasAudioFocusUsers() {
+ /** only public for mocking/spying, do not call outside of AudioService */
+ @VisibleForTesting
+ public boolean hasAudioFocusUsers() {
return mMediaFocusControl.hasAudioFocusUsers();
}
@@ -6147,31 +5791,36 @@
// are transformed into key events for the HDMI playback client.
//==========================================================================================
- private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
- public void onComplete(int status) {
+ @GuardedBy("mHdmiClientLock")
+ private void updateHdmiCecSinkLocked(boolean hdmiCecSink) {
+ mHdmiCecSink = hdmiCecSink;
+ if (mHdmiCecSink) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "CEC sink: setting HDMI as full vol device");
+ }
+ mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+ } else {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device");
+ }
+ // Android TV devices without CEC service apply software volume on
+ // HDMI output
+ mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
+ }
+
+ checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
+ "HdmiPlaybackClient.DisplayStatusCallback");
+ }
+
+ private class MyHdmiControlStatusChangeListenerCallback
+ implements HdmiControlManager.HdmiControlStatusChangeListener {
+ public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
synchronized (mHdmiClientLock) {
- if (mHdmiManager != null) {
- mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
- // Television devices without CEC service apply software volume on HDMI output
- if (mHdmiCecSink) {
- if (DEBUG_VOL) {
- Log.d(TAG, "CEC sink: setting HDMI as full vol device");
- }
- mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
- } else {
- if (DEBUG_VOL) {
- Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device");
- }
- // Android TV devices without CEC service apply software volume on
- // HDMI output
- mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
- }
- checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
- "HdmiPlaybackClient.DisplayStatusCallback");
- }
+ if (mHdmiManager == null) return;
+ updateHdmiCecSinkLocked(isCecEnabled ? isCecAvailable : false);
}
}
- }
+ };
private final Object mHdmiClientLock = new Object();
@@ -6188,12 +5837,14 @@
@GuardedBy("mHdmiClientLock")
private HdmiPlaybackClient mHdmiPlaybackClient;
// true if we are a set-top box, an HDMI sink is connected and it supports CEC.
+ @GuardedBy("mHdmiClientLock")
private boolean mHdmiCecSink;
// Set only when device is an audio system.
@GuardedBy("mHdmiClientLock")
private HdmiAudioSystemClient mHdmiAudioSystemClient;
- private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();
+ private MyHdmiControlStatusChangeListenerCallback mHdmiControlStatusChangeListenerCallback =
+ new MyHdmiControlStatusChangeListenerCallback();
@Override
public int setHdmiSystemAudioSupported(boolean on) {
@@ -6379,6 +6030,12 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (mAudioHandler != null) {
+ pw.println("\nMessage handler (watch for unhandled messages):");
+ mAudioHandler.dump(new PrintWriterPrinter(pw), " ");
+ } else {
+ pw.println("\nMessage handler is null");
+ }
mMediaFocusControl.dump(pw);
dumpStreamStates(pw);
dumpRingerMode(pw);
@@ -6414,11 +6071,14 @@
dumpAudioPolicies(pw);
mDynPolicyLogger.dump(pw);
-
mPlaybackMonitor.dump(pw);
-
mRecordMonitor.dump(pw);
+ pw.println("\nAudioDeviceBroker:");
+ mDeviceBroker.dump(pw, " ");
+ pw.println("\nSoundEffects:");
+ mSfxHelper.dump(pw, " ");
+
pw.println("\n");
pw.println("\nEvent logs:");
mModeLogger.dump(pw);
@@ -7349,9 +7009,10 @@
Binder.restoreCallingIdentity(identity);
}
- boolean hasMixAffectingUsage(int usage) {
+ boolean hasMixAffectingUsage(int usage, int excludedFlags) {
for (AudioMix mix : mMixes) {
- if (mix.isAffectingUsage(usage)) {
+ if (mix.isAffectingUsage(usage)
+ && ((mix.getRouteFlags() & excludedFlags) != excludedFlags)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 1a63f8f..9f1a6bd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -139,6 +139,12 @@
public int getCodec() {
return mCodec;
}
+
+ // redefine equality op so we can match messages intended for this device
+ @Override
+ public boolean equals(Object o) {
+ return mBtDevice.equals(o);
+ }
}
// A2DP device events
@@ -441,9 +447,9 @@
return;
}
final BluetoothDevice btDevice = deviceList.get(0);
- final @BluetoothProfile.BtProfileState int state = mA2dp.getConnectionState(btDevice);
- mDeviceBroker.handleSetA2dpSinkConnectionState(
- state, new BluetoothA2dpDeviceInfo(btDevice));
+ // the device is guaranteed CONNECTED
+ mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(btDevice,
+ BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, true, -1);
}
/*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) {
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index db55138..bd129f7 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -364,28 +364,8 @@
// check enforcement by the framework
boolean handled = false;
- if (focusLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
- && MediaFocusControl.ENFORCE_DUCKING
- && frWinner != null) {
- // candidate for enforcement by the framework
- if (frWinner.mCallingUid != this.mCallingUid) {
- if (!forceDuck && ((mGrantFlags
- & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0)) {
- // the focus loser declared it would pause instead of duck, let it
- // handle it (the framework doesn't pause for apps)
- handled = false;
- Log.v(TAG, "not ducking uid " + this.mCallingUid + " - flags");
- } else if (!forceDuck && (MediaFocusControl.ENFORCE_DUCKING_FOR_NEW &&
- this.getSdkTarget() <= MediaFocusControl.DUCKING_IN_APP_SDK_LEVEL))
- {
- // legacy behavior, apps used to be notified when they should be ducking
- handled = false;
- Log.v(TAG, "not ducking uid " + this.mCallingUid + " - old SDK");
- } else {
- handled = mFocusController.duckPlayers(frWinner, this, forceDuck);
- }
- } // else: the focus change is within the same app, so let the dispatching
- // happen as if the framework was not involved.
+ if (frWinner != null) {
+ handled = frameworkHandleFocusLoss(focusLoss, frWinner, forceDuck);
}
if (handled) {
@@ -415,6 +395,47 @@
}
}
+ /**
+ * Let the framework handle the focus loss if possible
+ * @param focusLoss
+ * @param frWinner
+ * @param forceDuck
+ * @return true if the framework handled the focus loss
+ */
+ @GuardedBy("MediaFocusControl.mAudioFocusLock")
+ private boolean frameworkHandleFocusLoss(int focusLoss, @NonNull final FocusRequester frWinner,
+ boolean forceDuck) {
+ if (frWinner.mCallingUid == this.mCallingUid) {
+ // the focus change is within the same app, so let the dispatching
+ // happen as if the framework was not involved.
+ return false;
+ }
+
+ if (focusLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+ if (!MediaFocusControl.ENFORCE_DUCKING) {
+ return false;
+ }
+
+ // candidate for enforcement by the framework
+ if (!forceDuck && ((mGrantFlags
+ & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0)) {
+ // the focus loser declared it would pause instead of duck, let it
+ // handle it (the framework doesn't pause for apps)
+ Log.v(TAG, "not ducking uid " + this.mCallingUid + " - flags");
+ return false;
+ }
+ if (!forceDuck && (MediaFocusControl.ENFORCE_DUCKING_FOR_NEW
+ && this.getSdkTarget() <= MediaFocusControl.DUCKING_IN_APP_SDK_LEVEL)) {
+ // legacy behavior, apps used to be notified when they should be ducking
+ Log.v(TAG, "not ducking uid " + this.mCallingUid + " - old SDK");
+ return false;
+ }
+
+ return mFocusController.duckPlayers(frWinner, this, forceDuck);
+ }
+ return false;
+ }
+
int dispatchFocusChange(int focusChange) {
if (mFocusDispatcher == null) {
if (MediaFocusControl.DEBUG) { Log.e(TAG, "dispatchFocusChange: no focus dispatcher"); }
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 5c93071..c845981 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -105,12 +105,13 @@
//=================================================================
// PlayerFocusEnforcer implementation
@Override
- public boolean duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck) {
+ public boolean duckPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser,
+ boolean forceDuck) {
return mFocusEnforcer.duckPlayers(winner, loser, forceDuck);
}
@Override
- public void unduckPlayers(FocusRequester winner) {
+ public void unduckPlayers(@NonNull FocusRequester winner) {
mFocusEnforcer.unduckPlayers(winner);
}
@@ -742,7 +743,20 @@
}
}
- /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
+ /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int)
+ * @param aa
+ * @param focusChangeHint
+ * @param cb
+ * @param fd
+ * @param clientId
+ * @param callingPackageName
+ * @param flags
+ * @param sdk
+ * @param forceDuck only true if
+ * {@link android.media.AudioFocusRequest.Builder#setFocusGain(int)} was set to true for
+ * accessibility.
+ * @return
+ */
protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
int flags, int sdk, boolean forceDuck) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 3a25d98..f8ba55b 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -425,7 +425,8 @@
private final DuckingManager mDuckingManager = new DuckingManager();
@Override
- public boolean duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck) {
+ public boolean duckPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser,
+ boolean forceDuck) {
if (DEBUG) {
Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
winner.getClientUid(), loser.getClientUid()));
@@ -473,7 +474,7 @@
}
@Override
- public void unduckPlayers(FocusRequester winner) {
+ public void unduckPlayers(@NonNull FocusRequester winner) {
if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
synchronized (mPlayerLock) {
mDuckingManager.unduckUid(winner.getClientUid(), mPlayers);
diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
index 3c834da..89e7b782 100644
--- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
+++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import android.annotation.NonNull;
+
public interface PlayerFocusEnforcer {
/**
@@ -25,11 +27,24 @@
* @param loser
* @return
*/
- public boolean duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck);
+ boolean duckPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser,
+ boolean forceDuck);
- public void unduckPlayers(FocusRequester winner);
+ /**
+ * Unduck the players that had been ducked with
+ * {@link #duckPlayers(FocusRequester, FocusRequester, boolean)}
+ * @param winner
+ */
+ void unduckPlayers(@NonNull FocusRequester winner);
- public void mutePlayersForCall(int[] usagesToMute);
+ /**
+ * Mute players at the beginning of a call
+ * @param usagesToMute array of {@link android.media.AudioAttributes} usages to mute
+ */
+ void mutePlayersForCall(int[] usagesToMute);
- public void unmutePlayersForCall();
+ /**
+ * Unmute players at the end of a call
+ */
+ void unmutePlayersForCall();
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
new file mode 100644
index 0000000..cf5bc8d
--- /dev/null
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -0,0 +1,521 @@
+/*
+ * 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.audio;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.SoundPool;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class for managing sound effects loading / unloading
+ * used by AudioService. As its methods are called on the message handler thread
+ * of AudioService, the actual work is offloaded to a dedicated thread.
+ * This helps keeping AudioService responsive.
+ * @hide
+ */
+class SoundEffectsHelper {
+ private static final String TAG = "AS.SfxHelper";
+
+ private static final int NUM_SOUNDPOOL_CHANNELS = 4;
+
+ /* Sound effect file names */
+ private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
+
+ private static final int EFFECT_NOT_IN_SOUND_POOL = 0; // SoundPool sample IDs > 0
+
+ private static final int MSG_LOAD_EFFECTS = 0;
+ private static final int MSG_UNLOAD_EFFECTS = 1;
+ private static final int MSG_PLAY_EFFECT = 2;
+ private static final int MSG_LOAD_EFFECTS_TIMEOUT = 3;
+
+ interface OnEffectsLoadCompleteHandler {
+ void run(boolean success);
+ }
+
+ private final AudioEventLogger mSfxLogger = new AudioEventLogger(
+ AudioManager.NUM_SOUND_EFFECTS + 10, "Sound Effects Loading");
+
+ private final Context mContext;
+ // default attenuation applied to sound played with playSoundEffect()
+ private final int mSfxAttenuationDb;
+
+ // thread for doing all work
+ private SfxWorker mSfxWorker;
+ // thread's message handler
+ private SfxHandler mSfxHandler;
+
+ private static final class Resource {
+ final String mFileName;
+ int mSampleId;
+ boolean mLoaded; // for effects in SoundPool
+ Resource(String fileName) {
+ mFileName = fileName;
+ mSampleId = EFFECT_NOT_IN_SOUND_POOL;
+ }
+ }
+ // All the fields below are accessed by the worker thread exclusively
+ private final List<Resource> mResources = new ArrayList<Resource>();
+ private final int[] mEffects = new int[AudioManager.NUM_SOUND_EFFECTS]; // indexes in mResources
+ private SoundPool mSoundPool;
+ private SoundPoolLoader mSoundPoolLoader;
+
+ SoundEffectsHelper(Context context) {
+ mContext = context;
+ mSfxAttenuationDb = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_soundEffectVolumeDb);
+ startWorker();
+ }
+
+ /*package*/ void loadSoundEffects(OnEffectsLoadCompleteHandler onComplete) {
+ sendMsg(MSG_LOAD_EFFECTS, 0, 0, onComplete, 0);
+ }
+
+ /**
+ * Unloads samples from the sound pool.
+ * This method can be called to free some memory when
+ * sound effects are disabled.
+ */
+ /*package*/ void unloadSoundEffects() {
+ sendMsg(MSG_UNLOAD_EFFECTS, 0, 0, null, 0);
+ }
+
+ /*package*/ void playSoundEffect(int effect, int volume) {
+ sendMsg(MSG_PLAY_EFFECT, effect, volume, null, 0);
+ }
+
+ /*package*/ void dump(PrintWriter pw, String prefix) {
+ if (mSfxHandler != null) {
+ pw.println(prefix + "Message handler (watch for unhandled messages):");
+ mSfxHandler.dump(new PrintWriterPrinter(pw), " ");
+ } else {
+ pw.println(prefix + "Message handler is null");
+ }
+ pw.println(prefix + "Default attenuation (dB): " + mSfxAttenuationDb);
+ mSfxLogger.dump(pw);
+ }
+
+ private void startWorker() {
+ mSfxWorker = new SfxWorker();
+ mSfxWorker.start();
+ synchronized (this) {
+ while (mSfxHandler == null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting " + mSfxWorker.getName() + " to start");
+ }
+ }
+ }
+ }
+
+ private void sendMsg(int msg, int arg1, int arg2, Object obj, int delayMs) {
+ mSfxHandler.sendMessageDelayed(mSfxHandler.obtainMessage(msg, arg1, arg2, obj), delayMs);
+ }
+
+ private void logEvent(String msg) {
+ mSfxLogger.log(new AudioEventLogger.StringEvent(msg));
+ }
+
+ // All the methods below run on the worker thread
+ private void onLoadSoundEffects(OnEffectsLoadCompleteHandler onComplete) {
+ if (mSoundPoolLoader != null) {
+ // Loading is ongoing.
+ mSoundPoolLoader.addHandler(onComplete);
+ return;
+ }
+ if (mSoundPool != null) {
+ if (onComplete != null) {
+ onComplete.run(true /*success*/);
+ }
+ return;
+ }
+
+ logEvent("effects loading started");
+ mSoundPool = new SoundPool.Builder()
+ .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
+ .setAudioAttributes(new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build())
+ .build();
+ loadTouchSoundAssets();
+
+ mSoundPoolLoader = new SoundPoolLoader();
+ mSoundPoolLoader.addHandler(new OnEffectsLoadCompleteHandler() {
+ @Override
+ public void run(boolean success) {
+ mSoundPoolLoader = null;
+ if (!success) {
+ Log.w(TAG, "onLoadSoundEffects(), Error while loading samples");
+ onUnloadSoundEffects();
+ }
+ }
+ });
+ mSoundPoolLoader.addHandler(onComplete);
+
+ int resourcesToLoad = 0;
+ for (Resource res : mResources) {
+ String filePath = getResourceFilePath(res);
+ int sampleId = mSoundPool.load(filePath, 0);
+ if (sampleId > 0) {
+ res.mSampleId = sampleId;
+ res.mLoaded = false;
+ resourcesToLoad++;
+ } else {
+ logEvent("effect " + filePath + " rejected by SoundPool");
+ Log.w(TAG, "SoundPool could not load file: " + filePath);
+ }
+ }
+
+ if (resourcesToLoad > 0) {
+ sendMsg(MSG_LOAD_EFFECTS_TIMEOUT, 0, 0, null, SOUND_EFFECTS_LOAD_TIMEOUT_MS);
+ } else {
+ logEvent("effects loading completed, no effects to load");
+ mSoundPoolLoader.onComplete(true /*success*/);
+ }
+ }
+
+ void onUnloadSoundEffects() {
+ if (mSoundPool == null) {
+ return;
+ }
+ if (mSoundPoolLoader != null) {
+ mSoundPoolLoader.addHandler(new OnEffectsLoadCompleteHandler() {
+ @Override
+ public void run(boolean success) {
+ onUnloadSoundEffects();
+ }
+ });
+ }
+
+ logEvent("effects unloading started");
+ for (Resource res : mResources) {
+ if (res.mSampleId != EFFECT_NOT_IN_SOUND_POOL) {
+ mSoundPool.unload(res.mSampleId);
+ }
+ }
+ mSoundPool.release();
+ mSoundPool = null;
+ logEvent("effects unloading completed");
+ }
+
+ void onPlaySoundEffect(int effect, int volume) {
+ float volFloat;
+ // use default if volume is not specified by caller
+ if (volume < 0) {
+ volFloat = (float) Math.pow(10, (float) mSfxAttenuationDb / 20);
+ } else {
+ volFloat = volume / 1000.0f;
+ }
+
+ Resource res = mResources.get(mEffects[effect]);
+ if (res.mSampleId != EFFECT_NOT_IN_SOUND_POOL && res.mLoaded) {
+ mSoundPool.play(res.mSampleId, volFloat, volFloat, 0, 0, 1.0f);
+ } else {
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ try {
+ String filePath = getResourceFilePath(res);
+ mediaPlayer.setDataSource(filePath);
+ mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
+ mediaPlayer.prepare();
+ mediaPlayer.setVolume(volFloat);
+ mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+ public void onCompletion(MediaPlayer mp) {
+ cleanupPlayer(mp);
+ }
+ });
+ mediaPlayer.setOnErrorListener(new OnErrorListener() {
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ cleanupPlayer(mp);
+ return true;
+ }
+ });
+ mediaPlayer.start();
+ } catch (IOException ex) {
+ Log.w(TAG, "MediaPlayer IOException: " + ex);
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "MediaPlayer IllegalArgumentException: " + ex);
+ } catch (IllegalStateException ex) {
+ Log.w(TAG, "MediaPlayer IllegalStateException: " + ex);
+ }
+ }
+ }
+
+ private static void cleanupPlayer(MediaPlayer mp) {
+ if (mp != null) {
+ try {
+ mp.stop();
+ mp.release();
+ } catch (IllegalStateException ex) {
+ Log.w(TAG, "MediaPlayer IllegalStateException: " + ex);
+ }
+ }
+ }
+
+ private static final String TAG_AUDIO_ASSETS = "audio_assets";
+ private static final String ATTR_VERSION = "version";
+ private static final String TAG_GROUP = "group";
+ private static final String ATTR_GROUP_NAME = "name";
+ private static final String TAG_ASSET = "asset";
+ private static final String ATTR_ASSET_ID = "id";
+ private static final String ATTR_ASSET_FILE = "file";
+
+ private static final String ASSET_FILE_VERSION = "1.0";
+ private static final String GROUP_TOUCH_SOUNDS = "touch_sounds";
+
+ private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 15000;
+
+ private String getResourceFilePath(Resource res) {
+ String filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH + res.mFileName;
+ if (!new File(filePath).isFile()) {
+ filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + res.mFileName;
+ }
+ return filePath;
+ }
+
+ private void loadTouchSoundAssetDefaults() {
+ int defaultResourceIdx = mResources.size();
+ mResources.add(new Resource("Effect_Tick.ogg"));
+ for (int i = 0; i < mEffects.length; i++) {
+ mEffects[i] = defaultResourceIdx;
+ }
+ }
+
+ private void loadTouchSoundAssets() {
+ XmlResourceParser parser = null;
+
+ // only load assets once.
+ if (!mResources.isEmpty()) {
+ return;
+ }
+
+ loadTouchSoundAssetDefaults();
+
+ try {
+ parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets);
+
+ XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS);
+ String version = parser.getAttributeValue(null, ATTR_VERSION);
+ boolean inTouchSoundsGroup = false;
+
+ if (ASSET_FILE_VERSION.equals(version)) {
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+ if (element.equals(TAG_GROUP)) {
+ String name = parser.getAttributeValue(null, ATTR_GROUP_NAME);
+ if (GROUP_TOUCH_SOUNDS.equals(name)) {
+ inTouchSoundsGroup = true;
+ break;
+ }
+ }
+ }
+ while (inTouchSoundsGroup) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+ if (element.equals(TAG_ASSET)) {
+ String id = parser.getAttributeValue(null, ATTR_ASSET_ID);
+ String file = parser.getAttributeValue(null, ATTR_ASSET_FILE);
+ int fx;
+
+ try {
+ Field field = AudioManager.class.getField(id);
+ fx = field.getInt(null);
+ } catch (Exception e) {
+ Log.w(TAG, "Invalid touch sound ID: " + id);
+ continue;
+ }
+
+ mEffects[fx] = findOrAddResourceByFileName(file);
+ } else {
+ break;
+ }
+ }
+ }
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "audio assets file not found", e);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "XML parser exception reading touch sound assets", e);
+ } catch (IOException e) {
+ Log.w(TAG, "I/O exception reading touch sound assets", e);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ private int findOrAddResourceByFileName(String fileName) {
+ for (int i = 0; i < mResources.size(); i++) {
+ if (mResources.get(i).mFileName.equals(fileName)) {
+ return i;
+ }
+ }
+ int result = mResources.size();
+ mResources.add(new Resource(fileName));
+ return result;
+ }
+
+ private Resource findResourceBySampleId(int sampleId) {
+ for (Resource res : mResources) {
+ if (res.mSampleId == sampleId) {
+ return res;
+ }
+ }
+ return null;
+ }
+
+ private class SfxWorker extends Thread {
+ SfxWorker() {
+ super("AS.SfxWorker");
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ synchronized (SoundEffectsHelper.this) {
+ mSfxHandler = new SfxHandler();
+ SoundEffectsHelper.this.notify();
+ }
+ Looper.loop();
+ }
+ }
+
+ private class SfxHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LOAD_EFFECTS:
+ onLoadSoundEffects((OnEffectsLoadCompleteHandler) msg.obj);
+ break;
+ case MSG_UNLOAD_EFFECTS:
+ onUnloadSoundEffects();
+ break;
+ case MSG_PLAY_EFFECT:
+ onLoadSoundEffects(new OnEffectsLoadCompleteHandler() {
+ @Override
+ public void run(boolean success) {
+ if (success) {
+ onPlaySoundEffect(msg.arg1 /*effect*/, msg.arg2 /*volume*/);
+ }
+ }
+ });
+ break;
+ case MSG_LOAD_EFFECTS_TIMEOUT:
+ if (mSoundPoolLoader != null) {
+ mSoundPoolLoader.onTimeout();
+ }
+ break;
+ }
+ }
+ }
+
+ private class SoundPoolLoader implements
+ android.media.SoundPool.OnLoadCompleteListener {
+
+ private List<OnEffectsLoadCompleteHandler> mLoadCompleteHandlers =
+ new ArrayList<OnEffectsLoadCompleteHandler>();
+
+ SoundPoolLoader() {
+ // SoundPool use the current Looper when creating its message handler.
+ // Since SoundPoolLoader is created on the SfxWorker thread, SoundPool's
+ // message handler ends up running on it (it's OK to have multiple
+ // handlers on the same Looper). Thus, onLoadComplete gets executed
+ // on the worker thread.
+ mSoundPool.setOnLoadCompleteListener(this);
+ }
+
+ void addHandler(OnEffectsLoadCompleteHandler handler) {
+ if (handler != null) {
+ mLoadCompleteHandlers.add(handler);
+ }
+ }
+
+ @Override
+ public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+ if (status == 0) {
+ int remainingToLoad = 0;
+ for (Resource res : mResources) {
+ if (res.mSampleId == sampleId && !res.mLoaded) {
+ logEvent("effect " + res.mFileName + " loaded");
+ res.mLoaded = true;
+ }
+ if (res.mSampleId != EFFECT_NOT_IN_SOUND_POOL && !res.mLoaded) {
+ remainingToLoad++;
+ }
+ }
+ if (remainingToLoad == 0) {
+ onComplete(true);
+ }
+ } else {
+ Resource res = findResourceBySampleId(sampleId);
+ String filePath;
+ if (res != null) {
+ filePath = getResourceFilePath(res);
+ } else {
+ filePath = "with unknown sample ID " + sampleId;
+ }
+ logEvent("effect " + filePath + " loading failed, status " + status);
+ Log.w(TAG, "onLoadSoundEffects(), Error " + status + " while loading sample "
+ + filePath);
+ onComplete(false);
+ }
+ }
+
+ void onTimeout() {
+ onComplete(false);
+ }
+
+ void onComplete(boolean success) {
+ mSoundPool.setOnLoadCompleteListener(null);
+ for (OnEffectsLoadCompleteHandler handler : mLoadCompleteHandlers) {
+ handler.run(success);
+ }
+ logEvent("effects loading " + (success ? "completed" : "failed"));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index fc38735..81e507c 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -19,7 +19,9 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.util.Slog;
+import android.util.StatsLog;
+import com.android.internal.compat.ChangeReporter;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.util.DumpUtils;
@@ -34,23 +36,27 @@
private static final String TAG = "Compatibility";
private final Context mContext;
+ private final ChangeReporter mChangeReporter;
public PlatformCompat(Context context) {
mContext = context;
+ mChangeReporter = new ChangeReporter();
}
@Override
public void reportChange(long changeId, ApplicationInfo appInfo) {
- Slog.d(TAG, "Compat change reported: " + changeId + "; UID " + appInfo.uid);
- // TODO log via StatsLog
+ reportChange(changeId, appInfo, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
}
@Override
public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
- reportChange(changeId, appInfo);
+ reportChange(changeId, appInfo,
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
return true;
}
+ reportChange(changeId, appInfo,
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
return false;
}
@@ -59,4 +65,13 @@
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
CompatConfig.get().dumpConfig(pw);
}
+
+ private void reportChange(long changeId, ApplicationInfo appInfo, int state) {
+ int uid = appInfo.uid;
+ //TODO(b/138374585): Implement rate limiting for the logs.
+ Slog.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+ mChangeReporter.reportChange(uid, changeId,
+ state, /* source */
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1bd29e5..063ad75 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2082,7 +2082,6 @@
}
out.write(0xFF);
out.write(0xFF);
- out.flush();
// Wait for End-of-File.
InputStream in = mSockets[i].getInputStream();
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3398d36..a83dd21 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -42,6 +42,7 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlService;
+import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiDeviceEventListener;
import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.hardware.hdmi.IHdmiInputChangeListener;
@@ -251,6 +252,11 @@
// Type of logical devices hosted in the system. Stored in the unmodifiable list.
private final List<Integer> mLocalDevices;
+ // List of records for HDMI control status change listener for death monitoring.
+ @GuardedBy("mLock")
+ private final ArrayList<HdmiControlStatusChangeListenerRecord>
+ mHdmiControlStatusChangeListenerRecords = new ArrayList<>();
+
// List of records for hotplug event listener to handle the the caller killed in action.
@GuardedBy("mLock")
private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
@@ -564,6 +570,7 @@
}
if (reason != -1) {
invokeVendorCommandListenersOnControlStateChanged(true, reason);
+ announceHdmiControlStatusChange(true);
}
}
@@ -1317,6 +1324,37 @@
// Record class that monitors the event of the caller of being killed. Used to clean up
// the listener list and record list accordingly.
+ private final class HdmiControlStatusChangeListenerRecord implements IBinder.DeathRecipient {
+ private final IHdmiControlStatusChangeListener mListener;
+
+ HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mHdmiControlStatusChangeListenerRecords.remove(this);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof HdmiControlStatusChangeListenerRecord)) return false;
+ if (obj == this) return true;
+ HdmiControlStatusChangeListenerRecord other =
+ (HdmiControlStatusChangeListenerRecord) obj;
+ return other.mListener == this.mListener;
+ }
+
+ @Override
+ public int hashCode() {
+ return mListener.hashCode();
+ }
+ }
+
+ // Record class that monitors the event of the caller of being killed. Used to clean up
+ // the listener list and record list accordingly.
private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
private final IHdmiHotplugEventListener mListener;
@@ -1621,6 +1659,20 @@
}
@Override
+ public void addHdmiControlStatusChangeListener(
+ final IHdmiControlStatusChangeListener listener) {
+ enforceAccessPermission();
+ HdmiControlService.this.addHdmiControlStatusChangeListener(listener);
+ }
+
+ @Override
+ public void removeHdmiControlStatusChangeListener(
+ final IHdmiControlStatusChangeListener listener) {
+ enforceAccessPermission();
+ HdmiControlService.this.removeHdmiControlStatusChangeListener(listener);
+ }
+
+ @Override
public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
enforceAccessPermission();
HdmiControlService.this.addHotplugEventListener(listener);
@@ -2135,6 +2187,51 @@
source.queryDisplayStatus(callback);
}
+ private void addHdmiControlStatusChangeListener(
+ final IHdmiControlStatusChangeListener listener) {
+ final HdmiControlStatusChangeListenerRecord record =
+ new HdmiControlStatusChangeListenerRecord(listener);
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Listener already died");
+ return;
+ }
+ synchronized (mLock) {
+ mHdmiControlStatusChangeListenerRecords.add(record);
+ }
+
+ // Inform the listener of the initial state of each HDMI port by generating
+ // hotplug events.
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return;
+ }
+
+ // Return the current status of mHdmiControlEnabled;
+ synchronized (mLock) {
+ invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled);
+ }
+ }
+ });
+ }
+
+ private void removeHdmiControlStatusChangeListener(
+ final IHdmiControlStatusChangeListener listener) {
+ synchronized (mLock) {
+ for (HdmiControlStatusChangeListenerRecord record :
+ mHdmiControlStatusChangeListenerRecords) {
+ if (record.mListener.asBinder() == listener.asBinder()) {
+ listener.asBinder().unlinkToDeath(record, 0);
+ mHdmiControlStatusChangeListenerRecords.remove(record);
+ break;
+ }
+ }
+ }
+ }
+
private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
try {
@@ -2367,6 +2464,47 @@
}
}
+ private void announceHdmiControlStatusChange(boolean isEnabled) {
+ assertRunOnServiceThread();
+ synchronized (mLock) {
+ for (HdmiControlStatusChangeListenerRecord record :
+ mHdmiControlStatusChangeListenerRecords) {
+ invokeHdmiControlStatusChangeListenerLocked(record.mListener, isEnabled);
+ }
+ }
+ }
+
+ private void invokeHdmiControlStatusChangeListenerLocked(
+ IHdmiControlStatusChangeListener listener, boolean isEnabled) {
+ if (isEnabled) {
+ queryDisplayStatus(new IHdmiControlCallback.Stub() {
+ public void onComplete(int status) {
+ boolean isAvailable = true;
+ if (status == HdmiControlManager.POWER_STATUS_UNKNOWN
+ || status == HdmiControlManager.RESULT_EXCEPTION
+ || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) {
+ isAvailable = false;
+ }
+
+ try {
+ listener.onStatusChange(isEnabled, isAvailable);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
+ + " isAvailable: " + isAvailable, e);
+ }
+ }
+ });
+ return;
+ }
+
+ try {
+ listener.onStatusChange(isEnabled, false);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
+ + " isAvailable: " + false, e);
+ }
+ }
+
public HdmiCecLocalDeviceTv tv() {
return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
}
@@ -2736,6 +2874,8 @@
disableHdmiControlService();
}
});
+ announceHdmiControlStatusChange(enabled);
+
return;
}
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index 18d193a..c6b082a 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -2034,10 +2034,10 @@
private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
4 * HOUR_IN_MILLIS;
private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
- 10 * MINUTE_IN_MILLIS;
+ MINUTE_IN_MILLIS;
private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
- private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = // 20/window = 120/hr = 1/session
- DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
+ private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE =
+ 75; // 75/window = 450/hr = 1/session
private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
(int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
@@ -2045,7 +2045,7 @@
private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
(int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
- 20; // 120/hr
+ 75; // 450/hr
private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
10; // 5/hr
private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT =
@@ -2199,7 +2199,7 @@
mResolver = resolver;
mResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS), false, this);
- updateConstants();
+ onChange(true, null);
}
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bd198dd..f12c689 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -892,8 +892,22 @@
@Override
public void onNotificationError(int callingUid, int callingPid, String pkg, String tag,
int id, int uid, int initialPid, String message, int userId) {
+ final boolean fgService;
+ synchronized (mNotificationLock) {
+ NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
+ fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0;
+ }
cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
REASON_ERROR, null);
+ if (fgService) {
+ // Still crash for foreground services, preventing the not-crash behaviour abused
+ // by apps to give us a garbage notification and silently start a fg service.
+ Binder.withCleanCallingIdentity(
+ () -> mAm.crashApplication(uid, initialPid, pkg, -1,
+ "Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
+ + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
+ + message));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index d5adb5e..47370b6 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -1047,8 +1047,14 @@
@Override
public void onSensorChanged(SensorEvent event) {
int newRotation;
+
+ int reportedRotation = (int) event.values[0];
+ if (reportedRotation < 0 || reportedRotation > 3) {
+ return;
+ }
+
synchronized (mLock) {
- mDesiredRotation = (int) event.values[0];
+ mDesiredRotation = reportedRotation;
newRotation = evaluateRotationChangeLocked();
}
if (newRotation >=0) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 6218498..3bc239b 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -57,11 +57,14 @@
],
include_dirs: [
- "bionic/libc/private",
"frameworks/base/libs",
"frameworks/native/services",
"system/gatekeeper/include",
],
+
+ header_libs: [
+ "bionic_libc_platform_headers",
+ ],
}
cc_defaults {
@@ -98,8 +101,6 @@
"libGLESv2",
"libnetutils",
"libhidlbase",
- "libhidltransport",
- "libhwbinder",
"libutils",
"libhwui",
"libbpf_android",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 159a496..78b64ca 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -24,7 +24,7 @@
#include <sensorservice/SensorService.h>
#include <sensorservicehidl/SensorManager.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
#include <cutils/properties.h>
#include <utils/Log.h>
diff --git a/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
index b53ea92..2b1c83f 100644
--- a/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
+++ b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
@@ -22,7 +22,7 @@
namespace {
static jint runSelfTest(JNIEnv* env, jobject /* clazz */) {
- return BORINGSSL_self_test();
+ return FIPS_mode();
}
static const JNINativeMethod methods[] = {
@@ -39,4 +39,4 @@
env, "com/android/server/devicepolicy/CryptoTestHelper", methods, NELEM(methods));
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 25bd4ec..c1bbb30 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -69,7 +69,7 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
- <uses-permission android:name="android.permission.MANAGE_APPOPS"/>
+ <uses-permission android:name="android.permission.BLUETOOTH"/>
<!-- Uses API introduced in O (26) -->
<uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
new file mode 100644
index 0000000..5c2ad94
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AudioDeviceBrokerTest {
+
+ private static final String TAG = "AudioDeviceBrokerTest";
+ private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100;
+
+ private Context mContext;
+ // the actual class under test
+ private AudioDeviceBroker mAudioDeviceBroker;
+
+ @Mock private AudioService mMockAudioService;
+ @Spy private AudioDeviceInventory mSpyDevInventory;
+
+ private BluetoothDevice mFakeBtDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+
+ mMockAudioService = mock(AudioService.class);
+ mSpyDevInventory = spy(new AudioDeviceInventory());
+ mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory);
+ mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
+
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ mFakeBtDevice = adapter.getRemoteDevice("00:01:02:03:04:05");
+ Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
+ }
+
+ @After
+ public void tearDown() throws Exception { }
+
+ @Test
+ public void testSetUpAndTearDown() { }
+
+ /**
+ * Verify call to postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for connection
+ * calls into AudioDeviceInventory with the right params
+ * @throws Exception
+ */
+ @Test
+ public void testPostA2dpDeviceConnectionChange() throws Exception {
+ Log.i(TAG, "testPostA2dpDeviceConnectionChange");
+ Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
+
+ mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
+ Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+ verify(mSpyDevInventory, times(1)).setBluetoothA2dpDeviceConnectionState(
+ any(BluetoothDevice.class),
+ ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED) /*state*/,
+ ArgumentMatchers.eq(BluetoothProfile.A2DP) /*profile*/,
+ ArgumentMatchers.eq(true) /*suppressNoisyIntent*/, anyInt() /*musicDevice*/,
+ ArgumentMatchers.eq(1) /*a2dpVolume*/
+ );
+ }
+
+ /**
+ * Verify call to postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for
+ * connection > pause > disconnection > connection
+ * keeps the device connected
+ * @throws Exception
+ */
+ @Test
+ public void testA2dpDeviceConnectionDisconnectionConnectionChange() throws Exception {
+ Log.i(TAG, "testA2dpDeviceConnectionDisconnectionConnectionChange");
+
+ doTestConnectionDisconnectionReconnection(0);
+ }
+
+ /**
+ * Verify device disconnection and reconnection within the BECOMING_NOISY window
+ * @throws Exception
+ */
+ @Test
+ public void testA2dpDeviceReconnectionWithinBecomingNoisyDelay() throws Exception {
+ Log.i(TAG, "testA2dpDeviceReconnectionWithinBecomingNoisyDelay");
+
+ doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2);
+ }
+
+ private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection)
+ throws Exception {
+ when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
+ .thenReturn(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+ when(mMockAudioService.isInCommunication()).thenReturn(false);
+ when(mMockAudioService.hasMediaDynamicPolicy()).thenReturn(false);
+ when(mMockAudioService.hasAudioFocusUsers()).thenReturn(false);
+
+ // first connection
+ mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
+ Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+
+ // disconnection
+ mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
+ BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, false, -1);
+ if (delayAfterDisconnection > 0) {
+ Thread.sleep(delayAfterDisconnection);
+ }
+
+ // reconnection
+ mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 2);
+ Thread.sleep(AudioService.BECOMING_NOISY_DELAY_MS + MAX_MESSAGE_HANDLING_DELAY_MS);
+
+ // Verify disconnection has been cancelled and we're seeing two connections attempts,
+ // with the device connected at the end of the test
+ verify(mSpyDevInventory, times(2)).onSetA2dpSinkConnectionState(
+ any(BtHelper.BluetoothA2dpDeviceInfo.class),
+ ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED));
+ Assert.assertTrue("Mock device not connected",
+ mSpyDevInventory.isA2dpDeviceConnected(mFakeBtDevice));
+ }
+}
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index 1214538..f783aa6 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -37,15 +37,21 @@
android_test {
name: "dex-builder-test",
srcs: [
+ "src/android/startop/test/ApkLayoutCompilerTest.java",
"src/android/startop/test/DexBuilderTest.java",
"src/android/startop/test/LayoutCompilerTest.java",
"src/android/startop/test/TestClass.java",
],
sdk_version: "current",
- data: [":generate_dex_testcases", ":generate_compiled_layout1", ":generate_compiled_layout2"],
+ data: [
+ ":generate_dex_testcases",
+ ":generate_compiled_layout1",
+ ":generate_compiled_layout2",
+ ],
static_libs: [
- "androidx.test.rules",
- "guava",
+ "androidx.test.core",
+ "androidx.test.runner",
+ "junit",
],
manifest: "AndroidManifest.xml",
resource_dirs: ["res"],
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
new file mode 100644
index 0000000..230e8df
--- /dev/null
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.startop.test;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import android.view.View;
+import dalvik.system.PathClassLoader;
+import java.lang.reflect.Method;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ApkLayoutCompilerTest {
+ static ClassLoader loadDexFile() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ return new PathClassLoader(context.getCodeCacheDir() + "/compiled_view.dex",
+ ClassLoader.getSystemClassLoader());
+ }
+
+ @BeforeClass
+ public static void setup() throws Exception {
+ // ensure PackageManager has compiled the layouts.
+ Process pm = Runtime.getRuntime().exec("pm compile --compile-layouts android.startop.test");
+ pm.waitFor();
+ }
+
+ @Test
+ public void loadAndInflateLayout1() throws Exception {
+ ClassLoader dex_file = loadDexFile();
+ Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
+ Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
+ Context context = InstrumentationRegistry.getTargetContext();
+ layout1.invoke(null, context, R.layout.layout1);
+ }
+
+ @Test
+ public void loadAndInflateLayout2() throws Exception {
+ ClassLoader dex_file = loadDexFile();
+ Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
+ Method layout2 = compiled_view.getMethod("layout2", Context.class, int.class);
+ Context context = InstrumentationRegistry.getTargetContext();
+ layout2.invoke(null, context, R.layout.layout2);
+ }
+}
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
index d1fe588..6af01f6f 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
@@ -14,8 +14,11 @@
package android.startop.test;
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
import dalvik.system.PathClassLoader;
-
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import org.junit.Assert;
import org.junit.Test;
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
index 3dfb20c..b0cf91d 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
@@ -15,11 +15,11 @@
package android.startop.test;
import android.content.Context;
-
import androidx.test.InstrumentationRegistry;
-
+import android.view.View;
import dalvik.system.PathClassLoader;
-
+import java.lang.reflect.Method;
+import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.Method;
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 3e4cd4d..ddda010 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -3963,6 +3963,13 @@
public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
/**
+ * The id of the subscription which received this cell broadcast message.
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String SUB_ID = "sub_id";
+
+ /**
* Message geographical scope. Valid values are:
* <ul>
* <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_CELL_WIDE}. meaning the
@@ -4184,6 +4191,18 @@
public static final String GEOMETRIES = "geometries";
/**
+ * Geo-Fencing Maximum Wait Time in second. The range of the time is [0, 255]. A device
+ * shall allow to determine its position meeting operator policy. If the device is unable to
+ * determine its position meeting operator policy within the GeoFencing Maximum Wait Time,
+ * it shall present the alert to the user and discontinue further positioning determination
+ * for the alert.
+ *
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time";
+
+ /**
* Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
* @hide
*/
@@ -4236,7 +4255,8 @@
CMAS_CERTAINTY,
RECEIVED_TIME,
MESSAGE_BROADCASTED,
- GEOMETRIES
+ GEOMETRIES,
+ MAXIMUM_WAIT_TIME
};
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index acf4681..f475822 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -108,6 +108,19 @@
"call_forwarding_visibility_bool";
/**
+ * Boolean indicating if carrier supports call forwarding option "When unreachable".
+ *
+ * {@code true}: Call forwarding option "When unreachable" is supported.
+ * {@code false}: Call forwarding option "When unreachable" is not supported. Option will be
+ * greyed out in the UI.
+ *
+ * By default this value is true.
+ * @hide
+ */
+ public static final String KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL =
+ "call_forwarding_when_unreachable_supported_bool";
+
+ /**
* Boolean indicating if the "Caller ID" item is visible in the Additional Settings menu.
* true means visible. false means gone.
* @hide
@@ -3145,6 +3158,7 @@
sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL, true);
sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL, true);
sDefaults.putBoolean(KEY_CALL_FORWARDING_VISIBILITY_BOOL, true);
+ sDefaults.putBoolean(KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL, true);
sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false);
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 271195b..fe882ea 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -33,13 +33,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
+import dalvik.system.VMRuntime;
+
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
-import dalvik.system.VMRuntime;
-
/**
* A listener class for monitoring changes in specific telephony states
* on the device, including service state, signal strength, message
@@ -362,6 +362,30 @@
@SystemApi
public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 0x08000000;
+ /**
+ * Listen for the emergency number placed from an outgoing call.
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+ *
+ * @see #onOutgoingEmergencyCall
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
+ public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 0x10000000;
+
+ /**
+ * Listen for the emergency number placed from an outgoing SMS.
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+ *
+ * @see #onOutgoingEmergencySms
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
+ public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 0x20000000;
+
/*
* Subscription used to listen to the phone state changes
* @hide
@@ -844,6 +868,27 @@
}
/**
+ * Callback invoked when an outgoing call is placed to an emergency number.
+ *
+ * @param placedEmergencyNumber the emergency number {@link EmergencyNumber} the call is placed
+ * to.
+ * @hide
+ */
+ public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
+ // default implementation empty
+ }
+
+ /**
+ * Callback invoked when an outgoing SMS is placed to an emergency number.
+ *
+ * @param sentEmergencyNumber the emergency number {@link EmergencyNumber} the SMS is sent to.
+ * @hide
+ */
+ public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
+ // default implementation empty
+ }
+
+ /**
* Callback invoked when OEM hook raw event is received on the registered subscription.
* Note, the registration subId comes from {@link TelephonyManager} object which registers
* PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
@@ -1151,7 +1196,6 @@
() -> psl.onPhysicalChannelConfigurationChanged(configs)));
}
- @Override
public void onEmergencyNumberListChanged(Map emergencyNumberList) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
@@ -1161,6 +1205,24 @@
() -> psl.onEmergencyNumberListChanged(emergencyNumberList)));
}
+ public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onOutgoingEmergencyCall(placedEmergencyNumber)));
+ }
+
+ public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onOutgoingEmergencySms(sentEmergencyNumber)));
+ }
+
public void onPhoneCapabilityChanged(PhoneCapability capability) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 77231d1..c078764 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -139,6 +139,12 @@
@Retention(RetentionPolicy.SOURCE)
public @interface MessagePriority {}
+ /**
+ * ATIS-0700041 Section 5.2.8 WAC Geo-Fencing Maximum Wait Time Table 12.
+ * @hide
+ */
+ public static final int MAXIMUM_WAIT_TIME_NOT_SET = 255;
+
/** Format of this message (for interpretation of service category values). */
private final int mMessageFormat;
@@ -187,6 +193,14 @@
@Nullable
private final SmsCbCmasInfo mCmasWarningInfo;
+ /**
+ * Geo-Fencing Maximum Wait Time in second, a device shall allow to determine its position
+ * meeting operator policy. If the device is unable to determine its position meeting operator
+ * policy within the GeoFencing Maximum Wait Time, it shall present the alert to the user and
+ * discontinue further positioning determination for the alert.
+ */
+ private final int mMaximumWaitTimeSec;
+
/** UNIX timestamp of when the message was received. */
private final long mReceivedTimeMillis;
@@ -202,8 +216,8 @@
@Nullable SmsCbCmasInfo cmasWarningInfo) {
this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language,
- body, priority, etwsWarningInfo, cmasWarningInfo, null /* geometries */,
- System.currentTimeMillis());
+ body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */,
+ null /* geometries */, System.currentTimeMillis());
}
/**
@@ -213,7 +227,7 @@
public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
SmsCbLocation location, int serviceCategory, String language, String body,
int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo,
- List<Geometry> geometries, long receivedTimeMillis) {
+ int maximumWaitTimeSec, List<Geometry> geometries, long receivedTimeMillis) {
mMessageFormat = messageFormat;
mGeographicalScope = geographicalScope;
mSerialNumber = serialNumber;
@@ -226,6 +240,7 @@
mCmasWarningInfo = cmasWarningInfo;
mReceivedTimeMillis = receivedTimeMillis;
mGeometries = geometries;
+ mMaximumWaitTimeSec = maximumWaitTimeSec;
}
/**
@@ -262,6 +277,7 @@
mReceivedTimeMillis = in.readLong();
String geoStr = in.readString();
mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
+ mMaximumWaitTimeSec = in.readInt();
}
/**
@@ -295,6 +311,7 @@
dest.writeLong(mReceivedTimeMillis);
dest.writeString(
mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null);
+ dest.writeInt(mMaximumWaitTimeSec);
}
@NonNull
@@ -389,6 +406,15 @@
}
/**
+ * Get the Geo-Fencing Maximum Wait Time.
+ * @return the time in second.
+ * @hide
+ */
+ public int getMaximumWaitingTime() {
+ return mMaximumWaitTimeSec;
+ }
+
+ /**
* Get the time when this message was received.
* @return the time in millisecond
*/
@@ -475,6 +501,7 @@
+ ", priority=" + mPriority
+ (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
+ (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "")
+ + ", maximumWaitingTime = " + mMaximumWaitTimeSec
+ ", geo=" + (mGeometries != null
? CbGeoUtils.encodeGeometriesToString(mGeometries) : "null")
+ '}';
@@ -535,6 +562,8 @@
cv.put(CellBroadcasts.GEOMETRIES, (String) null);
}
+ cv.put(CellBroadcasts.MAXIMUM_WAIT_TIME, mMaximumWaitTimeSec);
+
return cv;
}
@@ -644,17 +673,21 @@
List<Geometry> geometries =
geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
- long receivedTimeSec = cursor.getLong(
+ long receivedTimeMillis = cursor.getLong(
cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME));
+ int maximumWaitTimeSec = cursor.getInt(
+ cursor.getColumnIndexOrThrow(CellBroadcasts.MAXIMUM_WAIT_TIME));
+
return new SmsCbMessage(format, geoScope, serialNum, location, category,
- language, body, priority, etwsInfo, cmasInfo, geometries, receivedTimeSec);
+ language, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries,
+ receivedTimeMillis);
}
/**
* @return {@code True} if this message needs geo-fencing check.
*/
public boolean needGeoFencingCheck() {
- return mGeometries != null;
+ return mMaximumWaitTimeSec > 0 && mGeometries != null;
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b830860..79b78f2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3083,19 +3083,62 @@
*/
@SystemApi
public int getSimCardState() {
- int simCardState = getSimState();
- switch (simCardState) {
+ int simState = getSimState();
+ return getSimCardStateFromSimState(simState);
+ }
+
+ /**
+ * Returns a constant indicating the state of the device SIM card in a physical slot.
+ *
+ * @param physicalSlotIndex physical slot index
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_ABSENT
+ * @see #SIM_STATE_CARD_IO_ERROR
+ * @see #SIM_STATE_CARD_RESTRICTED
+ * @see #SIM_STATE_PRESENT
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getSimCardState(int physicalSlotIndex) {
+ int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex));
+ return getSimCardStateFromSimState(simState);
+ }
+
+ /**
+ * Converts SIM state to SIM card state.
+ * @param simState
+ * @return SIM card state
+ */
+ private int getSimCardStateFromSimState(int simState) {
+ switch (simState) {
case SIM_STATE_UNKNOWN:
case SIM_STATE_ABSENT:
case SIM_STATE_CARD_IO_ERROR:
case SIM_STATE_CARD_RESTRICTED:
- return simCardState;
+ return simState;
default:
return SIM_STATE_PRESENT;
}
}
/**
+ * Converts a physical slot index to logical slot index.
+ * @param physicalSlotIndex physical slot index
+ * @return logical slot index
+ */
+ private int getLogicalSlotIndex(int physicalSlotIndex) {
+ UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
+ if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length) {
+ return slotInfos[physicalSlotIndex].getLogicalSlotIdx();
+ }
+
+ return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+ }
+
+ /**
* Returns a constant indicating the state of the card applications on the default SIM card.
*
* @see #SIM_STATE_UNKNOWN
@@ -3110,8 +3153,41 @@
*/
@SystemApi
public int getSimApplicationState() {
- int simApplicationState = getSimStateIncludingLoaded();
- switch (simApplicationState) {
+ int simState = getSimStateIncludingLoaded();
+ return getSimApplicationStateFromSimState(simState);
+ }
+
+ /**
+ * Returns a constant indicating the state of the card applications on the device SIM card in
+ * a physical slot.
+ *
+ * @param physicalSlotIndex physical slot index
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_PIN_REQUIRED
+ * @see #SIM_STATE_PUK_REQUIRED
+ * @see #SIM_STATE_NETWORK_LOCKED
+ * @see #SIM_STATE_NOT_READY
+ * @see #SIM_STATE_PERM_DISABLED
+ * @see #SIM_STATE_LOADED
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getSimApplicationState(int physicalSlotIndex) {
+ int simState =
+ SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex));
+ return getSimApplicationStateFromSimState(simState);
+ }
+
+ /**
+ * Converts SIM state to SIM application state.
+ * @param simState
+ * @return SIM application state
+ */
+ private int getSimApplicationStateFromSimState(int simState) {
+ switch (simState) {
case SIM_STATE_UNKNOWN:
case SIM_STATE_ABSENT:
case SIM_STATE_CARD_IO_ERROR:
@@ -3122,14 +3198,39 @@
// NOT_READY to either LOCKED or LOADED.
return SIM_STATE_NOT_READY;
default:
- return simApplicationState;
+ return simState;
}
}
+
/**
- * Returns a constant indicating the state of the device SIM card in a slot.
+ * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
+ * on the UICC card.
*
- * @param slotIndex
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+ *
+ * @param appType the uicc app type like {@link APPTYPE_CSIM}
+ * @return true if the specified type of application in UICC CARD or false if no uicc or error.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isApplicationOnUicc(@UiccAppType int appType) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.isApplicationOnUicc(getSubId(), appType);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isApplicationOnUicc", e);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a constant indicating the state of the device SIM card in a logical slot.
+ *
+ * @param slotIndex logical slot index
*
* @see #SIM_STATE_UNKNOWN
* @see #SIM_STATE_ABSENT
diff --git a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
index 606df15..6a35e33 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
@@ -22,7 +22,6 @@
*/
oneway interface IImsSmsListener {
void onSendSmsResult(int token, int messageRef, int status, int reason);
- void onSmsStatusReportReceived(int token, int messageRef, in String format,
- in byte[] pdu);
+ void onSmsStatusReportReceived(int token, in String format, in byte[] pdu);
void onSmsReceived(int token, in String format, in byte[] pdu);
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 3a9979d..3562880 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -201,15 +201,20 @@
}
/**
- * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask.
- * <p>
- * Typically this class is not used directly, but rather extended in subclasses of
+ * Contains the IMS capabilities defined and supported by an ImsFeature in the form of a
+ * bit-mask.
+ *
+ * @deprecated This class is not used directly, but rather extended in subclasses of
* {@link ImsFeature} to provide service specific capabilities.
+ * @see MmTelFeature.MmTelCapabilities
* @hide
*/
- @SystemApi
+ // Not Actually deprecated, but we need to remove it from the @SystemApi surface.
+ @Deprecated
+ @SystemApi // SystemApi only because it was leaked through type usage in a previous release.
public static class Capabilities {
/** @deprecated Use getters and accessors instead. */
+ // Not actually deprecated, but we need to remove it from the @SystemApi surface eventually.
protected int mCapabilities = 0;
/**
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 20c191d..ceb4704 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -242,8 +242,8 @@
* @param capabilities The capabilities that are supported for MmTel in the form of a
* bitfield.
*/
- public MmTelCapabilities(int capabilities) {
- mCapabilities = capabilities;
+ public MmTelCapabilities(@MmTelCapability int capabilities) {
+ super(capabilities);
}
@IntDef(flag = true,
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 852c8e0..2e4bfb3 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -147,7 +147,7 @@
* {@link SmsMessage#FORMAT_3GPP2}.
* @param smsc the Short Message Service Center address.
* @param isRetry whether it is a retry of an already attempted message or not.
- * @param pdu PDUs representing the contents of the message.
+ * @param pdu PDU representing the contents of the message.
*/
public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
byte[] pdu) {
@@ -166,27 +166,29 @@
* provider.
*
* @param token token provided in {@link #onSmsReceived(int, String, byte[])}
+ * @param messageRef the message reference
* @param result result of delivering the message. Valid values are:
* {@link #DELIVER_STATUS_OK},
* {@link #DELIVER_STATUS_ERROR_GENERIC},
* {@link #DELIVER_STATUS_ERROR_NO_MEMORY},
* {@link #DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED}
- * @param messageRef the message reference
*/
- public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) {
+ public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
}
/**
* This method will be triggered by the platform after
- * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the
+ * {@link #onSmsStatusReportReceived(int, int, String, byte[])} or
+ * {@link #onSmsStatusReportReceived(int, String, byte[])} has been called to provide the
* result to the IMS provider.
*
- * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+ * @param token token provided in {@link #onSmsStatusReportReceived(int, int, String, byte[])}
+ * or {@link #onSmsStatusReportReceived(int, String, byte[])}
+ * @param messageRef the message reference
* @param result result of delivering the message. Valid values are:
* {@link #STATUS_REPORT_STATUS_OK},
* {@link #STATUS_REPORT_STATUS_ERROR}
- * @param messageRef the message reference
*/
public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
@@ -204,7 +206,7 @@
* callbacks for this message.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
* {@link SmsMessage#FORMAT_3GPP2}.
- * @param pdu PDUs representing the contents of the message.
+ * @param pdu PDU representing the contents of the message.
* @throws RuntimeException if called before {@link #onReady()} is triggered.
*/
public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
@@ -285,23 +287,32 @@
}
/**
- * Sets the status report of the sent message.
+ * This method should be triggered by the IMS providers when the status report of the sent
+ * message is received. The platform will handle the report and notify the IMS provider of the
+ * result by calling {@link #acknowledgeSmsReport(int, int, int)}.
*
+ * This method must not be called before {@link #onReady()} is called or the call will fail. If
+ * the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
+ * with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
* @param messageRef the message reference.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
- * @param pdu PDUs representing the content of the status report.
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDU representing the content of the status report.
* @throws RuntimeException if called before {@link #onReady()} is triggered
+ *
+ * @deprecated Use {@link #onSmsStatusReportReceived(int, String, byte[])} instead without the
+ * message reference.
*/
+ @Deprecated
public final void onSmsStatusReportReceived(int token, int messageRef, String format,
- byte[] pdu) throws RuntimeException{
+ byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
}
try {
- mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
+ mListener.onSmsStatusReportReceived(token, format, pdu);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR);
@@ -310,6 +321,46 @@
}
/**
+ * This method should be triggered by the IMS providers when the status report of the sent
+ * message is received. The platform will handle the report and notify the IMS provider of the
+ * result by calling {@link #acknowledgeSmsReport(int, int, int)}.
+ *
+ * This method must not be called before {@link #onReady()} is called or the call will fail. If
+ * the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
+ * with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
+ * @param token unique token generated by IMS providers that the platform will use to trigger
+ * callbacks for this message.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param pdu PDU representing the content of the status report.
+ * @throws RuntimeException if called before {@link #onReady()} is triggered
+ */
+ public final void onSmsStatusReportReceived(int token, String format, byte[] pdu)
+ throws RuntimeException {
+ synchronized (mLock) {
+ if (mListener == null) {
+ throw new RuntimeException("Feature not ready.");
+ }
+ try {
+ mListener.onSmsStatusReportReceived(token, format, pdu);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
+ SmsMessage message = SmsMessage.createFromPdu(pdu, format);
+ if (message != null && message.mWrappedSmsMessage != null) {
+ acknowledgeSmsReport(
+ token,
+ message.mWrappedSmsMessage.mMessageRef,
+ STATUS_REPORT_STATUS_ERROR);
+ } else {
+ Log.w(LOG_TAG,
+ "onSmsStatusReportReceivedWithoutMessageRef: Invalid pdu entered.");
+ acknowledgeSmsReport(token, 0, STATUS_REPORT_STATUS_ERROR);
+ }
+ }
+ }
+ }
+
+ /**
* Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
* Provider.
*
diff --git a/telephony/java/com/android/internal/telephony/CbGeoUtils.java b/telephony/java/com/android/internal/telephony/CbGeoUtils.java
index 73dd822..0b73252 100644
--- a/telephony/java/com/android/internal/telephony/CbGeoUtils.java
+++ b/telephony/java/com/android/internal/telephony/CbGeoUtils.java
@@ -299,7 +299,8 @@
* @return the encoded string.
*/
@NonNull
- public static String encodeGeometriesToString(@NonNull List<Geometry> geometries) {
+ public static String encodeGeometriesToString(List<Geometry> geometries) {
+ if (geometries == null || geometries.isEmpty()) return "";
return geometries.stream()
.map(geometry -> encodeGeometryToString(geometry))
.filter(encodedStr -> !TextUtils.isEmpty(encodedStr))
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 4a263f0..90019ee 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -58,6 +58,8 @@
void onRadioPowerStateChanged(in int state);
void onCallAttributesChanged(in CallAttributes callAttributes);
void onEmergencyNumberListChanged(in Map emergencyNumberList);
+ void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber);
+ void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber);
void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause);
void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a2dcc78..b9e6c93 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2003,6 +2003,13 @@
*/
int getRadioHalVersion();
+ /**
+ * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
+ * on the UICC card.
+ * @hide
+ */
+ boolean isApplicationOnUicc(int subId, int appType);
+
boolean isModemEnabledForSlot(int slotIndex, String callingPackage);
boolean isDataEnabledForApn(int apnType, int subId, String callingPackage);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index dca4e6b..6eea118 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -104,7 +104,7 @@
header.getSerialNumber(), location, header.getServiceCategory(), null,
getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
- header.getCmasInfo(), null /* geometries */, receivedTimeMillis);
+ header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis);
} else if (header.isUmtsFormat()) {
// UMTS format has only 1 PDU
byte[] pdu = pdus[0];
@@ -120,9 +120,13 @@
// Has Warning Area Coordinates information
List<Geometry> geometries = null;
+ int maximumWaitingTimeSec = 255;
if (pdu.length > wacDataOffset) {
try {
- geometries = parseWarningAreaCoordinates(pdu, wacDataOffset);
+ Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
+ wacDataOffset);
+ maximumWaitingTimeSec = wac.first;
+ geometries = wac.second;
} catch (Exception ex) {
// Catch the exception here, the message will be considered as having no WAC
// information which means the message will be broadcasted directly.
@@ -133,7 +137,8 @@
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
header.getGeographicalScope(), header.getSerialNumber(), location,
header.getServiceCategory(), language, body, priority,
- header.getEtwsInfo(), header.getCmasInfo(), geometries, receivedTimeMillis);
+ header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
+ receivedTimeMillis);
} else {
String language = null;
StringBuilder sb = new StringBuilder();
@@ -148,7 +153,7 @@
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
header.getGeographicalScope(), header.getSerialNumber(), location,
header.getServiceCategory(), language, sb.toString(), priority,
- header.getEtwsInfo(), header.getCmasInfo(), null /* geometries */,
+ header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
receivedTimeMillis);
}
}
@@ -197,7 +202,17 @@
}
}
- private static List<Geometry> parseWarningAreaCoordinates(byte[] pdu, int wacOffset) {
+ /**
+ * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
+ *
+ * @param pdu Warning Area Coordinates TLV.
+ * @param wacOffset the offset of Warning Area Coordinates TLV.
+ * @return a pair with the first element is maximum wait time and the second is the broadcast
+ * area. The default value of the maximum wait time is 255 which means use the device default
+ * value.
+ */
+ private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
+ byte[] pdu, int wacOffset) {
// little-endian
int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset];
int offset = wacOffset + 2;
@@ -209,6 +224,8 @@
BitStreamReader bitReader = new BitStreamReader(pdu, offset);
+ int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
+
List<Geometry> geo = new ArrayList<>();
int remainedBytes = wacDataLength;
while (remainedBytes > 0) {
@@ -220,8 +237,7 @@
switch (type) {
case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
- // TODO: handle the maximum wait time in cell broadcast provider.
- int maximumWaitTimeSec = bitReader.read(8);
+ maximumWaitTimeSec = bitReader.read(8);
break;
case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
List<LatLng> latLngs = new ArrayList<>();
@@ -247,7 +263,7 @@
throw new IllegalArgumentException("Unsupported geoType = " + type);
}
}
- return geo;
+ return new Pair(maximumWaitTimeSec, geo);
}
/**
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 502aa97..e91abb6 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -20,8 +20,6 @@
"libdl_android",
"libhidl-gen-utils",
"libhidlbase",
- "libhidltransport",
- "libhwbinder",
"libjsoncpp",
"liblog",
"liblzma",