Merge "Add source style for each TypedValue."
diff --git a/Android.bp b/Android.bp
index c202408..b4d5e58 100644
--- a/Android.bp
+++ b/Android.bp
@@ -305,6 +305,7 @@
"core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl",
"core/java/android/service/gatekeeper/IGateKeeperService.aidl",
"core/java/android/service/contentcapture/IContentCaptureService.aidl",
+ "core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl",
"core/java/android/service/notification/INotificationListener.aidl",
"core/java/android/service/notification/IStatusBarNotificationHolder.aidl",
"core/java/android/service/notification/IConditionListener.aidl",
@@ -1317,7 +1318,6 @@
"ext",
"framework",
"voip-common",
- "android.test.mock.impl",
],
local_sourcepaths: frameworks_base_subdirs,
installable: false,
diff --git a/api/current.txt b/api/current.txt
index 2357d08..3d6645b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16458,9 +16458,9 @@
public class BiometricManager {
method public int canAuthenticate();
- field public static final int BIOMETRIC_ERROR_NO_BIOMETRICS = 11; // 0xb
+ field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
+ field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
- field public static final int BIOMETRIC_ERROR_UNAVAILABLE = 1; // 0x1
field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
}
@@ -16503,6 +16503,7 @@
method public android.hardware.biometrics.BiometricPrompt build();
method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(java.lang.CharSequence);
method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(java.lang.CharSequence, java.util.concurrent.Executor, android.content.DialogInterface.OnClickListener);
+ method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean);
method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(java.lang.CharSequence);
method public android.hardware.biometrics.BiometricPrompt.Builder setTitle(java.lang.CharSequence);
}
@@ -53014,19 +53015,8 @@
ctor public InspectionCompanion.UninitializedPropertyMapException();
}
- public final class IntEnumMapping {
- method public java.lang.String nameOf(int);
- }
-
- public static final class IntEnumMapping.Builder {
- ctor public IntEnumMapping.Builder();
- method public android.view.inspector.IntEnumMapping.Builder addValue(java.lang.String, int);
- method public android.view.inspector.IntEnumMapping build();
- method public void clear();
- }
-
public final class IntFlagMapping {
- method public java.lang.String[] namesOf(int);
+ method public java.util.Set<java.lang.String> get(int);
}
public static final class IntFlagMapping.Builder {
@@ -53034,7 +53024,6 @@
method public android.view.inspector.IntFlagMapping.Builder addFlag(java.lang.String, int);
method public android.view.inspector.IntFlagMapping.Builder addFlag(java.lang.String, int, int);
method public android.view.inspector.IntFlagMapping build();
- method public void clear();
}
public abstract interface PropertyMapper {
@@ -53046,7 +53035,7 @@
method public abstract int mapFloat(java.lang.String, int);
method public abstract int mapGravity(java.lang.String, int);
method public abstract int mapInt(java.lang.String, int);
- method public abstract int mapIntEnum(java.lang.String, int, android.view.inspector.IntEnumMapping);
+ method public abstract int mapIntEnum(java.lang.String, int, android.util.SparseArray<java.lang.String>);
method public abstract int mapIntFlag(java.lang.String, int, android.view.inspector.IntFlagMapping);
method public abstract int mapLong(java.lang.String, int);
method public abstract int mapObject(java.lang.String, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 276be9d..83d1da7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -94,7 +94,6 @@
field public static final java.lang.String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
field public static final java.lang.String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
field public static final java.lang.String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
- field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
field public static final java.lang.String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
@@ -151,6 +150,7 @@
field public static final java.lang.String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
field public static final java.lang.String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
field public static final java.lang.String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
+ field public static final java.lang.String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
field public static final java.lang.String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
field public static final java.lang.String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final java.lang.String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -4445,6 +4445,10 @@
method public abstract java.lang.Object onTransactStarted(android.os.IBinder, int);
}
+ public static class Build.VERSION {
+ field public static final java.lang.String PREVIEW_SDK_FINGERPRINT;
+ }
+
public final class ConfigUpdate {
field public static final java.lang.String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
field public static final java.lang.String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 4beb699..5a7ec8b 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -1,3 +1,11 @@
+package android {
+
+ public static final class Manifest.permission {
+ field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
+ }
+
+}
+
package android.app {
public class Notification implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 71f02b9..6ddb341 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -509,6 +509,11 @@
method public static android.graphics.ImageDecoder.Source createSource(android.content.res.Resources, java.io.InputStream, int);
}
+ public class Paint {
+ method public void setColor(long);
+ method public void setShadowLayer(float, float, float, long);
+ }
+
}
package android.graphics.drawable {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f4a1715..453a0c0 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -25,6 +25,7 @@
import "frameworks/base/core/proto/android/app/settings_enums.proto";
import "frameworks/base/core/proto/android/app/job/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/enums.proto";
+import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
@@ -183,6 +184,7 @@
RescuePartyResetReported rescue_party_reset_reported = 122;
SignedConfigReported signed_config_reported = 123;
GnssNiEventReported gnss_ni_event_reported = 124;
+ BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125;
}
// Pulled events will start at field 10000.
@@ -1316,6 +1318,90 @@
optional int32 bt_profile = 3;
}
+// Logs when there is an event affecting Bluetooth device's link layer connection.
+// - This event is triggered when there is a related HCI command or event
+// - Users of this metrics can deduce Bluetooth device's connection state from these events
+// - HCI commands are logged before the command is sent, after receiving command status, and after
+// receiving command complete
+// - HCI events are logged when they arrive
+//
+// Low level log from system/bt
+//
+// Bluetooth classic commands:
+// - CMD_CREATE_CONNECTION
+// - CMD_DISCONNECT
+// - CMD_CREATE_CONNECTION_CANCEL
+// - CMD_ACCEPT_CONNECTION_REQUEST
+// - CMD_REJECT_CONNECTION_REQUEST
+// - CMD_SETUP_ESCO_CONNECTION
+// - CMD_ACCEPT_ESCO_CONNECTION
+// - CMD_REJECT_ESCO_CONNECTION
+// - CMD_ENH_SETUP_ESCO_CONNECTION
+// - CMD_ENH_ACCEPT_ESCO_CONNECTION
+//
+// Bluetooth low energy commands:
+// - CMD_BLE_CREATE_LL_CONN [Only logged on error or when initiator filter policy is 0x00]
+// - CMD_BLE_CREATE_CONN_CANCEL [Only logged when there is an error]
+// - CMD_BLE_EXTENDED_CREATE_CONNECTION [Only logged on error or when initiator filter policy is 0x00]
+// - CMD_BLE_CLEAR_WHITE_LIST
+// - CMD_BLE_ADD_WHITE_LIST
+// - CMD_BLE_REMOVE_WHITE_LIST
+//
+// Bluetooth classic events:
+// - EVT_CONNECTION_COMP
+// - EVT_CONNECTION_REQUEST
+// - EVT_DISCONNECTION_COMP
+// - EVT_ESCO_CONNECTION_COMP
+// - EVT_ESCO_CONNECTION_CHANGED
+//
+// Bluetooth low energy meta events:
+// - BLE_EVT_CONN_COMPLETE_EVT
+// - BLE_EVT_ENHANCED_CONN_COMPLETE_EVT
+//
+// Next tag: 10
+message BluetoothLinkLayerConnectionEvent {
+ // An identifier that can be used to match events for this device.
+ // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+ // Salt: Randomly generated 256 bit value
+ // Hash algorithm: HMAC-SHA256
+ // Size: 32 byte
+ // Default: null or empty if the device identifier is not known
+ optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+ // Connection handle of this connection if available
+ // Range: 0x0000 - 0x0EFF (12 bits)
+ // Default: 0xFFFF if the handle is unknown
+ optional int32 connection_handle = 2;
+ // Direction of the link
+ // Default: DIRECTION_UNKNOWN
+ optional android.bluetooth.DirectionEnum direction = 3;
+ // Type of this link
+ // Default: LINK_TYPE_UNKNOWN
+ optional android.bluetooth.LinkTypeEnum type = 4;
+
+ // Reason metadata for this link layer connection event, rules for interpretation:
+ // 1. If hci_cmd is set and valid, hci_event can be either EVT_COMMAND_STATUS or
+ // EVT_COMMAND_COMPLETE, ignore hci_ble_event in this case
+ // 2. If hci_event is set to EVT_BLE_META, look at hci_ble_event; otherwise, if hci_event is
+ // set and valid, ignore hci_ble_event
+
+ // HCI command associated with this event
+ // Default: CMD_UNKNOWN
+ optional android.bluetooth.hci.CommandEnum hci_cmd = 5;
+ // HCI event associated with this event
+ // Default: EVT_UNKNOWN
+ optional android.bluetooth.hci.EventEnum hci_event = 6;
+ // HCI BLE meta event associated with this event
+ // Default: BLE_EVT_UNKNOWN
+ optional android.bluetooth.hci.BleMetaEventEnum hci_ble_event = 7;
+ // HCI command status code if this is triggerred by hci_cmd
+ // Default: STATUS_UNKNOWN
+ optional android.bluetooth.hci.StatusEnum cmd_status = 8;
+ // HCI reason code associated with this event
+ // Default: STATUS_UNKNOWN
+ optional android.bluetooth.hci.StatusEnum reason_code = 9;
+}
+
+
/**
* Logs when something is plugged into or removed from the USB-C connector.
*
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 4d3c5d1..5392a3c 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -1492,7 +1492,6 @@
Landroid/test/AndroidTestCase;->getTestContext()Landroid/content/Context;
Landroid/test/AndroidTestCase;->setTestContext(Landroid/content/Context;)V
Landroid/test/InstrumentationTestCase;->runMethod(Ljava/lang/reflect/Method;I)V
-Landroid/test/RepetitiveTest;->numIterations()I
Landroid/util/Singleton;-><init>()V
Landroid/util/XmlPullAttributes;-><init>(Lorg/xmlpull/v1/XmlPullParser;)V
Landroid/util/XmlPullAttributes;->mParser:Lorg/xmlpull/v1/XmlPullParser;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 98c5a0fb..d374f1c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.os.Process.myUid;
import static java.lang.Character.MIN_VALUE;
@@ -1025,7 +1026,7 @@
*/
@Nullable private ContentCaptureManager getContentCaptureManager() {
// ContextCapture disabled for system apps
- if (getApplicationInfo().isSystemApp()) return null;
+ if (!UserHandle.isApp(myUid())) return null;
if (mContentCaptureManager == null) {
mContentCaptureManager = getSystemService(ContentCaptureManager.class);
}
@@ -1048,9 +1049,8 @@
private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
final ContentCaptureManager cm = getContentCaptureManager();
- if (cm == null) {
- return;
- }
+ if (cm == null) return;
+
switch (type) {
case CONTENT_CAPTURE_START:
//TODO(b/111276913): decide whether the InteractionSessionId should be
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d423260..e0b8d78 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3705,11 +3705,16 @@
* Returns whether switching to provided user was successful.
*
* @param user the user to switch to.
+ *
+ * @throws IllegalArgumentException if the user is null.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
- public boolean switchUser(UserHandle user) {
+ public boolean switchUser(@NonNull UserHandle user) {
+ if (user == null) {
+ throw new IllegalArgumentException("UserHandle cannot be null.");
+ }
return switchUser(user.getIdentifier());
}
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 57132a7..ab8f234 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -16,6 +16,10 @@
package android.app;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+
import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager.StackInfo;
@@ -32,7 +36,6 @@
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceSession;
@@ -82,7 +85,6 @@
private boolean mOpened; // Protected by mGuard.
private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
- private Surface mTmpSurface = new Surface();
/** The ActivityView is only allowed to contain one task. */
private final boolean mSingleTaskInstance;
@@ -319,20 +321,20 @@
private class SurfaceCallback implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
- mTmpSurface = new Surface();
if (mVirtualDisplay == null) {
initVirtualDisplay(new SurfaceSession());
if (mVirtualDisplay != null && mActivityViewCallback != null) {
mActivityViewCallback.onActivityViewReady(ActivityView.this);
}
} else {
- // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by
- // whether it has a surface. Setting a fake surface here so DisplayManager will
- // consider this display on.
- mVirtualDisplay.setSurface(mTmpSurface);
mTmpTransaction.reparent(mRootSurfaceControl,
mSurfaceView.getSurfaceControl().getHandle()).apply();
}
+
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.setDisplayState(true);
+ }
+
updateLocation();
}
@@ -346,10 +348,8 @@
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
- mTmpSurface.release();
- mTmpSurface = null;
if (mVirtualDisplay != null) {
- mVirtualDisplay.setSurface(null);
+ mVirtualDisplay.setDisplayState(false);
}
cleanTapExcludeRegion();
}
@@ -370,15 +370,11 @@
final int height = mSurfaceView.getHeight();
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
- // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by
- // whether it has a surface. Setting a fake surface here so DisplayManager will consider
- // this display on.
mVirtualDisplay = displayManager.createVirtualDisplay(
- DISPLAY_NAME + "@" + System.identityHashCode(this),
- width, height, getBaseDisplayDensity(), mTmpSurface,
- DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
+ DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
+ getBaseDisplayDensity(), null,
+ VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
if (mVirtualDisplay == null) {
Log.e(TAG, "Failed to initialize ActivityView");
return;
@@ -443,11 +439,6 @@
displayReleased = false;
}
- if (mTmpSurface != null) {
- mTmpSurface.release();
- mTmpSurface = null;
- }
-
if (displayReleased && mActivityViewCallback != null) {
mActivityViewCallback.onActivityViewDestroyed(this);
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 83563d0..45b5dca 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -137,6 +137,12 @@
public abstract void setLocationPackagesProvider(PackagesProvider provider);
/**
+ * Set the location extra packages provider.
+ * @param provider The packages provider.
+ */
+ public abstract void setLocationExtraPackagesProvider(PackagesProvider provider);
+
+ /**
* Sets the voice interaction packages provider.
* @param provider The packages provider.
*/
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index eb3414d..8aac1bf 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -42,13 +42,13 @@
/**
* The hardware is unavailable. Try again later.
*/
- public static final int BIOMETRIC_ERROR_UNAVAILABLE =
+ public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE =
BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
/**
* The user does not have any biometrics enrolled.
*/
- public static final int BIOMETRIC_ERROR_NO_BIOMETRICS =
+ public static final int BIOMETRIC_ERROR_NONE_ENROLLED =
BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS;
/**
@@ -58,8 +58,8 @@
BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
@IntDef({BIOMETRIC_SUCCESS,
- BIOMETRIC_ERROR_UNAVAILABLE,
- BIOMETRIC_ERROR_NO_BIOMETRICS,
+ BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ BIOMETRIC_ERROR_NONE_ENROLLED,
BIOMETRIC_ERROR_NO_HARDWARE})
@interface BiometricError {}
@@ -95,8 +95,8 @@
* Determine if biometrics can be used. In other words, determine if {@link BiometricPrompt}
* can be expected to be shown (hardware available, templates enrolled, user-enabled).
*
- * @return Returns {@link #BIOMETRIC_ERROR_NO_BIOMETRICS} if the user does not have any
- * enrolled, or {@link #BIOMETRIC_ERROR_UNAVAILABLE} if none are currently
+ * @return Returns {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any
+ * enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are currently
* supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if a biometric can currently be
* used (enrolled and available).
*/
@@ -113,7 +113,7 @@
return BIOMETRIC_ERROR_NO_HARDWARE;
} else {
Slog.w(TAG, "hasEnrolledBiometrics(): Service not connected");
- return BIOMETRIC_ERROR_UNAVAILABLE;
+ return BIOMETRIC_ERROR_HW_UNAVAILABLE;
}
}
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index f652f85..c69b68e4 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -235,7 +235,6 @@
* requiring confirmation.
*
* @param requireConfirmation
- * @hide
*/
public Builder setRequireConfirmation(boolean requireConfirmation) {
mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index cda8498..7e45441 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -436,6 +436,7 @@
public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
try {
mDm.setVirtualDisplaySurface(token, surface);
+ setVirtualDisplayState(token, surface != null);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -458,6 +459,14 @@
}
}
+ void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) {
+ try {
+ mDm.setVirtualDisplayState(token, isOn);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
/**
* Gets the stable device display size, in pixels.
*/
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 2d81cdf..aae8afb 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -82,6 +82,9 @@
// No permissions required but must be same Uid as the creator.
void releaseVirtualDisplay(in IVirtualDisplayCallback token);
+ // No permissions required but must be same Uid as the creator.
+ void setVirtualDisplayState(in IVirtualDisplayCallback token, boolean isOn);
+
// Get a stable metric for the device's display size. No permissions required.
Point getStableDisplaySize();
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index d354666..bf62c95 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -104,6 +104,18 @@
}
}
+ /**
+ * Sets the on/off state for a virtual display.
+ *
+ * @param isOn Whether the display should be on or off.
+ * @hide
+ */
+ public void setDisplayState(boolean isOn) {
+ if (mToken != null) {
+ mGlobal.setVirtualDisplayState(mToken, isOn);
+ }
+ }
+
@Override
public String toString() {
return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken
diff --git a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
index d382eb9..bdd5ab6 100644
--- a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
+++ b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
@@ -15,10 +15,12 @@
*/
package android.hardware.hdmi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -56,6 +58,21 @@
mHandler = handler == null ? new Handler(Looper.getMainLooper()) : handler;
}
+ /**
+ * Callback interface used to get the set System Audio Mode result.
+ *
+ * @hide
+ */
+ // TODO(b/110094868): unhide and add @SystemApi for Q
+ public interface SetSystemAudioModeCallback {
+ /**
+ * Called when the input was changed.
+ *
+ * @param result the result of the set System Audio Mode
+ */
+ void onComplete(int result);
+ }
+
/** @hide */
// TODO(b/110094868): unhide and add @SystemApi for Q
@Override
@@ -117,4 +134,34 @@
mPendingReportAudioStatus = true;
}
}
+
+ /**
+ * Set System Audio Mode on/off with audio system device.
+ *
+ * @param state true to set System Audio Mode on. False to set off.
+ * @param callback callback offer the setting result.
+ *
+ * @hide
+ */
+ // TODO(b/110094868): unhide and add @SystemApi for Q
+ public void setSystemAudioMode(boolean state, @NonNull SetSystemAudioModeCallback callback) {
+ // TODO(amyjojo): implement this when needed.
+ }
+
+ /**
+ * When device is switching to an audio only source, this method is called to broadcast
+ * a setSystemAudioMode on message to the HDMI CEC system without querying Active Source or
+ * TV supporting System Audio Control or not. This is to get volume control passthrough
+ * from STB even if TV does not support it.
+ *
+ * @hide
+ */
+ // TODO(b/110094868): unhide and add @SystemApi for Q
+ public void setSystemAudioModeOnForAudioOnlySource() {
+ try {
+ mService.setSystemAudioModeOnForAudioOnlySource();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to set System Audio Mode on for Audio Only source");
+ }
+ }
}
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index f5d288e..a7734f5 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -33,6 +33,8 @@
import android.util.ArrayMap;
import android.util.Log;
+import java.util.List;
+
/**
* The {@link HdmiControlManager} class is used to send HDMI control messages
* to attached CEC devices.
@@ -404,6 +406,72 @@
}
/**
+ * Get a snapshot of the real-time status of the remote devices.
+ *
+ * @return a list of {@link HdmiDeviceInfo} of the devices connected to the current device.
+ *
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public List<HdmiDeviceInfo> getConnectedDevicesList() {
+ try {
+ return mService.getDeviceList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Power off the target device.
+ *
+ * @param deviceInfo HdmiDeviceInfo of the device to be powered off
+ *
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public void powerOffRemoteDevice(HdmiDeviceInfo deviceInfo) {
+ try {
+ mService.powerOffRemoteDevice(
+ deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Power on the target device.
+ *
+ * @param deviceInfo HdmiDeviceInfo of the device to be powered on
+ *
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
+ try {
+ mService.powerOnRemoteDevice(
+ deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Ask the target device to be the new Active Source.
+ *
+ * @param deviceInfo HdmiDeviceInfo of the target device
+ *
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public void askRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+ try {
+ mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Controls standby mode of the system. It will also try to turn on/off the connected devices if
* necessary.
*
@@ -432,6 +500,19 @@
}
/**
+ * Get the physical address of the device.
+ *
+ * @hide
+ */
+ public int getPhysicalAddress() {
+ try {
+ return mService.getPhysicalAddress();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Listener used to get hotplug event from HDMI port.
*/
public interface HotplugEventListener {
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 2b8d00b..1cd9920 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -50,6 +50,7 @@
List<HdmiPortInfo> getPortInfo();
boolean canChangeSystemAudioMode();
boolean getSystemAudioMode();
+ int getPhysicalAddress();
void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback);
void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
@@ -60,6 +61,9 @@
void setInputChangeListener(IHdmiInputChangeListener listener);
List<HdmiDeviceInfo> getInputDevices();
List<HdmiDeviceInfo> getDeviceList();
+ void powerOffRemoteDevice(int logicalAddress, int powerStatus);
+ void powerOnRemoteDevice(int logicalAddress, int powerStatus);
+ void askRemoteDeviceToBecomeActiveSource(int physicalAddress);
void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
boolean hasVendorId);
void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
@@ -73,4 +77,5 @@
void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener);
void setStandbyMode(boolean isStandbyModeOn);
void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute);
+ void setSystemAudioModeOnForAudioOnlySource();
}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 617125b3..c2963fd 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -191,6 +191,7 @@
}
setMtu(source.mMtu);
mTcpBufferSizes = source.mTcpBufferSizes;
+ mNat64Prefix = source.mNat64Prefix;
}
}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index d7e5b27..b932d21 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -28,6 +28,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* A POD object to represent attributes of a single L2 network entry.
@@ -207,4 +208,52 @@
public int hashCode() {
return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
}
+
+ /** Pretty print */
+ @Override
+ public String toString() {
+ final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
+ final ArrayList<String> nullFields = new ArrayList<>();
+
+ if (null != assignedV4Address) {
+ resultJoiner.add("assignedV4Addr :");
+ resultJoiner.add(assignedV4Address.toString());
+ } else {
+ nullFields.add("assignedV4Addr");
+ }
+
+ if (null != groupHint) {
+ resultJoiner.add("groupHint :");
+ resultJoiner.add(groupHint);
+ } else {
+ nullFields.add("groupHint");
+ }
+
+ if (null != dnsAddresses) {
+ resultJoiner.add("dnsAddr : [");
+ for (final InetAddress addr : dnsAddresses) {
+ resultJoiner.add(addr.getHostAddress());
+ }
+ resultJoiner.add("]");
+ } else {
+ nullFields.add("dnsAddr");
+ }
+
+ if (null != mtu) {
+ resultJoiner.add("mtu :");
+ resultJoiner.add(mtu.toString());
+ } else {
+ nullFields.add("mtu");
+ }
+
+ if (!nullFields.isEmpty()) {
+ resultJoiner.add("; Null fields : [");
+ for (final String field : nullFields) {
+ resultJoiner.add(field);
+ }
+ resultJoiner.add("]");
+ }
+
+ return resultJoiner.toString();
+ }
}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
index 0cb37e9..d040dcc 100644
--- a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -128,4 +128,19 @@
public int hashCode() {
return Objects.hash(l2Key1, l2Key2, confidence);
}
+
+ @Override
+ /** Pretty print */
+ public String toString() {
+ switch (getNetworkSameness()) {
+ case NETWORK_SAME:
+ return "\"" + l2Key1 + "\" same L3 network as \"" + l2Key2 + "\"";
+ case NETWORK_DIFFERENT:
+ return "\"" + l2Key1 + "\" different L3 network from \"" + l2Key2 + "\"";
+ case NETWORK_NEVER_CONNECTED:
+ return "\"" + l2Key1 + "\" can't be tested against \"" + l2Key2 + "\"";
+ default:
+ return "Buggy sameness value ? \"" + l2Key1 + "\", \"" + l2Key2 + "\"";
+ }
+ }
}
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
index 5b016ec..95e5042 100644
--- a/core/java/android/net/ipmemorystore/Status.java
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -26,6 +26,8 @@
public class Status {
public static final int SUCCESS = 0;
+ public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1;
+
public final int resultCode;
public Status(final int resultCode) {
@@ -47,4 +49,14 @@
public boolean isSuccess() {
return SUCCESS == resultCode;
}
+
+ /** Pretty print */
+ @Override
+ public String toString() {
+ switch (resultCode) {
+ case SUCCESS: return "SUCCESS";
+ case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
+ default: return "Unknown value ?!";
+ }
+ }
}
diff --git a/core/java/android/net/ipmemorystore/Utils.java b/core/java/android/net/ipmemorystore/Utils.java
new file mode 100644
index 0000000..73d8c83
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/** {@hide} */
+public class Utils {
+ /** Pretty print */
+ public static String blobToString(final Blob blob) {
+ final StringBuilder sb = new StringBuilder("Blob : [");
+ if (blob.data.length <= 24) {
+ appendByteArray(sb, blob.data, 0, blob.data.length);
+ } else {
+ appendByteArray(sb, blob.data, 0, 16);
+ sb.append("...");
+ appendByteArray(sb, blob.data, blob.data.length - 8, blob.data.length);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ // Adds the hex representation of the array between the specified indices (inclusive, exclusive)
+ private static void appendByteArray(@NonNull final StringBuilder sb, @NonNull final byte[] ar,
+ final int from, final int to) {
+ for (int i = from; i < to; ++i) {
+ sb.append(String.format("%02X", ar[i]));
+ }
+ }
+}
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index b15a4d3..cbb3909 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -114,7 +114,6 @@
}
}
- // TODO(b/111441001) Connect up with BugreportListener methods.
private final class DumpstateListener extends IDumpstateListener.Stub
implements DeathRecipient {
private final BugreportListener mListener;
@@ -130,35 +129,35 @@
@Override
public void onProgress(int progress) throws RemoteException {
- // TODO(b/111441001): implement
+ mListener.onProgress(progress);
}
@Override
public void onError(int errorCode) throws RemoteException {
- // TODO(b/111441001): implement
+ mListener.onError(errorCode);
}
@Override
public void onFinished(long durationMs, String title, String description)
throws RemoteException {
- // TODO(b/111441001): implement
+ mListener.onFinished(durationMs, title, description);
}
// Old methods; should go away
@Override
public void onProgressUpdated(int progress) throws RemoteException {
- // TODO(b/111441001): implement
+ // TODO(b/111441001): remove from interface
}
@Override
public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
- // TODO(b/111441001): implement
+ // TODO(b/111441001): remove from interface
}
@Override
public void onSectionComplete(String title, int status, int size, int durationMs)
throws RemoteException {
- // TODO(b/111441001): implement
+ // TODO(b/111441001): remove from interface
}
}
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9fea873..2d61a4e 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -289,6 +289,26 @@
"ro.build.version.preview_sdk", 0);
/**
+ * The SDK fingerprint for a given prerelease SDK. This value will always be
+ * {@code REL} on production platform builds/devices.
+ *
+ * <p>When this value is not {@code REL}, it contains a string fingerprint of the API
+ * surface exposed by the preview SDK. Preview platforms with different API surfaces
+ * will have different {@code PREVIEW_SDK_FINGERPRINT}.
+ *
+ * <p>This attribute is intended for use by installers for finer grained targeting of
+ * packages. Applications targeting preview APIs should not use this field and should
+ * instead use {@code PREVIEW_SDK_INT} or use reflection or other runtime checks to
+ * detect the presence of an API or guard themselves against unexpected runtime
+ * behavior.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String PREVIEW_SDK_FINGERPRINT = SystemProperties.get(
+ "ro.build.version.preview_sdk_fingerprint", "REL");
+
+ /**
* The current development codename, or the string "REL" if this is
* a release build.
*/
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index fdd7488..8ced722 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -356,16 +356,6 @@
void removeVpnUidRanges(int netId, in UidRange[] ranges);
/**
- * Start the clatd (464xlat) service on the given interface.
- */
- void startClatd(String interfaceName);
-
- /**
- * Stop the clatd (464xlat) service on the given interface.
- */
- void stopClatd(String interfaceName);
-
- /**
* Start listening for mobile activity state changes.
*/
void registerNetworkActivityListener(INetworkActivityListener listener);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8482548..39c4266 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9381,6 +9381,15 @@
"hdmi_system_audio_control_enabled";
/**
+ * Whether HDMI Routing Control feature is enabled. If enabled, the switch device will
+ * route to the correct input source on receiving Routing Control related messages. If
+ * disabled, you can only switch the input via controls on this device.
+ * @hide
+ */
+ public static final String HDMI_CEC_SWITCH_ENABLED =
+ "hdmi_cec_switch_enabled";
+
+ /**
* Whether TV will automatically turn on upon reception of the CEC command
* <Text View On> or <Image View On>. (0 = false, 1 = true)
*
@@ -11082,6 +11091,31 @@
/** {@hide} */
public static final String
BLUETOOTH_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_";
+ /**
+ * Enable/disable radio bug detection
+ *
+ * {@hide}
+ */
+ public static final String
+ ENABLE_RADIO_BUG_DETECTION = "enable_radio_bug_detection";
+
+ /**
+ * Count threshold of RIL wakelock timeout for radio bug detection
+ *
+ * {@hide}
+ */
+ public static final String
+ RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD =
+ "radio_bug_wakelock_timeout_count_threshold";
+
+ /**
+ * Count threshold of RIL system error for radio bug detection
+ *
+ * {@hide}
+ */
+ public static final String
+ RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD =
+ "radio_bug_system_error_count_threshold";
/**
* Activity manager specific settings.
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 0116961..aaba85b 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -42,6 +42,7 @@
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAugmentedAutofillManagerClient;
+import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
@@ -95,10 +96,10 @@
}
@Override
- public void onDestroyFillWindowRequest(int sessionId) {
+ public void onDestroyAllFillWindowsRequest() {
mHandler.sendMessage(
- obtainMessage(AugmentedAutofillService::handleOnDestroyFillWindowRequest,
- AugmentedAutofillService.this, sessionId));
+ obtainMessage(AugmentedAutofillService::handleOnDestroyAllFillWindowsRequest,
+ AugmentedAutofillService.this));
}
};
@@ -185,18 +186,21 @@
new FillCallback(proxy));
}
- private void handleOnDestroyFillWindowRequest(@NonNull int sessionId) {
- AutofillProxy proxy = null;
+ private void handleOnDestroyAllFillWindowsRequest() {
if (mAutofillProxies != null) {
- proxy = mAutofillProxies.get(sessionId);
+ final int size = mAutofillProxies.size();
+ for (int i = 0; i < size; i++) {
+ final int sessionId = mAutofillProxies.keyAt(i);
+ final AutofillProxy proxy = mAutofillProxies.valueAt(i);
+ if (proxy == null) {
+ // TODO(b/111330312): this might be fine, in which case we should logv it
+ Log.w(TAG, "No proxy for session " + sessionId);
+ return;
+ }
+ proxy.destroy();
+ }
+ mAutofillProxies.clear();
}
- if (proxy == null) {
- // TODO(b/111330312): this might be fine, in which case we should logv it
- Log.w(TAG, "No proxy for session " + sessionId);
- return;
- }
- proxy.destroy();
- mAutofillProxies.remove(sessionId);
}
private void handleOnUnbind() {
@@ -350,6 +354,16 @@
}
}
+ public void requestShowFillUi(int width, int height, Rect anchorBounds,
+ IAutofillWindowPresenter presenter) throws RemoteException {
+ mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds,
+ presenter);
+ }
+
+ public void requestHideFillUi() throws RemoteException {
+ mClient.requestHideFillUi(mSessionId, mFocusedId);
+ }
+
private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) {
synchronized (mLock) {
// TODO(b/111330312): should we close the popupwindow if the focused id changed?
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 33b88e42..51b0f01 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -16,22 +16,25 @@
package android.service.autofill.augmented;
import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG;
+import static android.service.autofill.augmented.AugmentedAutofillService.VERBOSE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.Dialog;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
import android.service.autofill.augmented.PresentationParams.Area;
import android.util.Log;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
import android.view.WindowManager;
+import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -71,7 +74,7 @@
/** Indicates the data being shown is a physical address */
public static final long FLAG_METADATA_ADDRESS = 0x1;
- // TODO(b/111330312): add moar flags
+ // TODO(b/111330312): add more flags
/** @hide */
@LongDef(prefix = { "FLAG" }, value = {
@@ -83,8 +86,17 @@
private final Object mLock = new Object();
private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
+ private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter();
+
@GuardedBy("mLock")
- private Dialog mDialog;
+ private WindowManager mWm;
+ @GuardedBy("mLock")
+ private View mFillView;
+ @GuardedBy("mLock")
+ private boolean mShowing;
+ @GuardedBy("mLock")
+ private Rect mBounds;
@GuardedBy("mLock")
private boolean mDestroyed;
@@ -140,51 +152,28 @@
// window instead of destroying. In fact, it might be better to allocate a full window
// initially, which is transparent (and let touches get through) everywhere but in the
// rect boundaries.
- destroy();
// TODO(b/111330312): make sure all touch events are handled, window is always closed,
// etc.
- mDialog = new Dialog(rootView.getContext()) {
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- FillWindow.this.destroy();
+ mWm = rootView.getContext().getSystemService(WindowManager.class);
+ mFillView = rootView;
+ // Listen to the touch outside to destroy the window when typing is detected.
+ mFillView.setOnTouchListener(
+ (view, motionEvent) -> {
+ if (motionEvent.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ if (VERBOSE) Log.v(TAG, "Outside touch detected, hiding the window");
+ hide();
+ }
+ return false;
}
- return false;
- }
- };
- mCloseGuard.open("destroy");
- final Window window = mDialog.getWindow();
- window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
- // Makes sure touch outside the dialog is received by the window behind the dialog.
- window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
- // Makes sure the touch outside the dialog is received by the dialog to dismiss it.
- window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
- // Makes sure keyboard shows up.
- window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
-
- final int height = rect.bottom - rect.top;
- final int width = rect.right - rect.left;
- final WindowManager.LayoutParams windowParams = window.getAttributes();
- windowParams.gravity = Gravity.TOP | Gravity.LEFT;
- windowParams.y = rect.top + height;
- windowParams.height = height;
- windowParams.x = rect.left;
- windowParams.width = width;
-
- window.setAttributes(windowParams);
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- window.setBackgroundDrawableResource(android.R.color.transparent);
-
- mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
- final ViewGroup.LayoutParams diagParams = new ViewGroup.LayoutParams(width, height);
- mDialog.setContentView(rootView, diagParams);
-
+ );
+ mShowing = false;
+ mBounds = new Rect(area.getBounds());
if (DEBUG) {
Log.d(TAG, "Created FillWindow: params= " + smartSuggestion + " view=" + rootView);
}
-
+ mDestroyed = false;
mProxy.setFillWindow(this);
return true;
}
@@ -194,36 +183,87 @@
void show() {
// TODO(b/111330312): check if updated first / throw exception
if (DEBUG) Log.d(TAG, "show()");
-
synchronized (mLock) {
checkNotDestroyedLocked();
- if (mDialog == null) {
+ if (mWm == null || mFillView == null) {
throw new IllegalStateException("update() not called yet, or already destroyed()");
}
-
- mDialog.show();
if (mProxy != null) {
+ try {
+ mProxy.requestShowFillUi(mBounds.right - mBounds.left,
+ mBounds.bottom - mBounds.top,
+ /*anchorBounds=*/ null, mFillWindowPresenter);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error requesting to show fill window", e);
+ }
mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
}
}
}
/**
+ * Hides the window.
+ *
+ * <p>The window is not destroyed and can be shown again
+ */
+ private void hide() {
+ if (DEBUG) Log.d(TAG, "hide()");
+ synchronized (mLock) {
+ checkNotDestroyedLocked();
+ if (mWm == null || mFillView == null) {
+ throw new IllegalStateException("update() not called yet, or already destroyed()");
+ }
+ if (mProxy != null && mShowing) {
+ try {
+ mProxy.requestHideFillUi();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error requesting to hide fill window", e);
+ }
+ }
+ }
+ }
+
+ private void handleShow(WindowManager.LayoutParams p) {
+ if (DEBUG) Log.d(TAG, "handleShow()");
+ synchronized (mLock) {
+ if (mWm != null && mFillView != null) {
+ p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+ if (!mShowing) {
+ mWm.addView(mFillView, p);
+ mShowing = true;
+ } else {
+ mWm.updateViewLayout(mFillView, p);
+ }
+ }
+ }
+ }
+
+ private void handleHide() {
+ if (DEBUG) Log.d(TAG, "handleHide()");
+ synchronized (mLock) {
+ if (mWm != null && mFillView != null && mShowing) {
+ mWm.removeView(mFillView);
+ mShowing = false;
+ }
+ }
+ }
+
+ /**
* Destroys the window.
*
* <p>Once destroyed, this window cannot be used anymore
*/
public void destroy() {
- if (DEBUG) Log.d(TAG, "destroy(): mDestroyed=" + mDestroyed + " mDialog=" + mDialog);
-
- synchronized (this) {
- if (mDestroyed || mDialog == null) return;
-
- mDialog.dismiss();
- mDialog = null;
- if (mProxy != null) {
- mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
- }
+ if (DEBUG) {
+ Log.d(TAG,
+ "destroy(): mDestroyed=" + mDestroyed + " mShowing=" + mShowing + " mFillView="
+ + mFillView);
+ }
+ synchronized (mLock) {
+ if (mDestroyed) return;
+ hide();
+ mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
+ mDestroyed = true;
mCloseGuard.close();
}
}
@@ -250,11 +290,15 @@
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
synchronized (this) {
pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
- if (mDialog != null) {
- pw.print(prefix); pw.print("dialog: ");
- pw.println(mDialog.isShowing() ? "shown" : "hidden");
- pw.print(prefix); pw.print("window: ");
- pw.println(mDialog.getWindow().getAttributes());
+ if (mFillView != null) {
+ pw.print(prefix); pw.print("fill window: ");
+ pw.println(mShowing ? "shown" : "hidden");
+ pw.print(prefix); pw.print("fill view: ");
+ pw.println(mFillView);
+ pw.print(prefix); pw.print("mBounds: ");
+ pw.println(mBounds);
+ pw.print(prefix); pw.print("mWm: ");
+ pw.println(mWm);
}
}
}
@@ -264,4 +308,19 @@
public void close() throws Exception {
destroy();
}
+
+ private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
+ @Override
+ public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
+ boolean fitsSystemWindows, int layoutDirection) {
+ if (DEBUG) Log.d(TAG, "FillWindowPresenter.show()");
+ mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p));
+ }
+
+ @Override
+ public void hide(Rect transitionEpicenter) {
+ if (DEBUG) Log.d(TAG, "FillWindowPresenter.hide()");
+ mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this));
+ }
+ }
}
diff --git a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
index b3ac2da..fb6912a 100644
--- a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
+++ b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
@@ -36,5 +36,5 @@
in ComponentName activityComponent, in AutofillId focusedId,
in AutofillValue focusedValue, long requestTime, in IFillCallback callback);
- void onDestroyFillWindowRequest(int sessionId);
+ void onDestroyAllFillWindowsRequest();
}
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index e5e028d..302e1a6 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -48,6 +48,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -77,6 +78,7 @@
"android.service.contentcapture.ContentCaptureService";
private Handler mHandler;
+ private IContentCaptureServiceCallback mCallback;
/**
* Binder that receives calls from the system server.
@@ -84,9 +86,15 @@
private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {
@Override
- public void onConnectedStateChanged(boolean state) {
- mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnConnectedStateChanged,
- ContentCaptureService.this, state));
+ public void onConnected(IBinder callback) {
+ mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnConnected,
+ ContentCaptureService.this, callback));
+ }
+
+ @Override
+ public void onDisconnected() {
+ mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDisconnected,
+ ContentCaptureService.this));
}
@Override
@@ -166,7 +174,16 @@
*/
public final void setContentCaptureWhitelist(@Nullable List<String> packages,
@Nullable List<ComponentName> activities) {
- //TODO(b/122595322): implement
+ final IContentCaptureServiceCallback callback = mCallback;
+ if (callback == null) {
+ Log.w(TAG, "setContentCaptureWhitelist(): no server callback");
+ return;
+ }
+ try {
+ callback.setContentCaptureWhitelist(packages, activities);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/**
@@ -177,7 +194,16 @@
*/
public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
boolean enabled) {
- //TODO(b/122595322): implement
+ final IContentCaptureServiceCallback callback = mCallback;
+ if (callback == null) {
+ Log.w(TAG, "setActivityContentCaptureEnabled(): no server callback");
+ return;
+ }
+ try {
+ callback.setActivityContentCaptureEnabled(activity, enabled);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/**
@@ -188,7 +214,16 @@
*/
public final void setPackageContentCaptureEnabled(@NonNull String packageName,
boolean enabled) {
- //TODO(b/122595322): implement
+ final IContentCaptureServiceCallback callback = mCallback;
+ if (callback == null) {
+ Log.w(TAG, "setPackageContentCaptureEnabled(): no server callback");
+ return;
+ }
+ try {
+ callback.setPackageContentCaptureEnabled(packageName, enabled);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/**
@@ -197,7 +232,12 @@
*/
@NonNull
public final Set<ComponentName> getContentCaptureDisabledActivities() {
- //TODO(b/122595322): implement
+ final IContentCaptureServiceCallback callback = mCallback;
+ if (callback == null) {
+ Log.w(TAG, "getContentCaptureDisabledActivities(): no server callback");
+ return Collections.emptySet();
+ }
+ //TODO(b/122595322): implement (using SyncResultReceiver)
return null;
}
@@ -207,7 +247,12 @@
*/
@NonNull
public final Set<String> getContentCaptureDisabledPackages() {
- //TODO(b/122595322): implement
+ final IContentCaptureServiceCallback callback = mCallback;
+ if (callback == null) {
+ Log.w(TAG, "getContentCaptureDisabledPackages(): no server callback");
+ return Collections.emptySet();
+ }
+ //TODO(b/122595322): implement (using SyncResultReceiver)
return null;
}
@@ -307,12 +352,14 @@
}
}
- private void handleOnConnectedStateChanged(boolean state) {
- if (state) {
- onConnected();
- } else {
- onDisconnected();
- }
+ private void handleOnConnected(@NonNull IBinder callback) {
+ mCallback = IContentCaptureServiceCallback.Stub.asInterface(callback);
+ onConnected();
+ }
+
+ private void handleOnDisconnected() {
+ onDisconnected();
+ mCallback = null;
}
//TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session,
@@ -323,15 +370,21 @@
mSessionUids.put(sessionId, uid);
onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
- final int flags = context.getFlags();
- if ((flags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_BY_FLAG_SECURE,
- mClientInterface.asBinder());
- return;
+ final int clientFlags = context.getFlags();
+ int stateFlags = 0;
+ if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
+ stateFlags |= ContentCaptureSession.STATE_FLAG_SECURE;
}
+ if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0) {
+ stateFlags |= ContentCaptureSession.STATE_BY_APP;
+ }
+ if (stateFlags == 0) {
+ stateFlags = ContentCaptureSession.STATE_ACTIVE;
+ } else {
+ stateFlags |= ContentCaptureSession.STATE_DISABLED;
- setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE,
- mClientInterface.asBinder());
+ }
+ setClientState(clientReceiver, stateFlags, mClientInterface.asBinder());
}
private void handleSendEvents(int uid,
diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl
index 1b4cccf..a8dd213 100644
--- a/core/java/android/service/contentcapture/IContentCaptureService.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl
@@ -16,6 +16,7 @@
package android.service.contentcapture;
+import android.os.IBinder;
import android.service.contentcapture.SnapshotData;
import android.view.contentcapture.ContentCaptureContext;
@@ -29,7 +30,8 @@
* @hide
*/
oneway interface IContentCaptureService {
- void onConnectedStateChanged(boolean state);
+ void onConnected(IBinder callback);
+ void onDisconnected();
void onSessionStarted(in ContentCaptureContext context, String sessionId, int uid,
in IResultReceiver clientReceiver);
void onSessionFinished(String sessionId);
diff --git a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
new file mode 100644
index 0000000..e84bd6f
--- /dev/null
+++ b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.contentcapture;
+
+import android.content.ComponentName;
+import com.android.internal.os.IResultReceiver;
+
+import java.util.List;
+
+/**
+ * Interface from the Content Capture service to the system.
+ *
+ * @hide
+ */
+oneway interface IContentCaptureServiceCallback {
+ void setContentCaptureWhitelist(in List<String> packages, in List<ComponentName> activities);
+ void setActivityContentCaptureEnabled(in ComponentName activity, boolean enabled);
+ void setPackageContentCaptureEnabled(in String packageName, boolean enabled);
+ void getContentCaptureDisabledActivities(in IResultReceiver receiver);
+ void getContentCaptureDisabledPackages(in IResultReceiver receiver);
+}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 85b6b88..44353b1 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -33,8 +33,7 @@
import android.icu.util.ULocale;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.SystemProperties;
-import android.provider.Settings;
+import android.sysprop.DisplayProperties;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AccessibilityClickableSpan;
import android.text.style.AccessibilityURLSpan;
@@ -2059,7 +2058,7 @@
return ((locale != null && !locale.equals(Locale.ROOT)
&& ULocale.forLocale(locale).isRightToLeft())
// If forcing into RTL layout mode, return RTL as default
- || SystemProperties.getBoolean(Settings.Global.DEVELOPMENT_FORCE_RTL, false))
+ || DisplayProperties.debug_force_rtl().orElse(false))
? View.LAYOUT_DIRECTION_RTL
: View.LAYOUT_DIRECTION_LTR;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index abefd55..32974ac 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -76,8 +76,8 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.Trace;
+import android.sysprop.DisplayProperties;
import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -812,14 +812,6 @@
private static final String CONTENT_CAPTURE_LOG_TAG = "View.ContentCapture";
/**
- * When set to true, apps will draw debugging information about their layouts.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public static final String DEBUG_LAYOUT_PROPERTY = "debug.layout";
-
- /**
* When set to true, this view will save its attribute data.
*
* @hide
@@ -27833,7 +27825,7 @@
/**
* Show where the margins, bounds and layout bounds are for each view.
*/
- boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false);
+ boolean mDebugLayout = DisplayProperties.debug_layout().orElse(false);
/**
* Point used to compute visible regions.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8e4dc67..27d4ea4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -75,6 +75,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
+import android.sysprop.DisplayProperties;
import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -7071,7 +7072,7 @@
}
// Layout debugging
- boolean layout = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false);
+ boolean layout = DisplayProperties.debug_layout().orElse(false);
if (layout != mAttachInfo.mDebugLayout) {
mAttachInfo.mDebugLayout = layout;
if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index c630177..dfd9a2e 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -187,6 +187,7 @@
/**
* An animation listener to be notified when the animation starts, ends or repeats.
*/
+ @UnsupportedAppUsage
private AnimationListener mListener;
/**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 93941d0..888a4c5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2997,5 +2997,23 @@
afm.post(() -> afm.autofill(sessionId, ids, values));
}
}
+
+ @Override
+ public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+ Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
+ presenter));
+ }
+ }
+
+ @Override
+ public void requestHideFillUi(int sessionId, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.requestHideFillUi(id, false));
+ }
+ }
}
}
diff --git a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
index 67cd0bf..140507c 100644
--- a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
/**
* Object running in the application process and responsible to provide the functionalities
@@ -29,6 +30,24 @@
* @hide
*/
interface IAugmentedAutofillManagerClient {
- Rect getViewCoordinates(in AutofillId id);
- void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values);
+ /**
+ * Gets the coordinates of the input field view.
+ */
+ Rect getViewCoordinates(in AutofillId id);
+
+ /**
+ * Autofills the activity with the contents of the values.
+ */
+ void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values);
+
+ /**
+ * Requests showing the fill UI.
+ */
+ void requestShowFillUi(int sessionId, in AutofillId id, int width, int height,
+ in Rect anchorBounds, in IAutofillWindowPresenter presenter);
+
+ /**
+ * Requests hiding the fill UI.
+ */
+ void requestHideFillUi(int sessionId, in AutofillId id);
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index ff45efd..81b2e01 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -29,12 +29,12 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
-import java.util.concurrent.atomic.AtomicBoolean;
/*
* NOTE: all methods in this class should return right away, or do the real work in a handler
@@ -62,8 +62,10 @@
static final boolean VERBOSE = false;
static final boolean DEBUG = true; // STOPSHIP if not set to false
- @NonNull
- private final AtomicBoolean mDisabled = new AtomicBoolean();
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mDisabled;
@NonNull
private final Context mContext;
@@ -71,11 +73,16 @@
@Nullable
private final IContentCaptureManager mService;
+ // Flags used for starting session.
+ @GuardedBy("mLock")
+ private int mFlags;
+
// TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
// held at the Application level
@NonNull
private final Handler mHandler;
+ @GuardedBy("mLock")
private MainContentCaptureSession mMainSession;
/** @hide */
@@ -114,20 +121,25 @@
@NonNull
@UiThread
public MainContentCaptureSession getMainContentCaptureSession() {
- if (mMainSession == null) {
- mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
- mDisabled);
- if (VERBOSE) {
- Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
+ synchronized (mLock) {
+ if (mMainSession == null) {
+ mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
+ mDisabled);
+ if (VERBOSE) {
+ Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
+ }
}
+ return mMainSession;
}
- return mMainSession;
}
/** @hide */
public void onActivityStarted(@NonNull IBinder applicationToken,
@NonNull ComponentName activityComponent, int flags) {
- getMainContentCaptureSession().start(applicationToken, activityComponent, flags);
+ synchronized (mLock) {
+ mFlags |= flags;
+ getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags);
+ }
}
/** @hide */
@@ -173,7 +185,9 @@
* Checks whether content capture is enabled for this activity.
*/
public boolean isContentCaptureEnabled() {
- return mService != null && !mDisabled.get();
+ synchronized (mLock) {
+ return mService != null && !mDisabled;
+ }
}
/**
@@ -183,7 +197,9 @@
* it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
*/
public void setContentCaptureEnabled(boolean enabled) {
- //TODO(b/111276913): implement (need to finish / disable all sessions)
+ synchronized (mLock) {
+ mFlags |= enabled ? 0 : ContentCaptureContext.FLAG_DISABLED_BY_APP;
+ }
}
/**
@@ -198,20 +214,22 @@
/** @hide */
public void dump(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.println("ContentCaptureManager");
-
- pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled.get());
- pw.print(prefix); pw.print("Context: "); pw.println(mContext);
- pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
- if (mService != null) {
- pw.print(prefix); pw.print("Service: "); pw.println(mService);
- }
- if (mMainSession != null) {
- final String prefix2 = prefix + " ";
- pw.print(prefix); pw.println("Main session:");
- mMainSession.dump(prefix2, pw);
- } else {
- pw.print(prefix); pw.println("No sessions");
+ synchronized (mLock) {
+ pw.print(prefix); pw.println("ContentCaptureManager");
+ pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
+ pw.print(prefix); pw.print("Context: "); pw.println(mContext);
+ pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
+ if (mService != null) {
+ pw.print(prefix); pw.print("Service: "); pw.println(mService);
+ }
+ pw.print(prefix); pw.print("Flags: "); pw.println(mFlags);
+ if (mMainSession != null) {
+ final String prefix2 = prefix + " ";
+ pw.print(prefix); pw.println("Main session:");
+ mMainSession.dump(prefix2, pw);
+ } else {
+ pw.print(prefix); pw.println("No sessions");
+ }
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index d9a8416..2123308 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -21,6 +21,7 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.DebugUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewStructure;
@@ -56,42 +57,58 @@
*
* @hide
*/
- public static final int STATE_UNKNOWN = 0;
+ // NOTE: not prefixed by STATE_ so it's not printed on getStateAsString()
+ public static final int UNKNWON_STATE = 0x0;
/**
* Service's startSession() was called, but server didn't confirm it was created yet.
*
* @hide
*/
- public static final int STATE_WAITING_FOR_SERVER = 1;
+ public static final int STATE_WAITING_FOR_SERVER = 0x1;
/**
* Session is active.
*
* @hide
*/
- public static final int STATE_ACTIVE = 2;
+ public static final int STATE_ACTIVE = 0x2;
/**
* Session is disabled because there is no service for this user.
*
* @hide
*/
- public static final int STATE_DISABLED_NO_SERVICE = 3;
+ public static final int STATE_DISABLED = 0x4;
/**
* Session is disabled because its id already existed on server.
*
* @hide
*/
- public static final int STATE_DISABLED_DUPLICATED_ID = 4;
+ public static final int STATE_DUPLICATED_ID = 0x8;
+
+ /**
+ * Session is disabled because service is not set for user.
+ *
+ * @hide
+ */
+ public static final int STATE_NO_SERVICE = 0x10;
/**
* Session is disabled by FLAG_SECURE
*
* @hide
*/
- public static final int STATE_DISABLED_BY_FLAG_SECURE = 5;
+ public static final int STATE_FLAG_SECURE = 0x20;
+
+ /**
+ * Session is disabled manually by the specific app.
+ *
+ * @hide
+ */
+ public static final int STATE_BY_APP = 0x40;
+
private static final int INITIAL_CHILDREN_CAPACITY = 5;
@@ -110,7 +127,7 @@
@Nullable
protected final String mId;
- private int mState = STATE_UNKNOWN;
+ private int mState = UNKNWON_STATE;
// Lazily created on demand.
private ContentCaptureSessionId mContentCaptureSessionId;
@@ -382,21 +399,7 @@
*/
@NonNull
protected static String getStateAsString(int state) {
- switch (state) {
- case STATE_UNKNOWN:
- return "UNKNOWN";
- case STATE_WAITING_FOR_SERVER:
- return "WAITING_FOR_SERVER";
- case STATE_ACTIVE:
- return "ACTIVE";
- case STATE_DISABLED_NO_SERVICE:
- return "DISABLED_NO_SERVICE";
- case STATE_DISABLED_DUPLICATED_ID:
- return "DISABLED_DUPLICATED_ID";
- case STATE_DISABLED_BY_FLAG_SECURE:
- return "DISABLED_FLAG_SECURE";
- default:
- return "INVALID:" + state;
- }
+ return state + " (" + (state == UNKNWON_STATE ? "UNKNOWN"
+ : DebugUtils.flagsToString(ContentCaptureSession.class, "STATE_", state)) + ")";
}
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index a29aaf0..1d9018c 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -88,6 +88,7 @@
*/
public static final String EXTRA_BINDER = "binder";
+ // TODO(b/111276913): make sure disabled state is in sync with manager's disabled
@NonNull
private final AtomicBoolean mDisabled;
@@ -113,7 +114,7 @@
@Nullable
private DeathRecipient mDirectServiceVulture;
- private int mState = STATE_UNKNOWN;
+ private int mState = UNKNWON_STATE;
@Nullable
private IBinder mApplicationToken;
@@ -133,11 +134,11 @@
/** @hide */
protected MainContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
@Nullable IContentCaptureManager systemServerInterface,
- @NonNull AtomicBoolean disabled) {
+ @NonNull boolean disabled) {
mContext = context;
mHandler = handler;
mSystemServerInterface = systemServerInterface;
- mDisabled = disabled;
+ mDisabled = new AtomicBoolean(disabled);
}
@Override
@@ -184,7 +185,7 @@
private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
int flags) {
- if (mState != STATE_UNKNOWN) {
+ if (mState != UNKNWON_STATE) {
// TODO(b/111276913): revisit this scenario
Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
+ getStateAsString(mState));
@@ -247,17 +248,14 @@
}
}
- // TODO(b/111276913): change the resultCode to use flags so there's just one flag for
- // disabled stuff
- if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID
- || resultCode == STATE_DISABLED_BY_FLAG_SECURE) {
+ if ((mState & STATE_DISABLED) != 0) {
mDisabled.set(true);
handleResetSession(/* resetState= */ false);
} else {
mDisabled.set(false);
}
if (VERBOSE) {
- Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId
+ Log.v(TAG, "handleSessionStarted() result: id=" + mId
+ ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
+ ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
}
@@ -407,7 +405,7 @@
// clearings out.
private void handleResetSession(boolean resetState) {
if (resetState) {
- mState = STATE_UNKNOWN;
+ mState = UNKNWON_STATE;
}
// TODO(b/122454205): must reset children (which currently is owned by superclass)
@@ -496,8 +494,7 @@
}
pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
- pw.print(prefix); pw.print("state: "); pw.print(mState); pw.print(" (");
- pw.print(getStateAsString(mState)); pw.println(")");
+ pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
if (mApplicationToken != null) {
pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
}
diff --git a/core/java/android/view/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java
index 5b957156..e2a763e 100644
--- a/core/java/android/view/inspector/InspectableProperty.java
+++ b/core/java/android/view/inspector/InspectableProperty.java
@@ -105,7 +105,6 @@
/**
* One entry in an enumeration packed into a primitive {int}.
*
- * @see IntEnumMapping
* @hide
*/
@Target({TYPE})
diff --git a/core/java/android/view/inspector/IntEnumMapping.java b/core/java/android/view/inspector/IntEnumMapping.java
deleted file mode 100644
index 69f6dce..0000000
--- a/core/java/android/view/inspector/IntEnumMapping.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.inspector;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.util.ArrayList;
-
-/**
- * Maps the values of an {int} property to string names for properties that encode enumerations.
- *
- * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
- * for enumerations packed into primitive {int} properties.
- *
- * This class is immutable, and must be constructed by a {@link Builder}.
- *
- * @see PropertyMapper#mapIntEnum(String, int, IntEnumMapping)
- */
-public final class IntEnumMapping {
- private final Value[] mValues;
-
- /**
- * Map from a property value to a string name.
- *
- * @param value The value of a property
- * @return The name of the enumeration value, null if the value is not mapped
- */
- @Nullable
- public String nameOf(int value) {
- for (Value valueTuple : mValues) {
- if (valueTuple.mValue == value) {
- return valueTuple.mName;
- }
- }
-
- return null;
- }
-
- /**
- * Create a new instance from a builder.
- *
- * This constructor is private, use {@link Builder#build()} instead.
- *
- * @param builder A builder to create from
- */
- private IntEnumMapping(Builder builder) {
- mValues = builder.mValues.toArray(new Value[builder.mValues.size()]);
- }
-
- /**
- * A builder for {@link IntEnumMapping}
- */
- public static final class Builder {
- private final ArrayList<Value> mValues;
-
- public Builder() {
- mValues = new ArrayList<>();
- }
-
- /**
- * Add a new entry to this mapping.
- *
- * @param name Name of the enumeration value
- * @param value Int value of the enumeration value
- * @return This builder
- */
- @NonNull
- public Builder addValue(@NonNull String name, int value) {
- mValues.add(new Value(name, value));
- return this;
- }
-
- /**
- * Clear the builder, allowing for recycling.
- */
- public void clear() {
- mValues.clear();
- }
-
- /**
- * Build a new {@link IntEnumMapping} from this builder
- *
- * @return A new mapping
- */
- @NonNull
- public IntEnumMapping build() {
- return new IntEnumMapping(this);
- }
- }
-
- /**
- * Inner class that holds the name and value of an enumeration value.
- */
- private static final class Value {
- @NonNull private final String mName;
- private final int mValue;
-
- private Value(@NonNull String name, int value) {
- mName = name;
- mValue = value;
- }
- }
-}
diff --git a/core/java/android/view/inspector/IntFlagMapping.java b/core/java/android/view/inspector/IntFlagMapping.java
index dcb87e1..8f7dfd5 100644
--- a/core/java/android/view/inspector/IntFlagMapping.java
+++ b/core/java/android/view/inspector/IntFlagMapping.java
@@ -19,14 +19,20 @@
import android.annotation.NonNull;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
/**
- * Maps the values of an {int} property to arrays of string for properties that encode flags.
+ * Maps the values of an {@code int} property to arrays of string for properties that encode flags.
*
* An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
- * for flag values packed into primitive {int} properties.
+ * for flag values packed into primitive {@code int} properties.
*
- * Each flag has a
+ * Each flag has a mask and a target value, for non-exclusive flags, the target can also be used as
+ * the mask. A given integer value is compared against each flag to find what flags are active for
+ * it by bitwise anding it with the mask and comparing the result against the target, that is,
+ * {@code (value & mask) == target}.
*
* This class is immutable, and must be constructed by a {@link Builder}.
*
@@ -42,8 +48,8 @@
* @return The names of the enabled flags
*/
@NonNull
- public String[] namesOf(int value) {
- ArrayList<String> enabledFlagNames = new ArrayList<>(mFlags.length);
+ public Set<String> get(int value) {
+ final Set<String> enabledFlagNames = new HashSet<>(mFlags.length);
for (Flag flag : mFlags) {
if (flag.isEnabledFor(value)) {
@@ -51,7 +57,7 @@
}
}
- return enabledFlagNames.toArray(new String[enabledFlagNames.size()]);
+ return Collections.unmodifiableSet(enabledFlagNames);
}
/**
@@ -81,7 +87,7 @@
* The target value will be used as a mask, to handle the common case where flag values
* are not mutually exclusive. The flag will be considered enabled for a property value if
* the result of bitwise anding the target and the value equals the target, that is:
- * {(value & target) == target}.
+ * {@code (value & target) == target}.
*
* @param name The name of the flag
* @param target The value to compare against
@@ -97,7 +103,7 @@
* Add a new flag with a mask.
*
* The flag will be considered enabled for a property value if the result of bitwise anding
- * the value and the mask equals the target, that is: {(value & mask) == target}.
+ * the value and the mask equals the target, that is: {@code (value & mask) == target}.
*
* @param name The name of the flag
* @param target The value to compare against
@@ -111,13 +117,6 @@
}
/**
- * Clear the builder, allowing for recycling.
- */
- public void clear() {
- mFlags.clear();
- }
-
- /**
* Build a new {@link IntFlagMapping} from this builder.
*
* @return A new mapping
@@ -143,7 +142,7 @@
}
/**
- * Compare the supplied property value against the mask and taget.
+ * Compare the supplied property value against the mask and target.
*
* @param value The value to check
* @return True if this flag is enabled
diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java
index 5fb291b..e20582b 100644
--- a/core/java/android/view/inspector/PropertyMapper.java
+++ b/core/java/android/view/inspector/PropertyMapper.java
@@ -18,6 +18,7 @@
import android.annotation.AttrRes;
import android.annotation.NonNull;
+import android.util.SparseArray;
/**
* An interface for mapping the string names of inspectable properties to integer identifiers.
@@ -154,7 +155,7 @@
int mapIntEnum(
@NonNull String name,
@AttrRes int attributeId,
- @NonNull IntEnumMapping mapping);
+ @NonNull SparseArray<String> mapping);
/**
* Map a string name to an integer ID for a flag set packed into an int property.
diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java
index fd83e8d..a8b7ecc 100644
--- a/core/java/android/view/inspector/PropertyReader.java
+++ b/core/java/android/view/inspector/PropertyReader.java
@@ -25,7 +25,13 @@
/**
* An interface for reading the properties of an inspectable object.
*
- * Used as the parameter for {@link InspectionCompanion#readProperties(Object, PropertyReader)}.
+ * {@code PropertyReader} is defined as an interface that will be called by
+ * {@link InspectionCompanion#readProperties(Object, PropertyReader)}. This approach allows a
+ * client inspector to read the values of primitive properties without the overhead of
+ * instantiating a class to hold the property values for each inspection pass. If an inspectable
+ * remains unchanged between reading passes, it should be possible for a {@code PropertyReader} to
+ * avoid new allocations for subsequent reading passes.
+ *
* It has separate methods for all primitive types to avoid autoboxing overhead if a concrete
* implementation is able to work with primitives. Implementations should be prepared to accept
* {null} as the value of {@link PropertyReader#readObject(int, Object)}.
@@ -38,7 +44,7 @@
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
- * @throws PropertyTypeMismatchException If the property ID is not mapped as a {boolean}
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code boolean}
*/
void readBoolean(int id, boolean value);
@@ -47,7 +53,7 @@
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
- * @throws PropertyTypeMismatchException If the property ID is not mapped as a {byte}
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code byte}
*/
void readByte(int id, byte value);
@@ -56,7 +62,7 @@
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
- * @throws PropertyTypeMismatchException If the property ID is not mapped as a {char}
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code char}
*/
void readChar(int id, char value);
@@ -65,7 +71,7 @@
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
- * @throws PropertyTypeMismatchException If the property ID is not mapped as a {double}
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code double}
*/
void readDouble(int id, double value);
@@ -74,7 +80,7 @@
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
- * @throws PropertyTypeMismatchException If the property ID is not mapped as a {float}
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code float}
*/
void readFloat(int id, float value);
@@ -83,7 +89,7 @@
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
- * @throws PropertyTypeMismatchException If the property ID is not mapped as an {int}
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as an {@code int}
*/
void readInt(int id, int value);
@@ -92,7 +98,7 @@
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
- * @throws PropertyTypeMismatchException If the property ID is not mapped as a {long}
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code long}
*/
void readLong(int id, long value);
@@ -101,7 +107,7 @@
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
- * @throws PropertyTypeMismatchException If the property ID is not mapped as a {short}
+ * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code short}
*/
void readShort(int id, short value);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index e97c9bc..bc49771 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -557,6 +557,38 @@
return result;
}
+ // FIXME: Should this be FastNative?
+ static void setColorLong(JNIEnv* env, jobject clazz, jlong paintHandle, jobject jColorSpace,
+ jfloat r, jfloat g, jfloat b, jfloat a) {
+ sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
+ if (GraphicsJNI::hasException(env)) {
+ return;
+ }
+
+ SkColor4f color = SkColor4f{r, g, b, a};
+ reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get());
+ }
+
+ static void setShadowLayerLong(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat radius,
+ jfloat dx, jfloat dy, jobject jColorSpace,
+ jfloat r, jfloat g, jfloat b, jfloat a) {
+ sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
+ if (GraphicsJNI::hasException(env)) {
+ return;
+ }
+
+ SkColor4f color = SkColor4f{r, g, b, a};
+
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ if (radius <= 0) {
+ paint->setLooper(nullptr);
+ }
+ else {
+ SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
+ paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
+ }
+ }
+
// ------------------ @FastNative ---------------------------
static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
@@ -1075,6 +1107,9 @@
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I",
(void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
+ {"nSetColor","(JLandroid/graphics/ColorSpace;FFFF)V", (void*) PaintGlue::setColorLong},
+ {"nSetShadowLayer", "(JFFFLandroid/graphics/ColorSpace;FFFF)V",
+ (void*)PaintGlue::setShadowLayerLong},
// --------------- @FastNative ----------------------
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 49a24a3..b2d3651 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -281,15 +281,18 @@
std::unique_ptr<NativeCode> code;
bool needs_native_bridge = false;
+ char* nativeloader_error_msg = nullptr;
void* handle = OpenNativeLibrary(env,
sdkVersion,
pathStr.c_str(),
classLoader,
libraryPath,
&needs_native_bridge,
- &g_error_msg);
+ &nativeloader_error_msg);
if (handle == nullptr) {
+ g_error_msg = nativeloader_error_msg;
+ NativeLoaderFreeErrorMessage(nativeloader_error_msg);
ALOGW("NativeActivity LoadNativeLibrary(\"%s\") failed: %s",
pathStr.c_str(),
g_error_msg.c_str());
diff --git a/core/proto/Android.bp b/core/proto/Android.bp
new file mode 100644
index 0000000..80cc2d4
--- /dev/null
+++ b/core/proto/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// C++ library for Bluetooth platform wide protobuf definitions
+cc_library_static {
+ name: "libbt-platform-protos-lite",
+ host_supported: true,
+ proto: {
+ export_proto_headers: true,
+ type: "lite",
+ },
+ srcs: [
+ "android/bluetooth/enums.proto",
+ "android/bluetooth/hci/enums.proto",
+ ],
+}
diff --git a/core/proto/android/bluetooth/enums.proto b/core/proto/android/bluetooth/enums.proto
index d0c9226..76c240e 100644
--- a/core/proto/android/bluetooth/enums.proto
+++ b/core/proto/android/bluetooth/enums.proto
@@ -41,3 +41,18 @@
ENABLE_DISABLE_REASON_USER_SWITCH = 8;
ENABLE_DISABLE_REASON_RESTORE_USER_SETTING = 9;
}
+
+enum DirectionEnum {
+ DIRECTION_UNKNOWN = 0;
+ DIRECTION_OUTGOING = 1;
+ DIRECTION_INCOMING = 2;
+}
+
+// First item is the default value, other values follow Bluetooth spec definition
+enum LinkTypeEnum {
+ // Link type is at most 1 byte (0xFF), thus 0xFFF must not be a valid value
+ LINK_TYPE_UNKNOWN = 0xFFF;
+ LINK_TYPE_SCO = 0x00;
+ LINK_TYPE_ACL = 0x01;
+ LINK_TYPE_ESCO = 0x02;
+}
diff --git a/core/proto/android/bluetooth/hci/enums.proto b/core/proto/android/bluetooth/hci/enums.proto
new file mode 100644
index 0000000..e1d96bb
--- /dev/null
+++ b/core/proto/android/bluetooth/hci/enums.proto
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.bluetooth.hci;
+
+option java_outer_classname = "BluetoothHciProtoEnums";
+option java_multiple_files = true;
+
+// HCI command opcodes (OCF+OGF) from Bluetooth 5.0 specification Vol 2, Part E, Section 7
+// Original definition: system/bt/stack/include/hcidefs.h
+enum CommandEnum {
+ // Opcode is at most 2 bytes (0xFFFF), thus 0xFFFFF must not be a valid value
+ CMD_UNKNOWN = 0xFFFFF;
+ // Link control commands 0x0400
+ CMD_INQUIRY = 0x0401;
+ CMD_INQUIRY_CANCEL = 0x0402;
+ CMD_PERIODIC_INQUIRY_MODE = 0x0403;
+ CMD_EXIT_PERIODIC_INQUIRY_MODE = 0x0404;
+ CMD_CREATE_CONNECTION = 0x0405;
+ CMD_DISCONNECT = 0x0406;
+ CMD_ADD_SCO_CONNECTION = 0x0407; // Deprecated since Bluetooth 1.2
+ CMD_CREATE_CONNECTION_CANCEL = 0x0408;
+ CMD_ACCEPT_CONNECTION_REQUEST = 0x0409;
+ CMD_REJECT_CONNECTION_REQUEST = 0x040A;
+ CMD_LINK_KEY_REQUEST_REPLY = 0x040B;
+ CMD_LINK_KEY_REQUEST_NEG_REPLY = 0x040C;
+ CMD_PIN_CODE_REQUEST_REPLY = 0x040D;
+ CMD_PIN_CODE_REQUEST_NEG_REPLY = 0x040E;
+ CMD_CHANGE_CONN_PACKET_TYPE = 0x040F;
+ CMD_AUTHENTICATION_REQUESTED = 0x0411;
+ CMD_SET_CONN_ENCRYPTION = 0x0413;
+ CMD_CHANGE_CONN_LINK_KEY = 0x0415;
+ CMD_MASTER_LINK_KEY = 0x0417;
+ CMD_RMT_NAME_REQUEST = 0x0419;
+ CMD_RMT_NAME_REQUEST_CANCEL = 0x041A;
+ CMD_READ_RMT_FEATURES = 0x041B;
+ CMD_READ_RMT_EXT_FEATURES = 0x041C;
+ CMD_READ_RMT_VERSION_INFO = 0x041D;
+ CMD_READ_RMT_CLOCK_OFFSET = 0x041F;
+ CMD_READ_LMP_HANDLE = 0x0420;
+ CMD_SETUP_ESCO_CONNECTION = 0x0428;
+ CMD_ACCEPT_ESCO_CONNECTION = 0x0429;
+ CMD_REJECT_ESCO_CONNECTION = 0x042A;
+ CMD_IO_CAPABILITY_REQUEST_REPLY = 0x042B;
+ CMD_USER_CONF_REQUEST_REPLY = 0x042C;
+ CMD_USER_CONF_VALUE_NEG_REPLY = 0x042D;
+ CMD_USER_PASSKEY_REQ_REPLY = 0x042E;
+ CMD_USER_PASSKEY_REQ_NEG_REPLY = 0x042F;
+ CMD_REM_OOB_DATA_REQ_REPLY = 0x0430;
+ CMD_REM_OOB_DATA_REQ_NEG_REPLY = 0x0433;
+ CMD_IO_CAP_REQ_NEG_REPLY = 0x0434;
+ // BEGIN: AMP commands (not used in system/bt)
+ CMD_CREATE_PHYSICAL_LINK = 0x0435;
+ CMD_ACCEPT_PHYSICAL_LINK = 0x0436;
+ CMD_DISCONNECT_PHYSICAL_LINK = 0x0437;
+ CMD_CREATE_LOGICAL_LINK = 0x0438;
+ CMD_ACCEPT_LOGICAL_LINK = 0x0439;
+ CMD_DISCONNECT_LOGICAL_LINK = 0x043A;
+ CMD_LOGICAL_LINK_CANCEL = 0x043B;
+ CMD_FLOW_SPEC_MODIFY = 0x043C;
+ // END: AMP commands
+ CMD_ENH_SETUP_ESCO_CONNECTION = 0x043D;
+ CMD_ENH_ACCEPT_ESCO_CONNECTION = 0x043E;
+ CMD_TRUNCATED_PAGE = 0x043F;
+ CMD_TRUNCATED_PAGE_CANCEL = 0x0440;
+ CMD_SET_CLB = 0x0441;
+ CMD_RECEIVE_CLB = 0x0442;
+ CMD_START_SYNC_TRAIN = 0x0443;
+ CMD_RECEIVE_SYNC_TRAIN = 0x0444;
+ CMD_REM_OOB_EXTENDED_DATA_REQ_REPLY = 0x0445; // Not currently used in system/bt
+ // Link policy commands 0x0800
+ CMD_HOLD_MODE = 0x0801;
+ CMD_SNIFF_MODE = 0x0803;
+ CMD_EXIT_SNIFF_MODE = 0x0804;
+ CMD_PARK_MODE = 0x0805;
+ CMD_EXIT_PARK_MODE = 0x0806;
+ CMD_QOS_SETUP = 0x0807;
+ CMD_ROLE_DISCOVERY = 0x0809;
+ CMD_SWITCH_ROLE = 0x080B;
+ CMD_READ_POLICY_SETTINGS = 0x080C;
+ CMD_WRITE_POLICY_SETTINGS = 0x080D;
+ CMD_READ_DEF_POLICY_SETTINGS = 0x080E;
+ CMD_WRITE_DEF_POLICY_SETTINGS = 0x080F;
+ CMD_FLOW_SPECIFICATION = 0x0810;
+ CMD_SNIFF_SUB_RATE = 0x0811;
+ // Host controller baseband commands 0x0C00
+ CMD_SET_EVENT_MASK = 0x0C01;
+ CMD_RESET = 0x0C03;
+ CMD_SET_EVENT_FILTER = 0x0C05;
+ CMD_FLUSH = 0x0C08;
+ CMD_READ_PIN_TYPE = 0x0C09;
+ CMD_WRITE_PIN_TYPE = 0x0C0A;
+ CMD_CREATE_NEW_UNIT_KEY = 0x0C0B;
+ CMD_GET_MWS_TRANS_LAYER_CFG = 0x0C0C; // Deprecated (not used in spec)
+ CMD_READ_STORED_LINK_KEY = 0x0C0D;
+ CMD_WRITE_STORED_LINK_KEY = 0x0C11;
+ CMD_DELETE_STORED_LINK_KEY = 0x0C12;
+ CMD_CHANGE_LOCAL_NAME = 0x0C13;
+ CMD_READ_LOCAL_NAME = 0x0C14;
+ CMD_READ_CONN_ACCEPT_TOUT = 0x0C15;
+ CMD_WRITE_CONN_ACCEPT_TOUT = 0x0C16;
+ CMD_READ_PAGE_TOUT = 0x0C17;
+ CMD_WRITE_PAGE_TOUT = 0x0C18;
+ CMD_READ_SCAN_ENABLE = 0x0C19;
+ CMD_WRITE_SCAN_ENABLE = 0x0C1A;
+ CMD_READ_PAGESCAN_CFG = 0x0C1B;
+ CMD_WRITE_PAGESCAN_CFG = 0x0C1C;
+ CMD_READ_INQUIRYSCAN_CFG = 0x0C1D;
+ CMD_WRITE_INQUIRYSCAN_CFG = 0x0C1E;
+ CMD_READ_AUTHENTICATION_ENABLE = 0x0C1F;
+ CMD_WRITE_AUTHENTICATION_ENABLE = 0x0C20;
+ CMD_READ_ENCRYPTION_MODE = 0x0C21; // Deprecated
+ CMD_WRITE_ENCRYPTION_MODE = 0x0C22; // Deprecated
+ CMD_READ_CLASS_OF_DEVICE = 0x0C23;
+ CMD_WRITE_CLASS_OF_DEVICE = 0x0C24;
+ CMD_READ_VOICE_SETTINGS = 0x0C25;
+ CMD_WRITE_VOICE_SETTINGS = 0x0C26;
+ CMD_READ_AUTOMATIC_FLUSH_TIMEOUT = 0x0C27;
+ CMD_WRITE_AUTOMATIC_FLUSH_TIMEOUT = 0x0C28;
+ CMD_READ_NUM_BCAST_REXMITS = 0x0C29;
+ CMD_WRITE_NUM_BCAST_REXMITS = 0x0C2A;
+ CMD_READ_HOLD_MODE_ACTIVITY = 0x0C2B;
+ CMD_WRITE_HOLD_MODE_ACTIVITY = 0x0C2C;
+ CMD_READ_TRANSMIT_POWER_LEVEL = 0x0C2D;
+ CMD_READ_SCO_FLOW_CTRL_ENABLE = 0x0C2E;
+ CMD_WRITE_SCO_FLOW_CTRL_ENABLE = 0x0C2F;
+ CMD_SET_HC_TO_HOST_FLOW_CTRL = 0x0C31;
+ CMD_HOST_BUFFER_SIZE = 0x0C33;
+ CMD_HOST_NUM_PACKETS_DONE = 0x0C35;
+ CMD_READ_LINK_SUPER_TOUT = 0x0C36;
+ CMD_WRITE_LINK_SUPER_TOUT = 0x0C37;
+ CMD_READ_NUM_SUPPORTED_IAC = 0x0C38;
+ CMD_READ_CURRENT_IAC_LAP = 0x0C39;
+ CMD_WRITE_CURRENT_IAC_LAP = 0x0C3A;
+ CMD_READ_PAGESCAN_PERIOD_MODE = 0x0C3B; // Deprecated
+ CMD_WRITE_PAGESCAN_PERIOD_MODE = 0x0C3C; // Deprecated
+ CMD_READ_PAGESCAN_MODE = 0x0C3D; // Deprecated
+ CMD_WRITE_PAGESCAN_MODE = 0x0C3E; // Deprecated
+ CMD_SET_AFH_CHANNELS = 0x0C3F;
+ CMD_READ_INQSCAN_TYPE = 0x0C42;
+ CMD_WRITE_INQSCAN_TYPE = 0x0C43;
+ CMD_READ_INQUIRY_MODE = 0x0C44;
+ CMD_WRITE_INQUIRY_MODE = 0x0C45;
+ CMD_READ_PAGESCAN_TYPE = 0x0C46;
+ CMD_WRITE_PAGESCAN_TYPE = 0x0C47;
+ CMD_READ_AFH_ASSESSMENT_MODE = 0x0C48;
+ CMD_WRITE_AFH_ASSESSMENT_MODE = 0x0C49;
+ CMD_READ_EXT_INQ_RESPONSE = 0x0C51;
+ CMD_WRITE_EXT_INQ_RESPONSE = 0x0C52;
+ CMD_REFRESH_ENCRYPTION_KEY = 0x0C53;
+ CMD_READ_SIMPLE_PAIRING_MODE = 0x0C55;
+ CMD_WRITE_SIMPLE_PAIRING_MODE = 0x0C56;
+ CMD_READ_LOCAL_OOB_DATA = 0x0C57;
+ CMD_READ_INQ_TX_POWER_LEVEL = 0x0C58;
+ CMD_WRITE_INQ_TX_POWER_LEVEL = 0x0C59;
+ CMD_READ_ERRONEOUS_DATA_RPT = 0x0C5A;
+ CMD_WRITE_ERRONEOUS_DATA_RPT = 0x0C5B;
+ CMD_ENHANCED_FLUSH = 0x0C5F;
+ CMD_SEND_KEYPRESS_NOTIF = 0x0C60;
+ CMD_READ_LOGICAL_LINK_ACCEPT_TIMEOUT = 0x0C61;
+ CMD_WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT = 0x0C62;
+ CMD_SET_EVENT_MASK_PAGE_2 = 0x0C63;
+ CMD_READ_LOCATION_DATA = 0x0C64;
+ CMD_WRITE_LOCATION_DATA = 0x0C65;
+ CMD_READ_FLOW_CONTROL_MODE = 0x0C66;
+ CMD_WRITE_FLOW_CONTROL_MODE = 0x0C67;
+ CMD_READ_ENHANCED_TX_PWR_LEVEL = 0x0C68; // Not currently used in system/bt
+ CMD_READ_BE_FLUSH_TOUT = 0x0C69;
+ CMD_WRITE_BE_FLUSH_TOUT = 0x0C6A;
+ CMD_SHORT_RANGE_MODE = 0x0C6B;
+ CMD_READ_BLE_HOST_SUPPORT = 0x0C6C;
+ CMD_WRITE_BLE_HOST_SUPPORT = 0x0C6D;
+ CMD_SET_MWS_CHANNEL_PARAMETERS = 0x0C6E;
+ CMD_SET_EXTERNAL_FRAME_CONFIGURATION = 0x0C6F;
+ CMD_SET_MWS_SIGNALING = 0x0C70;
+ CMD_SET_MWS_TRANSPORT_LAYER = 0x0C71;
+ CMD_SET_MWS_SCAN_FREQUENCY_TABLE = 0x0C72;
+ CMD_SET_MWS_PATTERN_CONFIGURATION = 0x0C73;
+ CMD_SET_RESERVED_LT_ADDR = 0x0C74;
+ CMD_DELETE_RESERVED_LT_ADDR = 0x0C75;
+ CMD_WRITE_CLB_DATA = 0x0C76;
+ CMD_READ_SYNC_TRAIN_PARAM = 0x0C77;
+ CMD_WRITE_SYNC_TRAIN_PARAM = 0x0C78;
+ CMD_READ_SECURE_CONNS_SUPPORT = 0x0C79;
+ CMD_WRITE_SECURE_CONNS_SUPPORT = 0x0C7A;
+ CMD_READ_AUTHED_PAYLOAD_TIMEOUT = 0x0C7B; // Not currently used in system/bt
+ CMD_WRITE_AUTHED_PAYLOAD_TIMEOUT = 0x0C7C; // Not currently used in system/bt
+ CMD_READ_LOCAL_OOB_EXTENDED_DATA = 0x0C7D; // Not currently used in system/bt
+ CMD_READ_EXTENDED_PAGE_TIMEOUT = 0x0C7E; // Not currently used in system/bt
+ CMD_WRITE_EXTENDED_PAGE_TIMEOUT = 0x0C7F; // Not currently used in system/bt
+ CMD_READ_EXTENDED_INQUIRY_LENGTH = 0x0C80; // Not currently used in system/bt
+ CMD_WRITE_EXTENDED_INQUIRY_LENGTH = 0x0C81; // Not currently used in system/bt
+ // Informational parameter commands 0x1000
+ CMD_READ_LOCAL_VERSION_INFO = 0x1001;
+ CMD_READ_LOCAL_SUPPORTED_CMDS = 0x1002;
+ CMD_READ_LOCAL_FEATURES = 0x1003;
+ CMD_READ_LOCAL_EXT_FEATURES = 0x1004;
+ CMD_READ_BUFFER_SIZE = 0x1005;
+ CMD_READ_COUNTRY_CODE = 0x1007; // Deprecated
+ CMD_READ_BD_ADDR = 0x1009;
+ CMD_READ_DATA_BLOCK_SIZE = 0x100A;
+ CMD_READ_LOCAL_SUPPORTED_CODECS = 0x100B;
+ // Status parameter commands 0x1400
+ CMD_READ_FAILED_CONTACT_COUNTER = 0x1401;
+ CMD_RESET_FAILED_CONTACT_COUNTER = 0x1402;
+ CMD_GET_LINK_QUALITY = 0x1403;
+ CMD_READ_RSSI = 0x1405;
+ CMD_READ_AFH_CH_MAP = 0x1406;
+ CMD_READ_CLOCK = 0x1407;
+ CMD_READ_ENCR_KEY_SIZE = 0x1408;
+ CMD_READ_LOCAL_AMP_INFO = 0x1409;
+ CMD_READ_LOCAL_AMP_ASSOC = 0x140A;
+ CMD_WRITE_REMOTE_AMP_ASSOC = 0x140B;
+ CMD_GET_MWS_TRANSPORT_CFG = 0x140C; // Not currently used in system/bt
+ CMD_SET_TRIGGERED_CLK_CAPTURE = 0x140D; // Not currently used in system/bt
+ // Testing commands 0x1800
+ CMD_READ_LOOPBACK_MODE = 0x1801;
+ CMD_WRITE_LOOPBACK_MODE = 0x1802;
+ CMD_ENABLE_DEV_UNDER_TEST_MODE = 0x1803;
+ CMD_WRITE_SIMP_PAIR_DEBUG_MODE = 0x1804;
+ CMD_ENABLE_AMP_RCVR_REPORTS = 0x1807;
+ CMD_AMP_TEST_END = 0x1808;
+ CMD_AMP_TEST = 0x1809;
+ CMD_WRITE_SECURE_CONN_TEST_MODE = 0x180A; // Not currently used in system/bt
+ // BLE commands 0x2000
+ CMD_BLE_SET_EVENT_MASK = 0x2001;
+ CMD_BLE_READ_BUFFER_SIZE = 0x2002;
+ CMD_BLE_READ_LOCAL_SPT_FEAT = 0x2003;
+ CMD_BLE_WRITE_LOCAL_SPT_FEAT = 0x2004;
+ CMD_BLE_WRITE_RANDOM_ADDR = 0x2005;
+ CMD_BLE_WRITE_ADV_PARAMS = 0x2006;
+ CMD_BLE_READ_ADV_CHNL_TX_POWER = 0x2007;
+ CMD_BLE_WRITE_ADV_DATA = 0x2008;
+ CMD_BLE_WRITE_SCAN_RSP_DATA = 0x2009;
+ CMD_BLE_WRITE_ADV_ENABLE = 0x200A;
+ CMD_BLE_WRITE_SCAN_PARAMS = 0x200B;
+ CMD_BLE_WRITE_SCAN_ENABLE = 0x200C;
+ CMD_BLE_CREATE_LL_CONN = 0x200D;
+ CMD_BLE_CREATE_CONN_CANCEL = 0x200E;
+ CMD_BLE_READ_WHITE_LIST_SIZE = 0x200F;
+ CMD_BLE_CLEAR_WHITE_LIST = 0x2010;
+ CMD_BLE_ADD_WHITE_LIST = 0x2011;
+ CMD_BLE_REMOVE_WHITE_LIST = 0x2012;
+ CMD_BLE_UPD_LL_CONN_PARAMS = 0x2013;
+ CMD_BLE_SET_HOST_CHNL_CLASS = 0x2014;
+ CMD_BLE_READ_CHNL_MAP = 0x2015;
+ CMD_BLE_READ_REMOTE_FEAT = 0x2016;
+ CMD_BLE_ENCRYPT = 0x2017;
+ CMD_BLE_RAND = 0x2018;
+ CMD_BLE_START_ENC = 0x2019;
+ CMD_BLE_LTK_REQ_REPLY = 0x201A;
+ CMD_BLE_LTK_REQ_NEG_REPLY = 0x201B;
+ CMD_BLE_READ_SUPPORTED_STATES = 0x201C;
+ CMD_BLE_RECEIVER_TEST = 0x201D;
+ CMD_BLE_TRANSMITTER_TEST = 0x201E;
+ CMD_BLE_TEST_END = 0x201F;
+ CMD_BLE_RC_PARAM_REQ_REPLY = 0x2020;
+ CMD_BLE_RC_PARAM_REQ_NEG_REPLY = 0x2021;
+ CMD_BLE_SET_DATA_LENGTH = 0x2022;
+ CMD_BLE_READ_DEFAULT_DATA_LENGTH = 0x2023;
+ CMD_BLE_WRITE_DEFAULT_DATA_LENGTH = 0x2024;
+ CMD_BLE_GENERATE_DHKEY = 0x2026; // Not currently used in system/bt
+ CMD_BLE_ADD_DEV_RESOLVING_LIST = 0x2027;
+ CMD_BLE_RM_DEV_RESOLVING_LIST = 0x2028;
+ CMD_BLE_CLEAR_RESOLVING_LIST = 0x2029;
+ CMD_BLE_READ_RESOLVING_LIST_SIZE = 0x202A;
+ CMD_BLE_READ_RESOLVABLE_ADDR_PEER = 0x202B;
+ CMD_BLE_READ_RESOLVABLE_ADDR_LOCAL = 0x202C;
+ CMD_BLE_SET_ADDR_RESOLUTION_ENABLE = 0x202D;
+ CMD_BLE_SET_RAND_PRIV_ADDR_TIMOUT = 0x202E;
+ CMD_BLE_READ_MAXIMUM_DATA_LENGTH = 0x202F;
+ CMD_BLE_READ_PHY = 0x2030;
+ CMD_BLE_SET_DEFAULT_PHY = 0x2031;
+ CMD_BLE_SET_PHY = 0x2032;
+ CMD_BLE_ENH_RECEIVER_TEST = 0x2033;
+ CMD_BLE_ENH_TRANSMITTER_TEST = 0x2034;
+ CMD_BLE_SET_EXT_ADVERTISING_RANDOM_ADDRESS = 0x2035;
+ CMD_BLE_SET_EXT_ADVERTISING_PARAM = 0x2036;
+ CMD_BLE_SET_EXT_ADVERTISING_DATA = 0x2037;
+ CMD_BLE_SET_EXT_ADVERTISING_SCAN_RESP = 0x2038;
+ CMD_BLE_SET_EXT_ADVERTISING_ENABLE = 0x2039;
+ CMD_BLE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH = 0x203A;
+ CMD_BLE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS = 0x203B;
+ CMD_BLE_REMOVE_ADVERTISING_SET = 0x203C;
+ CMD_BLE_CLEAR_ADVERTISING_SETS = 0x203D;
+ CMD_BLE_SET_PERIODIC_ADVERTISING_PARAM = 0x203E;
+ CMD_BLE_SET_PERIODIC_ADVERTISING_DATA = 0x203F;
+ CMD_BLE_SET_PERIODIC_ADVERTISING_ENABLE = 0x2040;
+ CMD_BLE_SET_EXTENDED_SCAN_PARAMETERS = 0x2041;
+ CMD_BLE_SET_EXTENDED_SCAN_ENABLE = 0x2042;
+ CMD_BLE_EXTENDED_CREATE_CONNECTION = 0x2043;
+ CMD_BLE_PERIODIC_ADVERTISING_CREATE_SYNC = 0x2044;
+ CMD_BLE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL = 0x2045;
+ CMD_BLE_PERIODIC_ADVERTISING_TERMINATE_SYNC = 0x2046;
+ CMD_BLE_ADD_DEVICE_TO_PERIODIC_ADVERTISING_LIST = 0x2047;
+ CMD_BLE_RM_DEVICE_FROM_PERIODIC_ADVERTISING_LIST = 0x2048;
+ CMD_BLE_CLEAR_PERIODIC_ADVERTISING_LIST = 0x2049;
+ CMD_BLE_READ_PERIODIC_ADVERTISING_LIST_SIZE = 0x204A;
+ CMD_BLE_READ_TRANSMIT_POWER = 0x204B;
+ CMD_BLE_READ_RF_COMPENS_POWER = 0x204C;
+ CMD_BLE_WRITE_RF_COMPENS_POWER = 0x204D;
+ CMD_BLE_SET_PRIVACY_MODE = 0x204E;
+ // Vendor specific commands 0xFC00 and above
+ // Android vendor specific commands defined in
+ // https://source.android.com/devices/bluetooth/hci_requirements#vendor-specific-capabilities
+ CMD_BLE_VENDOR_CAP = 0xFD53;
+ CMD_BLE_MULTI_ADV = 0xFD54;
+ CMD_BLE_BATCH_SCAN = 0xFD56;
+ CMD_BLE_ADV_FILTER = 0xFD57;
+ CMD_BLE_TRACK_ADV = 0xFD58;
+ CMD_BLE_ENERGY_INFO = 0xFD59;
+ CMD_BLE_EXTENDED_SCAN_PARAMS = 0xFD5A;
+ CMD_CONTROLLER_DEBUG_INFO = 0xFD5B;
+ CMD_CONTROLLER_A2DP_OPCODE = 0xFD5D;
+ CMD_BRCM_SET_ACL_PRIORITY = 0xFC57;
+ // Other vendor specific commands below here
+}
+
+// HCI event codes from the Bluetooth 5.0 specification Vol 2, Part 7, Section 7
+// Original definition: system/bt/stack/include/hcidefs.h
+enum EventEnum {
+ // Event is at most 1 byte (0xFF), thus 0xFFF must not be a valid value
+ EVT_UNKNOWN = 0xFFF;
+ EVT_INQUIRY_COMP = 0x01;
+ EVT_INQUIRY_RESULT = 0x02;
+ EVT_CONNECTION_COMP = 0x03;
+ EVT_CONNECTION_REQUEST = 0x04;
+ EVT_DISCONNECTION_COMP = 0x05;
+ EVT_AUTHENTICATION_COMP = 0x06;
+ EVT_RMT_NAME_REQUEST_COMP = 0x07;
+ EVT_ENCRYPTION_CHANGE = 0x08;
+ EVT_CHANGE_CONN_LINK_KEY = 0x09;
+ EVT_MASTER_LINK_KEY_COMP = 0x0A;
+ EVT_READ_RMT_FEATURES_COMP = 0x0B;
+ EVT_READ_RMT_VERSION_COMP = 0x0C;
+ EVT_QOS_SETUP_COMP = 0x0D;
+ EVT_COMMAND_COMPLETE = 0x0E;
+ EVT_COMMAND_STATUS = 0x0F;
+ EVT_HARDWARE_ERROR = 0x10;
+ EVT_FLUSH_OCCURED = 0x11;
+ EVT_ROLE_CHANGE = 0x12;
+ EVT_NUM_COMPL_DATA_PKTS = 0x13;
+ EVT_MODE_CHANGE = 0x14;
+ EVT_RETURN_LINK_KEYS = 0x15;
+ EVT_PIN_CODE_REQUEST = 0x16;
+ EVT_LINK_KEY_REQUEST = 0x17;
+ EVT_LINK_KEY_NOTIFICATION = 0x18;
+ EVT_LOOPBACK_COMMAND = 0x19;
+ EVT_DATA_BUF_OVERFLOW = 0x1A;
+ EVT_MAX_SLOTS_CHANGED = 0x1B;
+ EVT_READ_CLOCK_OFF_COMP = 0x1C;
+ EVT_CONN_PKT_TYPE_CHANGE = 0x1D;
+ EVT_QOS_VIOLATION = 0x1E;
+ EVT_PAGE_SCAN_MODE_CHANGE = 0x1F; // Deprecated
+ EVT_PAGE_SCAN_REP_MODE_CHNG = 0x20;
+ EVT_FLOW_SPECIFICATION_COMP = 0x21;
+ EVT_INQUIRY_RSSI_RESULT = 0x22;
+ EVT_READ_RMT_EXT_FEATURES_COMP = 0x23;
+ EVT_ESCO_CONNECTION_COMP = 0x2C;
+ EVT_ESCO_CONNECTION_CHANGED = 0x2D;
+ EVT_SNIFF_SUB_RATE = 0x2E;
+ EVT_EXTENDED_INQUIRY_RESULT = 0x2F;
+ EVT_ENCRYPTION_KEY_REFRESH_COMP = 0x30;
+ EVT_IO_CAPABILITY_REQUEST = 0x31;
+ EVT_IO_CAPABILITY_RESPONSE = 0x32;
+ EVT_USER_CONFIRMATION_REQUEST = 0x33;
+ EVT_USER_PASSKEY_REQUEST = 0x34;
+ EVT_REMOTE_OOB_DATA_REQUEST = 0x35;
+ EVT_SIMPLE_PAIRING_COMPLETE = 0x36;
+ EVT_LINK_SUPER_TOUT_CHANGED = 0x38;
+ EVT_ENHANCED_FLUSH_COMPLETE = 0x39;
+ EVT_USER_PASSKEY_NOTIFY = 0x3B;
+ EVT_KEYPRESS_NOTIFY = 0x3C;
+ EVT_RMT_HOST_SUP_FEAT_NOTIFY = 0x3D;
+ EVT_BLE_META = 0x3E;
+ EVT_PHYSICAL_LINK_COMP = 0x40;
+ EVT_CHANNEL_SELECTED = 0x41;
+ EVT_DISC_PHYSICAL_LINK_COMP = 0x42;
+ EVT_PHY_LINK_LOSS_EARLY_WARNING = 0x43;
+ EVT_PHY_LINK_RECOVERY = 0x44;
+ EVT_LOGICAL_LINK_COMP = 0x45;
+ EVT_DISC_LOGICAL_LINK_COMP = 0x46;
+ EVT_FLOW_SPEC_MODIFY_COMP = 0x47;
+ EVT_NUM_COMPL_DATA_BLOCKS = 0x48;
+ EVT_AMP_TEST_START = 0x49; // Not currently used in system/bt
+ EVT_AMP_TEST_END = 0x4A; // Not currently used in system/bt
+ EVT_AMP_RECEIVER_RPT = 0x4B; // Not currently used in system/bt
+ EVT_SHORT_RANGE_MODE_COMPLETE = 0x4C;
+ EVT_AMP_STATUS_CHANGE = 0x4D;
+ EVT_SET_TRIGGERED_CLOCK_CAPTURE = 0x4E;
+ EVT_SYNC_TRAIN_CMPL = 0x4F; // Not currently used in system/bt
+ EVT_SYNC_TRAIN_RCVD = 0x50; // Not currently used in system/bt
+ EVT_CONNLESS_SLAVE_BROADCAST_RCVD = 0x51; // Not currently used in system/bt
+ EVT_CONNLESS_SLAVE_BROADCAST_TIMEOUT = 0x52; // Not currently used in system/bt
+ EVT_TRUNCATED_PAGE_CMPL = 0x53; // Not currently used in system/bt
+ EVT_SLAVE_PAGE_RES_TIMEOUT = 0x54; // Not currently used in system/bt
+ EVT_CONNLESS_SLAVE_BROADCAST_CHNL_MAP_CHANGE = 0x55; // Not currently used in system/bt
+ EVT_INQUIRY_RES_NOTIFICATION = 0x56; // Not currently used in system/bt
+ EVT_AUTHED_PAYLOAD_TIMEOUT = 0x57; // Not currently used in system/bt
+ EVT_SAM_STATUS_CHANGE = 0x58; // Not currently used in system/bt
+}
+
+// Bluetooth low energy related meta event codes
+// from the Bluetooth 5.0 specification Vol 2, Part E, Section 7.7.65
+// Original definition: system/bt/stack/include/hcidefs.h
+enum BleMetaEventEnum {
+ // BLE meta event code is at most 1 byte (0xFF), thus 0xFFF must not be a valid value
+ BLE_EVT_UNKNOWN = 0xFFF;
+ BLE_EVT_CONN_COMPLETE_EVT = 0x01;
+ BLE_EVT_ADV_PKT_RPT_EVT = 0x02;
+ BLE_EVT_LL_CONN_PARAM_UPD_EVT = 0x03;
+ BLE_EVT_READ_REMOTE_FEAT_CMPL_EVT = 0x04;
+ BLE_EVT_LTK_REQ_EVT = 0x05;
+ BLE_EVT_RC_PARAM_REQ_EVT = 0x06;
+ BLE_EVT_DATA_LENGTH_CHANGE_EVT = 0x07;
+ BLE_EVT_READ_LOCAL_P256_PUB_KEY = 0x08; // Not currently used in system/bt
+ BLE_EVT_GEN_DHKEY_CMPL = 0x09; // Not currently used in system/bt
+ BLE_EVT_ENHANCED_CONN_COMPLETE_EVT = 0x0a;
+ BLE_EVT_DIRECT_ADV_EVT = 0x0b;
+ BLE_EVT_PHY_UPDATE_COMPLETE_EVT = 0x0c;
+ BLE_EVT_EXTENDED_ADVERTISING_REPORT_EVT = 0x0D;
+ BLE_EVT_PERIODIC_ADV_SYNC_EST_EVT = 0x0E;
+ BLE_EVT_PERIODIC_ADV_REPORT_EVT = 0x0F;
+ BLE_EVT_PERIODIC_ADV_SYNC_LOST_EVT = 0x10;
+ BLE_EVT_SCAN_TIMEOUT_EVT = 0x11;
+ BLE_EVT_ADVERTISING_SET_TERMINATED_EVT = 0x12;
+ BLE_EVT_SCAN_REQ_RX_EVT = 0x13;
+ BLE_EVT_CHNL_SELECTION_ALGORITHM = 0x14; // Not currently used in system/bt
+}
+
+// HCI status code from the Bluetooth 5.0 specification Vol 2, Part D.
+// Original definition: system/bt/stack/include/hcidefs.h
+enum StatusEnum {
+ // Status is at most 1 byte (0xFF), thus 0xFFF must not be a valid value
+ STATUS_UNKNOWN = 0xFFF;
+ STATUS_SUCCESS = 0x00;
+ STATUS_ILLEGAL_COMMAND = 0x01;
+ STATUS_NO_CONNECTION = 0x02;
+ STATUS_HW_FAILURE = 0x03;
+ STATUS_PAGE_TIMEOUT = 0x04;
+ STATUS_AUTH_FAILURE = 0x05;
+ STATUS_KEY_MISSING = 0x06;
+ STATUS_MEMORY_FULL = 0x07;
+ STATUS_CONNECTION_TOUT = 0x08;
+ STATUS_MAX_NUM_OF_CONNECTIONS = 0x09;
+ STATUS_MAX_NUM_OF_SCOS = 0x0A;
+ STATUS_CONNECTION_EXISTS = 0x0B;
+ STATUS_COMMAND_DISALLOWED = 0x0C;
+ STATUS_HOST_REJECT_RESOURCES = 0x0D;
+ STATUS_HOST_REJECT_SECURITY = 0x0E;
+ STATUS_HOST_REJECT_DEVICE = 0x0F;
+ STATUS_HOST_TIMEOUT = 0x10;
+ STATUS_UNSUPPORTED_VALUE = 0x11;
+ STATUS_ILLEGAL_PARAMETER_FMT = 0x12;
+ STATUS_PEER_USER = 0x13;
+ STATUS_PEER_LOW_RESOURCES = 0x14;
+ STATUS_PEER_POWER_OFF = 0x15;
+ STATUS_CONN_CAUSE_LOCAL_HOST = 0x16;
+ STATUS_REPEATED_ATTEMPTS = 0x17;
+ STATUS_PAIRING_NOT_ALLOWED = 0x18;
+ STATUS_UNKNOWN_LMP_PDU = 0x19;
+ STATUS_UNSUPPORTED_REM_FEATURE = 0x1A;
+ STATUS_SCO_OFFSET_REJECTED = 0x1B;
+ STATUS_SCO_INTERVAL_REJECTED = 0x1C;
+ STATUS_SCO_AIR_MODE = 0x1D;
+ STATUS_INVALID_LMP_PARAM = 0x1E;
+ STATUS_UNSPECIFIED = 0x1F;
+ STATUS_UNSUPPORTED_LMP_FEATURE = 0x20;
+ STATUS_ROLE_CHANGE_NOT_ALLOWED = 0x21;
+ STATUS_LMP_RESPONSE_TIMEOUT = 0x22;
+ STATUS_LMP_STATUS_TRANS_COLLISION = 0x23;
+ STATUS_LMP_PDU_NOT_ALLOWED = 0x24;
+ STATUS_ENCRY_MODE_NOT_ACCEPTABLE = 0x25;
+ STATUS_UNIT_KEY_USED = 0x26;
+ STATUS_QOS_NOT_SUPPORTED = 0x27;
+ STATUS_INSTANT_PASSED = 0x28;
+ STATUS_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED = 0x29;
+ STATUS_DIFF_TRANSACTION_COLLISION = 0x2A;
+ STATUS_UNDEFINED_0x2B = 0x2B; // Not used
+ STATUS_QOS_UNACCEPTABLE_PARAM = 0x2C;
+ STATUS_QOS_REJECTED = 0x2D;
+ STATUS_CHAN_CLASSIF_NOT_SUPPORTED = 0x2E;
+ STATUS_INSUFFCIENT_SECURITY = 0x2F;
+ STATUS_PARAM_OUT_OF_RANGE = 0x30;
+ STATUS_UNDEFINED_0x31 = 0x31; // Not used
+ STATUS_ROLE_SWITCH_PENDING = 0x32;
+ STATUS_UNDEFINED_0x33 = 0x33;
+ STATUS_RESERVED_SLOT_VIOLATION = 0x34;
+ STATUS_ROLE_SWITCH_FAILED = 0x35;
+ STATUS_INQ_RSP_DATA_TOO_LARGE = 0x36;
+ STATUS_SIMPLE_PAIRING_NOT_SUPPORTED = 0x37;
+ STATUS_HOST_BUSY_PAIRING = 0x38;
+ STATUS_REJ_NO_SUITABLE_CHANNEL = 0x39;
+ STATUS_CONTROLLER_BUSY = 0x3A;
+ STATUS_UNACCEPT_CONN_INTERVAL = 0x3B;
+ STATUS_ADVERTISING_TIMEOUT = 0x3C;
+ STATUS_CONN_TOUT_DUE_TO_MIC_FAILURE = 0x3D;
+ STATUS_CONN_FAILED_ESTABLISHMENT = 0x3E;
+ STATUS_MAC_CONNECTION_FAILED = 0x3F;
+ STATUS_LT_ADDR_ALREADY_IN_USE = 0x40;
+ STATUS_LT_ADDR_NOT_ALLOCATED = 0x41;
+ STATUS_CLB_NOT_ENABLED = 0x42;
+ STATUS_CLB_DATA_TOO_BIG = 0x43;
+ STATUS_OPERATION_CANCELED_BY_HOST = 0x44; // Not currently used in system/bt
+}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 0ec8c1a..7f3ea7a 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -403,18 +403,23 @@
optional bool is_charging = 1;
optional bool is_in_parole = 2;
+ // List of UIDs currently in the foreground.
+ repeated int32 foreground_uids = 3;
+
message TrackedJob {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional JobStatusShortInfoProto info = 1;
optional int32 source_uid = 2;
optional JobStatusDumpProto.Bucket effective_standby_bucket = 3;
- optional bool has_quota = 4;
+ // If the job started while the app was in the TOP state.
+ optional bool is_top_started_job = 4;
+ optional bool has_quota = 5;
// The amount of time that this job has remaining in its quota. This
// can be negative if the job is out of quota.
- optional int64 remaining_quota_ms = 5;
+ optional int64 remaining_quota_ms = 6;
}
- repeated TrackedJob tracked_jobs = 3;
+ repeated TrackedJob tracked_jobs = 4;
message Package {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -456,7 +461,7 @@
repeated TimingSession saved_sessions = 3;
}
- repeated PackageStats package_stats = 4;
+ repeated PackageStats package_stats = 5;
}
message StorageController {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f3a491..7813128 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3196,9 +3196,10 @@
<!-- @SystemApi Required to add or remove another application as a device admin.
<p>Not for use by third-party applications.
- @hide -->
+ @hide
+ @removed -->
<permission android:name="android.permission.MANAGE_DEVICE_ADMINS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature" />
<!-- @SystemApi Allows an app to reset the device password.
<p>Not for use by third-party applications.
@@ -3520,19 +3521,25 @@
<permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to provide remote displays.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.REMOTE_DISPLAY_PROVIDER"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to capture video output.
<p>Not for use by third-party applications.</p>
@hide
@removed -->
<permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature" />
<!-- Allows an application to capture secure video output.
<p>Not for use by third-party applications.</p>
@hide
@removed -->
<permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature" />
<!-- Allows an application to know what content is playing and control its playback.
<p>Not for use by third-party applications due to privacy of media consumption</p> -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2e3bd7c..75f8d6f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1689,6 +1689,8 @@
config_enableFusedLocationOverlay is false. -->
<string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
+ <string-array name="config_locationExtraPackageNames" translatable="false"></string-array>
+
<!-- The package name of the default network recommendation app.
A network recommendation provider must:
* Be granted the SCORE_NETWORKS permission.
@@ -3696,7 +3698,7 @@
<string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string>
<!-- Component name for the default module metadata provider on this device -->
- <string name="config_defaultModuleMetadataProvider">com.android.modulemetadata</string>
+ <string name="config_defaultModuleMetadataProvider" translatable="false">com.android.modulemetadata</string>
<!-- This is the default launcher component to use on secondary displays that support system
decorations.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daf8b44..e5c9b84 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1840,6 +1840,7 @@
<java-symbol type="array" name="radioAttributes" />
<java-symbol type="array" name="config_oemUsbModeOverride" />
<java-symbol type="array" name="config_locationProviderPackageNames" />
+ <java-symbol type="array" name="config_locationExtraPackageNames" />
<java-symbol type="array" name="config_testLocationProviders" />
<java-symbol type="array" name="config_defaultNotificationVibePattern" />
<java-symbol type="array" name="config_notificationFallbackVibePattern" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 1ed5ce4..df4600e 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -27,7 +27,6 @@
import static java.lang.reflect.Modifier.isStatic;
import android.platform.test.annotations.Presubmit;
-import android.provider.Settings.Global;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -270,6 +269,7 @@
Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
Settings.Global.GNSS_SATELLITE_BLACKLIST,
Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
+ Settings.Global.HDMI_CEC_SWITCH_ENABLED,
Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
Settings.Global.HDMI_CONTROL_ENABLED,
@@ -554,8 +554,10 @@
Settings.Global.APPOP_HISTORY_PARAMETERS,
Settings.Global.APPOP_HISTORY_MODE,
Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER,
- Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS);
-
+ Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
+ Settings.Global.ENABLE_RADIO_BUG_DETECTION,
+ Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
+ Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 0dd9d09..28a8afe 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -318,6 +318,27 @@
mMaxVolume = maxVolume;
mIsMute = isMute;
}
+
+ @Override
+ public void setSystemAudioModeOnForAudioOnlySource() {
+ }
+
+ @Override
+ public int getPhysicalAddress() {
+ return 0x0000;
+ }
+
+ @Override
+ public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
+ }
+
+ @Override
+ public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
+ }
+
+ @Override
+ public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
+ }
}
}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 6ce8148..035ee10 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -39,9 +39,6 @@
name: "privapp-permissions-platform.xml",
sub_dir: "permissions",
src: "privapp-permissions-platform.xml",
- required: [
- "privapp_whitelist_com.android.settings.intelligence",
- ],
}
prebuilt_etc {
@@ -86,6 +83,7 @@
prebuilt_etc {
name: "privapp_whitelist_com.android.settings.intelligence",
+ product_specific: true,
sub_dir: "permissions",
src: "com.android.settings.intelligence.xml",
filename_from_src: true,
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 4a2db0a..fb43e41 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -223,12 +223,12 @@
code to link against. -->
<library name="android.test.base"
- file="/system/framework/android.test.base.impl.jar" />
+ file="/system/framework/android.test.base.jar" />
<library name="android.test.mock"
- file="/system/framework/android.test.mock.impl.jar"
+ file="/system/framework/android.test.mock.jar"
dependency="android.test.base" />
<library name="android.test.runner"
- file="/system/framework/android.test.runner.impl.jar"
+ file="/system/framework/android.test.runner.jar"
dependency="android.test.base:android.test.mock" />
<!-- In BOOT_JARS historically, and now added to legacy applications. -->
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3342fd2..cbb780d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,12 +17,14 @@
package android.graphics;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.Size;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
@@ -972,6 +974,31 @@
}
/**
+ * Set the paint's color with a {@link ColorLong}. Note that the color is
+ * a long with an encoded {@link ColorSpace} as well as alpha and r,g,b.
+ * These values are not premultiplied, meaning that alpha can be any value,
+ * regardless of the values of r,g,b. See the {@link Color} class for more
+ * details.
+ *
+ * @param color The new color (including alpha and {@link ColorSpace})
+ * to set in the paint.
+ * @throws IllegalArgumentException if the color space encoded in the long
+ * is invalid or unknown.
+ *
+ * @hide pending API approval
+ */
+ @TestApi
+ public void setColor(@ColorLong long color) {
+ ColorSpace cs = Color.colorSpace(color);
+ float r = Color.red(color);
+ float g = Color.green(color);
+ float b = Color.blue(color);
+ float a = Color.alpha(color);
+
+ nSetColor(mNativePaint, cs, r, g, b, a);
+ }
+
+ /**
* Helper to getColor() that just returns the color's alpha value. This is
* the same as calling getColor() >>> 24. It always returns a value between
* 0 (completely transparent) and 255 (completely opaque).
@@ -1370,12 +1397,45 @@
* The alpha of the shadow will be the paint's alpha if the shadow color is
* opaque, or the alpha from the shadow color if not.
*/
- public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
- mShadowLayerRadius = radius;
- mShadowLayerDx = dx;
- mShadowLayerDy = dy;
- mShadowLayerColor = shadowColor;
- nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
+ public void setShadowLayer(float radius, float dx, float dy, @ColorInt int shadowColor) {
+ mShadowLayerRadius = radius;
+ mShadowLayerDx = dx;
+ mShadowLayerDy = dy;
+ mShadowLayerColor = shadowColor;
+ // FIXME: Share a single native method with the ColorLong version.
+ nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
+ }
+
+ /**
+ * This draws a shadow layer below the main layer, with the specified
+ * offset and color, and blur radius. If radius is 0, then the shadow
+ * layer is removed.
+ * <p>
+ * Can be used to create a blurred shadow underneath text. Support for use
+ * with other drawing operations is constrained to the software rendering
+ * pipeline.
+ * <p>
+ * The alpha of the shadow will be the paint's alpha if the shadow color is
+ * opaque, or the alpha from the shadow color if not.
+ *
+ * @throws IllegalArgumentException if the color space encoded in the long
+ * is invalid or unknown.
+ *
+ * @hide pending API approval
+ */
+ @TestApi
+ public void setShadowLayer(float radius, float dx, float dy, @ColorLong long shadowColor) {
+ ColorSpace cs = Color.colorSpace(shadowColor);
+ float r = Color.red(shadowColor);
+ float g = Color.green(shadowColor);
+ float b = Color.blue(shadowColor);
+ float a = Color.alpha(shadowColor);
+ nSetShadowLayer(mNativePaint, radius, dx, dy, cs, r, g, b, a);
+
+ mShadowLayerRadius = radius;
+ mShadowLayerDx = dx;
+ mShadowLayerDy = dy;
+ mShadowLayerColor = Color.toArgb(shadowColor);
}
/**
@@ -2906,6 +2966,11 @@
int contextStart, int contextEnd, boolean isRtl, int offset);
private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, float advance);
+ private static native void nSetColor(long paintPtr, ColorSpace cs,
+ float r, float g, float b, float a);
+ private static native void nSetShadowLayer(long paintPtr,
+ float radius, float dx, float dy, ColorSpace cs,
+ float r, float g, float b, float a);
// ---------------- @FastNative ------------------------
@@ -2961,7 +3026,7 @@
int mMinikinLocaleListId);
@CriticalNative
private static native void nSetShadowLayer(long paintPtr,
- float radius, float dx, float dy, int color);
+ float radius, float dx, float dy, @ColorInt int color);
@CriticalNative
private static native boolean nHasShadowLayer(long paintPtr);
@CriticalNative
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 9170d6d..68541b4 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -86,6 +86,26 @@
mCallbacks.gles.draw(mFunctor, mData, drawInfo);
}
+void WebViewFunctor::initVk(const VkFunctorInitParams& params) {
+ ATRACE_NAME("WebViewFunctor::initVk");
+ if (!mHasContext) {
+ mHasContext = true;
+ } else {
+ return;
+ }
+ mCallbacks.vk.initialize(mFunctor, mData, params);
+}
+
+void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) {
+ ATRACE_NAME("WebViewFunctor::drawVk");
+ mCallbacks.vk.draw(mFunctor, mData, params);
+}
+
+void WebViewFunctor::postDrawVk() {
+ ATRACE_NAME("WebViewFunctor::postDrawVk");
+ mCallbacks.vk.postDraw(mFunctor, mData);
+}
+
void WebViewFunctor::destroyContext() {
if (mHasContext) {
mHasContext = false;
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index 1719ce7..2846cb1 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -42,6 +42,12 @@
void drawGl(const DrawGlInfo& drawInfo) const { mReference.drawGl(drawInfo); }
+ void initVk(const VkFunctorInitParams& params) { mReference.initVk(params); }
+
+ void drawVk(const VkFunctorDrawParams& params) { mReference.drawVk(params); }
+
+ void postDrawVk() { mReference.postDrawVk(); }
+
private:
friend class WebViewFunctor;
@@ -53,6 +59,9 @@
int id() const { return mFunctor; }
void sync(const WebViewSyncData& syncData) const;
void drawGl(const DrawGlInfo& drawInfo);
+ void initVk(const VkFunctorInitParams& params);
+ void drawVk(const VkFunctorDrawParams& params);
+ void postDrawVk();
void destroyContext();
sp<Handle> createHandle() {
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 4338b1c..e6e6b0e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -96,7 +96,7 @@
SkASSERT(mRenderThread.getGrContext() != nullptr);
sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(
- mRenderThread.getGrContext(), backendRT, kBottomLeft_GrSurfaceOrigin, colorType,
+ mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType,
mSurfaceColorSpace, &props));
SkiaPipeline::updateLighting(lightGeometry, lightInfo);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 47991069..6692922 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -39,6 +39,7 @@
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode> >& renderNodes,
FrameInfoVisualizer* profiler) override;
+ GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 2e7850d..d7faaf7 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -174,7 +174,8 @@
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- SkBudgeted::kYes, info, 0, &props));
+ SkBudgeted::kYes, info, 0,
+ this->getSurfaceOrigin(), &props));
if (node->getLayerSurface()) {
// update the transform in window of the layer to reset its origin wrt light source
// position
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index ff87313..94a699b 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -48,7 +48,7 @@
bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
ErrorHandler* errorHandler) override;
- SkColorType getSurfaceColorType() const { return mSurfaceColorType; }
+ SkColorType getSurfaceColorType() const override { return mSurfaceColorType; }
sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; }
void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 6eefed9..d54275f 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -125,8 +125,6 @@
uirenderer::GlFunctorLifecycleListener* listener) {
FunctorDrawable* functorDrawable;
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
- // TODO(cblume) use VkFunctorDrawable instead of VkInteropFunctorDrawable here when the
- // interop is disabled/moved.
functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>(
functor, listener, asSkCanvas());
} else {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 53ffc44..9343076 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -35,6 +35,7 @@
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode> >& renderNodes,
FrameInfoVisualizer* profiler) override;
+ GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index 156f74a..2f8d381 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -17,6 +17,8 @@
#include "VkFunctorDrawable.h"
#include <private/hwui/DrawVkInfo.h>
+#include "renderthread/VulkanManager.h"
+#include "renderthread/RenderThread.h"
#include <GrBackendDrawableInfo.h>
#include <SkImage.h>
#include <utils/Color.h>
@@ -31,34 +33,58 @@
namespace uirenderer {
namespace skiapipeline {
-VkFunctorDrawHandler::VkFunctorDrawHandler(Functor* functor) : INHERITED(), mFunctor(functor) {}
+VkFunctorDrawHandler::VkFunctorDrawHandler(sp<WebViewFunctor::Handle> functor_handle,
+ const SkMatrix& matrix, const SkIRect& clip,
+ const SkImageInfo& image_info)
+ : INHERITED()
+ , mFunctorHandle(functor_handle)
+ , mMatrix(matrix)
+ , mClip(clip)
+ , mImageInfo(image_info) {}
VkFunctorDrawHandler::~VkFunctorDrawHandler() {
- // TODO(cblume) Fill in the DrawVkInfo parameters.
- (*mFunctor)(DrawVkInfo::kModePostComposite, nullptr);
+ mFunctorHandle->postDrawVk();
}
void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) {
ATRACE_CALL();
+ if (!renderthread::RenderThread::isCurrent())
+ LOG_ALWAYS_FATAL("VkFunctorDrawHandler::draw not called on render thread");
GrVkDrawableInfo vulkan_info;
if (!info.getVkDrawableInfo(&vulkan_info)) {
return;
}
+ renderthread::VulkanManager& vk_manager =
+ renderthread::RenderThread::getInstance().vulkanManager();
+ mFunctorHandle->initVk(vk_manager.getVkFunctorInitParams());
- DrawVkInfo draw_vk_info;
- // TODO(cblume) Fill in the rest of the parameters and test the actual call.
- draw_vk_info.isLayer = true;
+ SkMatrix44 mat4(mMatrix);
+ VkFunctorDrawParams params{
+ .width = mImageInfo.width(),
+ .height = mImageInfo.height(),
+ .is_layer = false, // TODO(boliu): Populate is_layer.
+ .color_space_ptr = mImageInfo.colorSpace(),
+ .clip_left = mClip.fLeft,
+ .clip_top = mClip.fTop,
+ .clip_right = mClip.fRight,
+ .clip_bottom = mClip.fBottom,
+ };
+ mat4.asColMajorf(¶ms.transform[0]);
+ params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
+ params.color_attachment_index = vulkan_info.fColorAttachmentIndex;
+ params.compatible_render_pass = vulkan_info.fCompatibleRenderPass;
+ params.format = vulkan_info.fFormat;
- (*mFunctor)(DrawVkInfo::kModeComposite, &draw_vk_info);
+ mFunctorHandle->drawVk(params);
+
+ vulkan_info.fDrawBounds->offset.x = mClip.fLeft;
+ vulkan_info.fDrawBounds->offset.y = mClip.fTop;
+ vulkan_info.fDrawBounds->extent.width = mClip.fRight - mClip.fLeft;
+ vulkan_info.fDrawBounds->extent.height = mClip.fBottom - mClip.fTop;
}
VkFunctorDrawable::~VkFunctorDrawable() {
- if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) {
- if (lp->listener) {
- lp->listener->onGlFunctorReleased(lp->functor);
- }
- }
}
void VkFunctorDrawable::onDraw(SkCanvas* /*canvas*/) {
@@ -67,16 +93,17 @@
}
std::unique_ptr<FunctorDrawable::GpuDrawHandler> VkFunctorDrawable::onSnapGpuDrawHandler(
- GrBackendApi backendApi, const SkMatrix& matrix) {
+ GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clip,
+ const SkImageInfo& image_info) {
if (backendApi != GrBackendApi::kVulkan) {
return nullptr;
}
std::unique_ptr<VkFunctorDrawHandler> draw;
if (mAnyFunctor.index() == 0) {
- LOG_ALWAYS_FATAL("Not implemented");
- return nullptr;
+ return std::make_unique<VkFunctorDrawHandler>(std::get<0>(mAnyFunctor).handle, matrix, clip,
+ image_info);
} else {
- return std::make_unique<VkFunctorDrawHandler>(std::get<1>(mAnyFunctor).functor);
+ LOG_ALWAYS_FATAL("Not implemented");
}
}
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.h b/libs/hwui/pipeline/skia/VkFunctorDrawable.h
index d6fefc1..1a53c8f 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.h
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.h
@@ -32,15 +32,18 @@
*/
class VkFunctorDrawHandler : public FunctorDrawable::GpuDrawHandler {
public:
- explicit VkFunctorDrawHandler(Functor* functor);
+ VkFunctorDrawHandler(sp<WebViewFunctor::Handle> functor_handle, const SkMatrix& matrix,
+ const SkIRect& clip, const SkImageInfo& image_info);
~VkFunctorDrawHandler() override;
void draw(const GrBackendDrawableInfo& info) override;
private:
typedef GpuDrawHandler INHERITED;
-
- Functor* mFunctor;
+ sp<WebViewFunctor::Handle> mFunctorHandle;
+ const SkMatrix mMatrix;
+ const SkIRect mClip;
+ const SkImageInfo mImageInfo;
};
/**
@@ -57,7 +60,8 @@
// SkDrawable functions:
void onDraw(SkCanvas* canvas) override;
std::unique_ptr<FunctorDrawable::GpuDrawHandler> onSnapGpuDrawHandler(
- GrBackendApi backendApi, const SkMatrix& matrix) override;
+ GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clip,
+ const SkImageInfo& image_info) override;
};
} // namespace skiapipeline
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index fd824bd..abc4dbf 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -17,80 +17,61 @@
#ifndef ANDROID_HWUI_DRAW_VK_INFO_H
#define ANDROID_HWUI_DRAW_VK_INFO_H
+#include <SkColorSpace.h>
#include <vulkan/vulkan.h>
namespace android {
namespace uirenderer {
-/**
- * Structure used by VulkanRenderer::callDrawVKFunction() to pass and receive data from Vulkan
- * functors.
- */
-struct DrawVkInfo {
- // Input: current width/height of destination surface
- int width;
- int height;
+struct VkFunctorInitParams {
+ VkInstance instance;
+ VkPhysicalDevice physical_device;
+ VkDevice device;
+ VkQueue queue;
+ uint32_t graphics_queue_index;
+ uint32_t instance_version;
+ const char* const* enabled_instance_extension_names;
+ uint32_t enabled_instance_extension_names_length;
+ const char* const* enabled_device_extension_names;
+ uint32_t enabled_device_extension_names_length;
+ const VkPhysicalDeviceFeatures2* device_features_2;
+};
- // Input: is the render target an FBO
- bool isLayer;
+struct VkFunctorDrawParams {
+ // Input: current width/height of destination surface.
+ int width;
+ int height;
- // Input: current transform matrix, in OpenGL format
- float transform[16];
+ // Input: is the render target a FBO
+ bool is_layer;
- // Input: WebView should do its main compositing draws into this. It cannot do anything that
- // would require stopping the render pass.
- VkCommandBuffer secondaryCommandBuffer;
+ // Input: current transform matrix
+ float transform[16];
- // Input: The main color attachment index where secondaryCommandBuffer will eventually be
- // submitted.
- uint32_t colorAttachmentIndex;
+ // Input WebView should do its main compositing draws into this. It cannot do
+ // anything that would require stopping the render pass.
+ VkCommandBuffer secondary_command_buffer;
- // Input: A render pass which will be compatible to the one which the secondaryCommandBuffer
- // will be submitted into.
- VkRenderPass compatibleRenderPass;
+ // Input: The main color attachment index where secondary_command_buffer will
+ // eventually be submitted.
+ uint32_t color_attachment_index;
- // Input: Format of the destination surface.
- VkFormat format;
+ // Input: A render pass which will be compatible to the one which the
+ // secondary_command_buffer will be submitted into.
+ VkRenderPass compatible_render_pass;
- // Input: Color space
- const SkColorSpace* colorSpaceInfo;
+ // Input: Format of the destination surface.
+ VkFormat format;
- // Input: current clip rect
- int clipLeft;
- int clipTop;
- int clipRight;
- int clipBottom;
+ // Input: Color space.
+ const SkColorSpace* color_space_ptr;
- /**
- * Values used as the "what" parameter of the functor.
- */
- enum Mode {
- // Called once at WebView start
- kModeInit,
- // Called when things need to be re-created
- kModeReInit,
- // Notifies the app that the composite functor will be called soon. This allows WebView to
- // begin work early.
- kModePreComposite,
- // Do the actual composite work
- kModeComposite,
- // This allows WebView to begin using the previously submitted objects in future work.
- kModePostComposite,
- // Invoked every time the UI thread pushes over a frame to the render thread and the owning
- // view has a dirty display list*. This is a signal to sync any data that needs to be
- // shared between the UI thread and the render thread. During this time the UI thread is
- // blocked.
- kModeSync
- };
-
- /**
- * Values used by Vulkan functors to tell the framework what to do next.
- */
- enum Status {
- // The functor is done
- kStatusDone = 0x0,
- };
-}; // struct DrawVkInfo
+ // Input: current clip rect
+ int clip_left;
+ int clip_top;
+ int clip_right;
+ int clip_bottom;
+};
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h
index da3d06a..96da947 100644
--- a/libs/hwui/private/hwui/WebViewFunctor.h
+++ b/libs/hwui/private/hwui/WebViewFunctor.h
@@ -19,6 +19,7 @@
#include <cutils/compiler.h>
#include <private/hwui/DrawGlInfo.h>
+#include <private/hwui/DrawVkInfo.h>
namespace android::uirenderer {
@@ -52,18 +53,12 @@
// Called on RenderThread. initialize is guaranteed to happen before this call
void (*draw)(int functor, void* data, const DrawGlInfo& params);
} gles;
- // TODO: VK support. The current DrawVkInfo is monolithic and needs to be split up for
- // what params are valid on what callbacks
struct {
// Called either the first time the functor is used or the first time it's used after
// a call to onContextDestroyed.
- // void (*initialize)(int functor, const InitParams& params);
- // void (*frameStart)(int functor, /* todo: what params are actually needed for this to
- // be useful? Is this useful? */)
- // void (*draw)(int functor, const CompositeParams& params /* todo: rename - composite
- // almost always means something else, and we aren't compositing */);
- // void (*frameEnd)(int functor, const PostCompositeParams& params /* todo: same as
- // CompositeParams - rename */);
+ void (*initialize)(int functor, void* data, const VkFunctorInitParams& params);
+ void (*draw)(int functor, void* data, const VkFunctorDrawParams& params);
+ void (*postDraw)(int functor, void*);
} vk;
};
};
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 42e17b273..d4dd629 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -84,6 +84,7 @@
virtual void onPrepareTree() = 0;
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
+ virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 5272227..1ef83fb 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -47,6 +47,10 @@
class RenderState;
class TestUtils;
+namespace skiapipeline {
+class VkFunctorDrawHandler;
+}
+
namespace renderthread {
class CanvasContext;
@@ -124,6 +128,7 @@
friend class DummyVsyncSource;
friend class android::uirenderer::TestUtils;
friend class android::uirenderer::WebViewFunctor;
+ friend class android::uirenderer::skiapipeline::VkFunctorDrawHandler;
RenderThread();
virtual ~RenderThread();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index aa7a141..6c540f6 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -34,6 +34,23 @@
namespace uirenderer {
namespace renderthread {
+static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
+ // All Vulkan structs that could be part of the features chain will start with the
+ // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
+ // so we can get access to the pNext for the next struct.
+ struct CommonVulkanHeader {
+ VkStructureType sType;
+ void* pNext;
+ };
+
+ void* pNext = features.pNext;
+ while (pNext) {
+ void* current = pNext;
+ pNext = static_cast<CommonVulkanHeader*>(current)->pNext;
+ free(current);
+ }
+}
+
#define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F)
#define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
#define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
@@ -66,6 +83,11 @@
mDevice = VK_NULL_HANDLE;
mPhysicalDevice = VK_NULL_HANDLE;
mInstance = VK_NULL_HANDLE;
+ mInstanceVersion = 0u;
+ mInstanceExtensions.clear();
+ mDeviceExtensions.clear();
+ free_features_extensions_structs(mPhysicalDeviceFeatures2);
+ mPhysicalDeviceFeatures2 = {};
}
bool VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFeatures2& features) {
@@ -81,7 +103,6 @@
VK_MAKE_VERSION(1, 1, 0), // apiVersion
};
- std::vector<const char*> instanceExtensions;
{
GET_PROC(EnumerateInstanceExtensionProperties);
@@ -99,7 +120,7 @@
bool hasKHRSurfaceExtension = false;
bool hasKHRAndroidSurfaceExtension = false;
for (uint32_t i = 0; i < extensionCount; ++i) {
- instanceExtensions.push_back(extensions[i].extensionName);
+ mInstanceExtensions.push_back(extensions[i].extensionName);
if (!strcmp(extensions[i].extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) {
hasKHRSurfaceExtension = true;
}
@@ -120,8 +141,8 @@
&app_info, // pApplicationInfo
0, // enabledLayerNameCount
nullptr, // ppEnabledLayerNames
- (uint32_t) instanceExtensions.size(), // enabledExtensionNameCount
- instanceExtensions.data(), // ppEnabledExtensionNames
+ (uint32_t) mInstanceExtensions.size(), // enabledExtensionNameCount
+ mInstanceExtensions.data(), // ppEnabledExtensionNames
};
GET_PROC(CreateInstance);
@@ -201,7 +222,6 @@
// presentation with any native window. So just use the first one.
mPresentQueueIndex = 0;
- std::vector<const char*> deviceExtensions;
{
uint32_t extensionCount = 0;
err = mEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &extensionCount,
@@ -220,7 +240,7 @@
}
bool hasKHRSwapchainExtension = false;
for (uint32_t i = 0; i < extensionCount; ++i) {
- deviceExtensions.push_back(extensions[i].extensionName);
+ mDeviceExtensions.push_back(extensions[i].extensionName);
if (!strcmp(extensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
hasKHRSwapchainExtension = true;
}
@@ -237,8 +257,8 @@
}
return vkGetInstanceProcAddr(instance, proc_name);
};
- grExtensions.init(getProc, mInstance, mPhysicalDevice, instanceExtensions.size(),
- instanceExtensions.data(), deviceExtensions.size(), deviceExtensions.data());
+ grExtensions.init(getProc, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
+ mInstanceExtensions.data(), mDeviceExtensions.size(), mDeviceExtensions.data());
if (!grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
this->destroy();
@@ -308,8 +328,8 @@
queueInfo, // pQueueCreateInfos
0, // layerCount
nullptr, // ppEnabledLayerNames
- (uint32_t) deviceExtensions.size(), // extensionCount
- deviceExtensions.data(), // ppEnabledExtensionNames
+ (uint32_t) mDeviceExtensions.size(), // extensionCount
+ mDeviceExtensions.data(), // ppEnabledExtensionNames
nullptr, // ppEnabledFeatures
};
@@ -351,36 +371,17 @@
return true;
}
-static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
- // All Vulkan structs that could be part of the features chain will start with the
- // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
- // so we can get access to the pNext for the next struct.
- struct CommonVulkanHeader {
- VkStructureType sType;
- void* pNext;
- };
-
- void* pNext = features.pNext;
- while (pNext) {
- void* current = pNext;
- pNext = static_cast<CommonVulkanHeader*>(current)->pNext;
- free(current);
- }
-}
-
void VulkanManager::initialize() {
if (mDevice != VK_NULL_HANDLE) {
return;
}
GET_PROC(EnumerateInstanceVersion);
- uint32_t instanceVersion = 0;
- LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
- LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
+ LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&mInstanceVersion));
+ LOG_ALWAYS_FATAL_IF(mInstanceVersion < VK_MAKE_VERSION(1, 1, 0));
GrVkExtensions extensions;
- VkPhysicalDeviceFeatures2 features;
- LOG_ALWAYS_FATAL_IF(!this->setupDevice(extensions, features));
+ LOG_ALWAYS_FATAL_IF(!this->setupDevice(extensions, mPhysicalDeviceFeatures2));
mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
@@ -397,9 +398,9 @@
backendContext.fDevice = mDevice;
backendContext.fQueue = mGraphicsQueue;
backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex;
- backendContext.fInstanceVersion = instanceVersion;
+ backendContext.fInstanceVersion = mInstanceVersion;
backendContext.fVkExtensions = &extensions;
- backendContext.fDeviceFeatures2 = &features;
+ backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
backendContext.fGetProc = std::move(getProc);
// create the command pool for the command buffers
@@ -433,13 +434,29 @@
LOG_ALWAYS_FATAL_IF(!grContext.get());
mRenderThread.setGrContext(grContext);
- free_features_extensions_structs(features);
-
if (Properties::enablePartialUpdates && Properties::useBufferAge) {
mSwapBehavior = SwapBehavior::BufferAge;
}
}
+VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
+ return VkFunctorInitParams{
+ .instance = mInstance,
+ .physical_device = mPhysicalDevice,
+ .device = mDevice,
+ .queue = mGraphicsQueue,
+ .graphics_queue_index = mGraphicsQueueIndex,
+ .instance_version = mInstanceVersion,
+ .enabled_instance_extension_names = mInstanceExtensions.data(),
+ .enabled_instance_extension_names_length =
+ static_cast<uint32_t>(mInstanceExtensions.size()),
+ .enabled_device_extension_names = mDeviceExtensions.data(),
+ .enabled_device_extension_names_length =
+ static_cast<uint32_t>(mDeviceExtensions.size()),
+ .device_features_2 = &mPhysicalDeviceFeatures2,
+ };
+}
+
// Returns the next BackbufferInfo to use for the next draw. The function will make sure all
// previous uses have finished before returning.
VulkanSurface::BackbufferInfo* VulkanManager::getAvailableBackbuffer(VulkanSurface* surface) {
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 9eb942c..105ee09 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -132,6 +132,9 @@
// Creates a fence that is signaled, when all the pending Vulkan commands are flushed.
status_t createReleaseFence(sp<Fence>& nativeFence);
+ // Returned pointers are owned by VulkanManager.
+ VkFunctorInitParams getVkFunctorInitParams() const;
+
private:
friend class RenderThread;
@@ -234,6 +237,12 @@
VkCommandBuffer mDummyCB = VK_NULL_HANDLE;
+ // Variables saved to populate VkFunctorInitParams.
+ uint32_t mInstanceVersion = 0u;
+ std::vector<const char*> mInstanceExtensions;
+ std::vector<const char*> mDeviceExtensions;
+ VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures2{};
+
enum class SwapBehavior {
Discard,
BufferAge,
diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h
index 75b26c6..0747243 100644
--- a/libs/services/include/android/os/DropBoxManager.h
+++ b/libs/services/include/android/os/DropBoxManager.h
@@ -62,7 +62,7 @@
// file descriptor.
Status addFile(const String16& tag, int fd, int flags);
- class Entry : public virtual RefBase, public Parcelable {
+ class Entry : public Parcelable {
public:
Entry();
virtual ~Entry();
@@ -89,9 +89,6 @@
friend class DropBoxManager;
};
- // Get the next entry from the drop box after the specified time.
- Status getNextEntry(const String16& tag, long msec, Entry* entry);
-
private:
enum {
HAS_BYTE_ARRAY = 8
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index 8282518..681d5f7 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -228,15 +228,4 @@
return service->add(entry);
}
-Status
-DropBoxManager::getNextEntry(const String16& tag, long msec, Entry* entry)
-{
- sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(
- defaultServiceManager()->getService(android::String16("dropbox")));
- if (service == NULL) {
- return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
- }
- return service->getNextEntry(tag, msec, android::String16("android"), entry);
-}
-
}} // namespace android::os
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 793aa27..5516086 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -816,7 +817,7 @@
*
* @return The audio frame size in bytes corresponding to the encoding and the channel mask.
*/
- public int getFrameSizeInBytes() {
+ public @IntRange(from = 1) int getFrameSizeInBytes() {
return mFrameSizeInBytes;
}
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index a836f4a..aa79c41 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -776,7 +776,7 @@
}
boolean hasError = false;
for (DataSourceDesc dsd : dsds) {
- if (dsd != null) {
+ if (dsd == null) {
hasError = true;
continue;
}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 452c6e1..0198470 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -84,7 +84,7 @@
}
cc_library_shared {
- name: "libmedia2_jni",
+ name: "libmediaplayer2_jni",
srcs: [
"android_media_DataSourceCallback.cpp",
@@ -116,7 +116,10 @@
"libz",
],
- header_libs: ["libhardware_headers"],
+ header_libs: [
+ "libhardware_headers",
+ "libnativewindow_headers",
+ ],
static_libs: [
"libbase",
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
new file mode 100644
index 0000000..1d7d6de
--- /dev/null
+++ b/media/jni/Android.mk
@@ -0,0 +1,30 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := -Wall -Werror
+LOCAL_SRC_FILES := \
+ Media2Jni.cpp \
+
+# TODO: Move libmedia2_jni from system to media apex. Currently, libraries defined in
+# Android.mk is not visible in apex build.
+LOCAL_MODULE:= libmedia2_jni
+LOCAL_SHARED_LIBRARIES := libdl liblog
+
+sanitizer_runtime_libraries := $(call normalize-path-list,$(addsuffix .so,\
+ $(ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
+ $(UBSAN_RUNTIME_LIBRARY) \
+ $(TSAN_RUNTIME_LIBRARY)))
+
+# $(info Sanitizer: $(sanitizer_runtime_libraries))
+
+ndk_libraries := $(call normalize-path-list,$(addprefix lib,$(addsuffix .so,\
+ $(NDK_PREBUILT_SHARED_LIBRARIES))))
+
+# $(info NDK: $(ndk_libraries))
+
+LOCAL_CFLAGS += -DLINKED_LIBRARIES='"$(sanitizer_runtime_libraries):$(ndk_libraries)"'
+
+sanitizer_runtime_libraries :=
+ndk_libraries :=
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/Media2Jni.cpp b/media/jni/Media2Jni.cpp
new file mode 100644
index 0000000..6c0a65c
--- /dev/null
+++ b/media/jni/Media2Jni.cpp
@@ -0,0 +1,89 @@
+/*
+**
+** 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.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaPlayer2-JNI"
+#include "utils/Log.h"
+
+#include "jni.h"
+#include <android/dlext.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <string.h>
+
+extern "C" {
+ // Copied from GraphicsEnv.cpp
+ // TODO(b/37049319) Get this from a header once one exists
+ android_namespace_t* android_create_namespace(const char* name,
+ const char* ld_library_path,
+ const char* default_library_path,
+ uint64_t type,
+ const char* permitted_when_isolated_path,
+ android_namespace_t* parent);
+ bool android_link_namespaces(android_namespace_t* from,
+ android_namespace_t* to,
+ const char* shared_libs_sonames);
+ enum {
+ ANDROID_NAMESPACE_TYPE_ISOLATED = 1,
+ };
+
+} // extern "C"
+
+static const char kApexLibPath[] = "/apex/com.android.media/lib"
+#ifdef __LP64__
+ "64"
+#endif
+ "/";
+static const char kMediaPlayer2LibPath[] = "/apex/com.android.media/lib"
+#ifdef __LP64__
+ "64"
+#endif
+ "/libmediaplayer2_jni.so";
+
+typedef jint (*Media2JniOnLoad)(JavaVM*, void*);
+
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ android_namespace_t *media2Ns = android_create_namespace("media2",
+ nullptr, // ld_library_path
+ kApexLibPath,
+ ANDROID_NAMESPACE_TYPE_ISOLATED,
+ nullptr, // permitted_when_isolated_path
+ nullptr); // parent
+ if (!android_link_namespaces(media2Ns, nullptr, LINKED_LIBRARIES)) {
+ ALOGE("Failed to link namespace. Failed to load extractor plug-ins in apex.");
+ return -1;
+ }
+ const android_dlextinfo dlextinfo = {
+ .flags = ANDROID_DLEXT_USE_NAMESPACE,
+ .library_namespace = media2Ns,
+ };
+ // load libmediaplayer2_jni and call JNI_OnLoad.
+ void *libHandle = android_dlopen_ext(kMediaPlayer2LibPath, RTLD_NOW | RTLD_LOCAL, &dlextinfo);
+ if (libHandle == NULL) {
+ ALOGW("couldn't dlopen(%s) %s", kMediaPlayer2LibPath, strerror(errno));
+ return -1;
+ }
+ Media2JniOnLoad media2JniOnLoad = (Media2JniOnLoad) dlsym(libHandle, "JNI_OnLoad");
+ if (!media2JniOnLoad) {
+ ALOGW("%s does not contain JNI_OnLoad()", kMediaPlayer2LibPath);
+ dlclose(libHandle);
+ return -1;
+ }
+ return media2JniOnLoad(vm, reserved);
+}
diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h
index bb2ee9b..0490e65 100644
--- a/native/webview/plat_support/draw_fn.h
+++ b/native/webview/plat_support/draw_fn.h
@@ -74,7 +74,10 @@
VkQueue queue;
uint32_t graphics_queue_index;
uint32_t instance_version;
- const char* const* enabled_extension_names;
+ const char* const* enabled_instance_extension_names;
+ uint32_t enabled_instance_extension_names_length;
+ const char* const* enabled_device_extension_names;
+ uint32_t enabled_device_extension_names_length;
// Only one of device_features and device_features_2 should be non-null.
// If both are null then no features are enabled.
VkPhysicalDeviceFeatures* device_features;
@@ -128,15 +131,13 @@
struct AwDrawFn_PostDrawVkParams {
int version;
-
- // Input: Fence for the composite command buffer to signal it has finished its
- // work on the GPU.
- int fd;
};
// Called on render thread while UI thread is blocked. Called for both GL and
// VK.
-typedef void AwDrawFn_OnSync(int functor, void* data, AwDrawFn_OnSyncParams* params);
+typedef void AwDrawFn_OnSync(int functor,
+ void* data,
+ AwDrawFn_OnSyncParams* params);
// Called on render thread when either the context is destroyed _or_ when the
// functor's last reference goes away. Will always be called with an active
@@ -150,17 +151,24 @@
typedef void AwDrawFn_OnDestroyed(int functor, void* data);
// Only called for GL.
-typedef void AwDrawFn_DrawGL(int functor, void* data, AwDrawFn_DrawGLParams* params);
+typedef void AwDrawFn_DrawGL(int functor,
+ void* data,
+ AwDrawFn_DrawGLParams* params);
// Initialize vulkan state. Needs to be called again after any
// OnContextDestroyed. Only called for Vulkan.
-typedef void AwDrawFn_InitVk(int functor, void* data, AwDrawFn_InitVkParams* params);
+typedef void AwDrawFn_InitVk(int functor,
+ void* data,
+ AwDrawFn_InitVkParams* params);
// Only called for Vulkan.
-typedef void AwDrawFn_DrawVk(int functor, void* data, AwDrawFn_DrawVkParams* params);
+typedef void AwDrawFn_DrawVk(int functor,
+ void* data,
+ AwDrawFn_DrawVkParams* params);
// Only called for Vulkan.
-typedef void AwDrawFn_PostDrawVk(int functor, void* data,
+typedef void AwDrawFn_PostDrawVk(int functor,
+ void* data,
AwDrawFn_PostDrawVkParams* params);
struct AwDrawFnFunctorCallbacks {
@@ -183,7 +191,8 @@
typedef AwDrawFnRenderMode AwDrawFn_QueryRenderMode(void);
// Create a functor. |functor_callbacks| should be valid until OnDestroyed.
-typedef int AwDrawFn_CreateFunctor(void* data, AwDrawFnFunctorCallbacks* functor_callbacks);
+typedef int AwDrawFn_CreateFunctor(void* data,
+ AwDrawFnFunctorCallbacks* functor_callbacks);
// May be called on any thread to signal that the functor should be destroyed.
// The functor will receive an onDestroyed when the last usage of it is
diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp
index 6c1ceab..b97bbc3 100644
--- a/native/webview/plat_support/draw_functor.cpp
+++ b/native/webview/plat_support/draw_functor.cpp
@@ -74,6 +74,79 @@
support->callbacks.draw_gl(functor, support->data, ¶ms);
}
+void initializeVk(int functor, void* data,
+ const uirenderer::VkFunctorInitParams& init_vk_params) {
+ SupportData* support = static_cast<SupportData*>(data);
+ VkPhysicalDeviceFeatures2 device_features_2;
+ if (init_vk_params.device_features_2)
+ device_features_2 = *init_vk_params.device_features_2;
+
+ AwDrawFn_InitVkParams params{
+ .version = kAwDrawFnVersion,
+ .instance = init_vk_params.instance,
+ .physical_device = init_vk_params.physical_device,
+ .device = init_vk_params.device,
+ .queue = init_vk_params.queue,
+ .graphics_queue_index = init_vk_params.graphics_queue_index,
+ .instance_version = init_vk_params.instance_version,
+ .enabled_instance_extension_names =
+ init_vk_params.enabled_instance_extension_names,
+ .enabled_instance_extension_names_length =
+ init_vk_params.enabled_instance_extension_names_length,
+ .enabled_device_extension_names =
+ init_vk_params.enabled_device_extension_names,
+ .enabled_device_extension_names_length =
+ init_vk_params.enabled_device_extension_names_length,
+ .device_features = nullptr,
+ .device_features_2 =
+ init_vk_params.device_features_2 ? &device_features_2 : nullptr,
+ };
+ support->callbacks.init_vk(functor, support->data, ¶ms);
+}
+
+void drawVk(int functor, void* data, const uirenderer::VkFunctorDrawParams& draw_vk_params) {
+ SupportData* support = static_cast<SupportData*>(data);
+ float gabcdef[7];
+ draw_vk_params.color_space_ptr->transferFn(gabcdef);
+ AwDrawFn_DrawVkParams params{
+ .version = kAwDrawFnVersion,
+ .width = draw_vk_params.width,
+ .height = draw_vk_params.height,
+ .is_layer = draw_vk_params.is_layer,
+ .secondary_command_buffer = draw_vk_params.secondary_command_buffer,
+ .color_attachment_index = draw_vk_params.color_attachment_index,
+ .compatible_render_pass = draw_vk_params.compatible_render_pass,
+ .format = draw_vk_params.format,
+ .transfer_function_g = gabcdef[0],
+ .transfer_function_a = gabcdef[1],
+ .transfer_function_b = gabcdef[2],
+ .transfer_function_c = gabcdef[3],
+ .transfer_function_d = gabcdef[4],
+ .transfer_function_e = gabcdef[5],
+ .transfer_function_f = gabcdef[6],
+ .clip_left = draw_vk_params.clip_left,
+ .clip_top = draw_vk_params.clip_top,
+ .clip_right = draw_vk_params.clip_right,
+ .clip_bottom = draw_vk_params.clip_bottom,
+ };
+ COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3),
+ gamut_transform_size_mismatch);
+ draw_vk_params.color_space_ptr->toXYZD50(
+ reinterpret_cast<skcms_Matrix3x3*>(¶ms.color_space_toXYZD50));
+ COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_vk_params.transform),
+ mismatched_transform_matrix_sizes);
+ for (int i = 0; i < NELEM(params.transform); ++i) {
+ params.transform[i] = draw_vk_params.transform[i];
+ }
+ support->callbacks.draw_vk(functor, support->data, ¶ms);
+}
+
+void postDrawVk(int functor, void* data) {
+ SupportData* support = static_cast<SupportData*>(data);
+ AwDrawFn_PostDrawVkParams params{.version = kAwDrawFnVersion};
+ support->callbacks.post_draw_vk(functor, support->data, ¶ms);
+}
+
int CreateFunctor(void* data, AwDrawFnFunctorCallbacks* functor_callbacks) {
static bool callbacks_initialized = false;
static uirenderer::WebViewFunctorCallbacks webview_functor_callbacks = {
@@ -82,9 +155,19 @@
.onDestroyed = &onDestroyed,
};
if (!callbacks_initialized) {
- // Under uirenderer::RenderMode::Vulkan, whether gles or vk union should
- // be populated should match whether the vk-gl interop is used.
- webview_functor_callbacks.gles.draw = &draw_gl;
+ switch (uirenderer::WebViewFunctor_queryPlatformRenderMode()) {
+ case uirenderer::RenderMode::OpenGL_ES:
+ webview_functor_callbacks.gles.draw = &draw_gl;
+ break;
+ case uirenderer::RenderMode::Vulkan:
+ webview_functor_callbacks.vk.initialize = &initializeVk;
+ webview_functor_callbacks.vk.draw = &drawVk;
+ webview_functor_callbacks.vk.postDraw = &postDrawVk;
+ // TODO(boliu): Remove this once SkiaRecordingCanvas::drawWebViewFunctor
+ // no longer uses GL interop.
+ webview_functor_callbacks.gles.draw = &draw_gl;
+ break;
+ }
callbacks_initialized = true;
}
SupportData* support = new SupportData{
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a29cf84..16bfcc9 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -103,6 +103,8 @@
<string name="ssid_by_passpoint_provider"><xliff:g id="ssid" example="Cafe Wifi">%1$s</xliff:g> by <xliff:g id="passpointProvider" example="Passpoint Provider">%2$s</xliff:g></string>
<!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
<string name="available_via_passpoint">Available via %1$s</string>
+ <!-- Status message of OSU Provider network when not connected. [CHAR LIMIT=NONE] -->
+ <string name="tap_to_set_up">Tap to set up</string>
<!-- Package name for Settings app-->
<string name="settings_package" translatable="false">com.android.settings</string>
<!-- Package name for Certinstaller app-->
@@ -125,6 +127,79 @@
<!-- Status message of Wi-Fi when an available network is a carrier network. [CHAR LIMIT=NONE] -->
<string name="available_via_carrier">Available via %1$s</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_AP_CONNECTION. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_ap_connection">Connection failed</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_SERVER_URL_INVALID. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_server_url_invalid">Invalid OSU server URL</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_SERVER_CONNECTION. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_server_connection">OSU server connection failed</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_SERVER_VALIDATION. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_server_validation">OSU server validation failed</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_service_provider_verification">Invalid OSU server certificate</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_PROVISIONING_ABORTED. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_provisioning_aborted">Provisioning aborted</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_PROVISIONING_NOT_AVAILABLE. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_provisioning_not_available">Provisioning not available</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_INVALID_SERVER_URL. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_invalid_server_url">Invalid OSU server URL</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_UNEXPECTED_COMMAND_TYPE. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_unexpected_command_type">Unexpected command type</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_unexpected_soap_message_type">Unexpected SOAP message type</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_SOAP_MESSAGE_EXCHANGE. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_soap_message_exchange">SOAP message exchange failed</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_START_REDIRECT_LISTENER. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_start_redirect_listener">Redirect listener failed to start</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_timed_out_redirect_listener">Timed out waiting for redirect</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_OSU_ACTIVITY_FOUND. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_no_osu_activity_found">No OSU activity found</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_unexpected_soap_message_status">Unexpected SOAP message status</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_PPS_MO. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_no_pps_mo">Failed to find PPS-MO</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_no_aaa_server_trust_root_node">Failed to find trust root node for AAA server</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_no_remediation_server_trust_root_node">Failed to find trust root node for remediation server</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_no_policy_server_trust_root_node">Failed to find trust root node for policy server</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_retrieve_trust_root_certificates">Failed to retrieve trust root certificates</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_no_aaa_trust_root_certificate">Failed to find trust root certificate for AAA server</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_add_passpoint_configuration">Failed to add PassPoint configuration</string>
+ <!-- Status message of OSU Provider on receiving OSU_FAILURE_OSU_PROVIDER_NOT_FOUND. [CHAR LIMIT=NONE] -->
+ <string name="osu_failure_osu_provider_not_found">Failed to find an OSU provider</string>
+
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_AP_CONNECTING. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_ap_connecting">Connecting</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_AP_CONNECTED. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_ap_connected">Connected</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_SERVER_CONNECTING. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_server_connecting">Connecting to OSU server</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_SERVER_VALIDATED. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_server_validated">OSU server validated</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_SERVER_CONNECTED. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_server_connected">Connected to OSU server</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_INIT_SOAP_EXCHANGE. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_init_soap_exchange">Initial SOAP exchange</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_waiting_for_redirect_response">Waiting for redirect response</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_REDIRECT_RESPONSE_RECEIVED. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_redirect_response_received">Received redirect response</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_SECOND_SOAP_EXCHANGE. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_second_soap_exchange">Second SOAP exchange</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_THIRD_SOAP_EXCHANGE. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_third_soap_exchange">Third SOAP exchange</string>
+ <!-- Status message of OSU Provider on receiving OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS. [CHAR LIMIT=NONE] -->
+ <string name="osu_status_retrieving_trust_root_certs">Retrieving trust root certificates</string>
+
+ <!-- Status message of OSU Provider on completing provisioning. [CHAR LIMIT=NONE] -->
+ <string name="osu_provisioning_complete">Provisioning complete</string>
+
<!-- Speed label for very slow network speed -->
<string name="speed_label_very_slow">Very Slow</string>
<!-- Speed label for slow network speed -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 595aeb3..c751c39 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -38,8 +38,8 @@
private static final String CURRENT_MODE_KEY = "CURRENT_MODE";
private static final String NEW_MODE_KEY = "NEW_MODE";
@VisibleForTesting
- static final String STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY =
- "ro.storage_manager.show_opt_in";
+ static final String STORAGE_MANAGER_ENABLED_PROPERTY =
+ "ro.storage_manager.enabled";
private static Signature[] sSystemSignature;
private static String sPermissionControllerPackageName;
@@ -373,8 +373,7 @@
public static boolean isStorageManagerEnabled(Context context) {
boolean isDefaultOn;
try {
- // Turn off by default if the opt-in was shown.
- isDefaultOn = !SystemProperties.getBoolean(STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY, true);
+ isDefaultOn = SystemProperties.getBoolean(STORAGE_MANAGER_ENABLED_PROPERTY, false);
} catch (Resources.NotFoundException e) {
isDefaultOn = false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 12b8efb..4ab9a9a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -55,6 +55,12 @@
"com.android.settings.category.ia.privacy";
public static final String CATEGORY_ENTERPRISE_PRIVACY =
"com.android.settings.category.ia.enterprise_privacy";
+ public static final String CATEGORY_ABOUT_LEGAL =
+ "com.android.settings.category.ia.about_legal";
+ public static final String CATEGORY_MY_DEVICE_INFO =
+ "com.android.settings.category.ia.my_device_info";
+ public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
+ "com.android.settings.category.ia.battery_saver_settings";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 1ae1d56..27dc628 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -43,6 +43,7 @@
import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.ProvisioningCallback;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -219,6 +220,11 @@
private boolean mIsCarrierAp = false;
private OsuProvider mOsuProvider;
+
+ private String mOsuStatus;
+ private String mOsuFailure;
+ private boolean mOsuProvisioningComplete = false;
+
/**
* The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
*/
@@ -315,9 +321,7 @@
public AccessPoint(Context context, OsuProvider provider) {
mContext = context;
mOsuProvider = provider;
- mRssi = 1;
- // TODO: This placeholder SSID is here to avoid null pointer exceptions.
- ssid = "<OsuProvider AP SSID goes here>";
+ ssid = provider.getFriendlyName();
updateKey();
}
@@ -358,24 +362,13 @@
/** Updates {@link #mKey} and should only called upon object creation/initialization. */
private void updateKey() {
// TODO(sghuman): Consolidate Key logic on ScanResultMatchInfo
-
- StringBuilder builder = new StringBuilder();
-
if (isPasspoint()) {
- builder.append(KEY_PREFIX_FQDN).append(mConfig.FQDN);
+ mKey = getKey(mConfig);
} else if (isOsuProvider()) {
- builder.append(KEY_PREFIX_OSU).append(mOsuProvider.getOsuSsid());
- builder.append(',').append(mOsuProvider.getServerUri());
+ mKey = getKey(mOsuProvider);
} else { // Non-Passpoint AP
- builder.append(KEY_PREFIX_AP);
- if (TextUtils.isEmpty(getSsidStr())) {
- builder.append(getBssid());
- } else {
- builder.append(getSsidStr());
- }
- builder.append(',').append(getSecurity());
+ mKey = getKey(getSsidStr(), getBssid(), getSecurity());
}
- mKey = builder.toString();
}
/**
@@ -616,34 +609,46 @@
}
public static String getKey(ScanResult result) {
- StringBuilder builder = new StringBuilder();
-
- builder.append(KEY_PREFIX_AP);
- if (TextUtils.isEmpty(result.SSID)) {
- builder.append(result.BSSID);
- } else {
- builder.append(result.SSID);
- }
-
- builder.append(',').append(getSecurity(result));
- return builder.toString();
+ return getKey(result.SSID, result.BSSID, getSecurity(result));
}
+ /**
+ * Returns the AccessPoint key for a WifiConfiguration.
+ * This will return a special Passpoint key if the config is for Passpoint.
+ */
public static String getKey(WifiConfiguration config) {
- StringBuilder builder = new StringBuilder();
-
if (config.isPasspoint()) {
- builder.append(KEY_PREFIX_FQDN).append(config.FQDN);
+ return new StringBuilder()
+ .append(KEY_PREFIX_FQDN)
+ .append(config.FQDN).toString();
} else {
- builder.append(KEY_PREFIX_AP);
- if (TextUtils.isEmpty(config.SSID)) {
- builder.append(config.BSSID);
- } else {
- builder.append(removeDoubleQuotes(config.SSID));
- }
- builder.append(',').append(getSecurity(config));
+ return getKey(config.SSID, config.BSSID, getSecurity(config));
}
+ }
+ /**
+ * Returns the AccessPoint key corresponding to the OsuProvider.
+ */
+ public static String getKey(OsuProvider provider) {
+ return new StringBuilder()
+ .append(KEY_PREFIX_OSU)
+ .append(provider.getFriendlyName())
+ .append(',')
+ .append(provider.getServerUri()).toString();
+ }
+
+ /**
+ * Returns the AccessPoint key for a normal non-Passpoint network by ssid/bssid and security.
+ */
+ private static String getKey(String ssid, String bssid, int security) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(KEY_PREFIX_AP);
+ if (TextUtils.isEmpty(ssid)) {
+ builder.append(bssid);
+ } else {
+ builder.append(ssid);
+ }
+ builder.append(',').append(security);
return builder.toString();
}
@@ -881,7 +886,17 @@
// Update to new summary
StringBuilder summary = new StringBuilder();
- if (isActive()) {
+ if (isOsuProvider()) {
+ if (mOsuProvisioningComplete) {
+ summary.append(mContext.getString(R.string.osu_provisioning_complete));
+ } else if (mOsuFailure != null) {
+ summary.append(mOsuFailure);
+ } else if (mOsuStatus != null) {
+ summary.append(mOsuStatus);
+ } else {
+ summary.append(mContext.getString(R.string.tap_to_set_up));
+ }
+ } else if (isActive()) {
if (isPasspoint()) {
// This is the active connection on passpoint
summary.append(getSummary(mContext, ssid, getDetailedState(),
@@ -1014,11 +1029,26 @@
}
/**
+ * Starts the OSU Provisioning flow.
+ */
+ public void startOsuProvisioning() {
+ mContext.getSystemService(WifiManager.class).startSubscriptionProvisioning(
+ mOsuProvider,
+ new AccessPointProvisioningCallback(),
+ ThreadUtils.getUiThreadHandler()
+ );
+ }
+
+ /**
* Return whether the given {@link WifiInfo} is for this access point.
* If the current AP does not have a network Id then the config is used to
* match based on SSID and security.
*/
private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
+ if (info.isOsuAp()) {
+ return (mOsuStatus != null);
+ }
+
if (isPasspoint() == false && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
return networkId == info.getNetworkId();
} else if (config != null) {
@@ -1491,4 +1521,166 @@
private static boolean isVerboseLoggingEnabled() {
return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
}
+
+ /**
+ * Callbacks relaying changes to the OSU provisioning status started in startOsuProvisioning().
+ *
+ * All methods are invoked on the Main Thread
+ */
+ private class AccessPointProvisioningCallback extends ProvisioningCallback {
+ // TODO: Remove logs and implement summary changing logic for these provisioning callbacks.
+ @Override
+ @MainThread public void onProvisioningFailure(int status) {
+ switch (status) {
+ case OSU_FAILURE_AP_CONNECTION:
+ mOsuFailure = mContext.getString(R.string.osu_failure_ap_connection);
+ break;
+ case OSU_FAILURE_SERVER_URL_INVALID:
+ mOsuFailure = mContext.getString(R.string.osu_failure_server_url_invalid);
+ break;
+ case OSU_FAILURE_SERVER_CONNECTION:
+ mOsuFailure = mContext.getString(R.string.osu_failure_server_connection);
+ break;
+ case OSU_FAILURE_SERVER_VALIDATION:
+ mOsuFailure = mContext.getString(R.string.osu_failure_server_validation);
+ break;
+ case OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_service_provider_verification);
+ break;
+ case OSU_FAILURE_PROVISIONING_ABORTED:
+ mOsuFailure = mContext.getString(R.string.osu_failure_provisioning_aborted);
+ break;
+ case OSU_FAILURE_PROVISIONING_NOT_AVAILABLE:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_provisioning_not_available);
+ break;
+ case OSU_FAILURE_INVALID_SERVER_URL:
+ mOsuFailure = mContext.getString(R.string.osu_failure_invalid_server_url);
+ break;
+ case OSU_FAILURE_UNEXPECTED_COMMAND_TYPE:
+ mOsuFailure = mContext.getString(R.string.osu_failure_unexpected_command_type);
+ break;
+ case OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_unexpected_soap_message_type);
+ break;
+ case OSU_FAILURE_SOAP_MESSAGE_EXCHANGE:
+ mOsuFailure = mContext.getString(R.string.osu_failure_soap_message_exchange);
+ break;
+ case OSU_FAILURE_START_REDIRECT_LISTENER:
+ mOsuFailure = mContext.getString(R.string.osu_failure_start_redirect_listener);
+ break;
+ case OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_timed_out_redirect_listener);
+ break;
+ case OSU_FAILURE_NO_OSU_ACTIVITY_FOUND:
+ mOsuFailure = mContext.getString(R.string.osu_failure_no_osu_activity_found);
+ break;
+ case OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_unexpected_soap_message_status);
+ break;
+ case OSU_FAILURE_NO_PPS_MO:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_no_pps_mo);
+ break;
+ case OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_no_aaa_server_trust_root_node);
+ break;
+ case OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_no_remediation_server_trust_root_node);
+ break;
+ case OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_no_policy_server_trust_root_node);
+ break;
+ case OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_retrieve_trust_root_certificates);
+ break;
+ case OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_no_aaa_trust_root_certificate);
+ break;
+ case OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION:
+ mOsuFailure = mContext.getString(
+ R.string.osu_failure_add_passpoint_configuration);
+ break;
+ case OSU_FAILURE_OSU_PROVIDER_NOT_FOUND:
+ mOsuFailure = mContext.getString(R.string.osu_failure_osu_provider_not_found);
+ break;
+ }
+ mOsuStatus = null;
+ mOsuProvisioningComplete = false;
+ ThreadUtils.postOnMainThread(() -> {
+ if (mAccessPointListener != null) {
+ mAccessPointListener.onAccessPointChanged(AccessPoint.this);
+ }
+ });
+ }
+
+ @Override
+ @MainThread public void onProvisioningStatus(int status) {
+ switch (status) {
+ case OSU_STATUS_AP_CONNECTING:
+ mOsuStatus = mContext.getString(R.string.osu_status_ap_connecting);
+ break;
+ case OSU_STATUS_AP_CONNECTED:
+ mOsuStatus = mContext.getString(R.string.osu_status_ap_connected);
+ break;
+ case OSU_STATUS_SERVER_CONNECTING:
+ mOsuStatus = mContext.getString(R.string.osu_status_server_connecting);
+ break;
+ case OSU_STATUS_SERVER_VALIDATED:
+ mOsuStatus = mContext.getString(R.string.osu_status_server_validated);
+ break;
+ case OSU_STATUS_SERVER_CONNECTED:
+ mOsuStatus = mContext.getString(R.string.osu_status_server_connected);
+ break;
+ case OSU_STATUS_INIT_SOAP_EXCHANGE:
+ mOsuStatus = mContext.getString(R.string.osu_status_init_soap_exchange);
+ break;
+ case OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE:
+ mOsuStatus = mContext.getString(
+ R.string.osu_status_waiting_for_redirect_response);
+ break;
+ case OSU_STATUS_REDIRECT_RESPONSE_RECEIVED:
+ mOsuStatus = mContext.getString(R.string.osu_status_redirect_response_received);
+ break;
+ case OSU_STATUS_SECOND_SOAP_EXCHANGE:
+ mOsuStatus = mContext.getString(R.string.osu_status_second_soap_exchange);
+ break;
+ case OSU_STATUS_THIRD_SOAP_EXCHANGE:
+ mOsuStatus = mContext.getString(R.string.osu_status_third_soap_exchange);
+ break;
+ case OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS:
+ mOsuStatus = mContext.getString(
+ R.string.osu_status_retrieving_trust_root_certs);
+ break;
+ }
+ mOsuFailure = null;
+ mOsuProvisioningComplete = false;
+ ThreadUtils.postOnMainThread(() -> {
+ if (mAccessPointListener != null) {
+ mAccessPointListener.onAccessPointChanged(AccessPoint.this);
+ }
+ });
+ }
+
+ @Override
+ @MainThread public void onProvisioningComplete() {
+ mOsuProvisioningComplete = true;
+ mOsuFailure = null;
+ mOsuStatus = null;
+ ThreadUtils.postOnMainThread(() -> {
+ if (mAccessPointListener != null) {
+ mAccessPointListener.onAccessPointChanged(AccessPoint.this);
+ }
+ });
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 6d28891..b9a5f23 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -68,6 +68,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -585,7 +586,6 @@
Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) {
WifiConfiguration config = pairing.first;
- // TODO(b/118705403): Prioritize home networks before roaming networks
List<ScanResult> scanResults = new ArrayList<>();
List<ScanResult> homeScans =
@@ -600,8 +600,12 @@
roamingScans = new ArrayList<>();
}
- scanResults.addAll(homeScans);
- scanResults.addAll(roamingScans);
+ // TODO(b/118705403): Differentiate home network vs roaming network for summary info
+ if (!homeScans.isEmpty()) {
+ scanResults.addAll(homeScans);
+ } else {
+ scanResults.addAll(roamingScans);
+ }
if (seenFQDNs.add(config.FQDN)) {
int bestRssi = Integer.MIN_VALUE;
@@ -612,27 +616,27 @@
}
}
- AccessPoint accessPoint = new AccessPoint(mContext, config);
- accessPoint.setScanResults(scanResults);
+ AccessPoint accessPoint =
+ getCachedOrCreatePasspoint(scanResults, cachedAccessPoints, config);
accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
accessPoints.add(accessPoint);
}
}
// Add Passpoint OSU Provider AccessPoints
- // TODO(b/118705403): filter out OSU Providers which we already have credentials from.
Map<OsuProvider, List<ScanResult>> providersAndScans =
mWifiManager.getMatchingOsuProviders(cachedScanResults);
+ Set<OsuProvider> alreadyProvisioned = mWifiManager
+ .getMatchingPasspointConfigsForOsuProviders(
+ providersAndScans.keySet()).keySet();
for (OsuProvider provider : providersAndScans.keySet()) {
- AccessPoint accessPointOsu = new AccessPoint(mContext, provider);
- // TODO(b/118705403): accessPointOsu.setScanResults(Matching ScanResult with best
- // RSSI)
- // TODO(b/118705403): Figure out if we would need to update an OSU AP (this will be
- // used if we need to display it at the top of the picker as the "active" AP).
- // Otherwise, OSU APs should ignore attempts to update the active connection
- // info.
- // accessPointOsu.update(connectionConfig, mLastInfo, mLastNetworkInfo);
- accessPoints.add(accessPointOsu);
+ if (!alreadyProvisioned.contains(provider)) {
+ AccessPoint accessPointOsu =
+ getCachedOrCreateOsu(providersAndScans.get(provider),
+ cachedAccessPoints, provider);
+ accessPointOsu.update(connectionConfig, mLastInfo, mLastNetworkInfo);
+ accessPoints.add(accessPointOsu);
+ }
}
@@ -685,16 +689,49 @@
AccessPoint getCachedOrCreate(
List<ScanResult> scanResults,
List<AccessPoint> cache) {
- final int N = cache.size();
- for (int i = 0; i < N; i++) {
- if (cache.get(i).getKey().equals(AccessPoint.getKey(scanResults.get(0)))) {
- AccessPoint ret = cache.remove(i);
- ret.setScanResults(scanResults);
- return ret;
+ AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(scanResults.get(0)));
+ if (accessPoint == null) {
+ accessPoint = new AccessPoint(mContext, scanResults);
+ } else {
+ accessPoint.setScanResults(scanResults);
+ }
+ return accessPoint;
+ }
+
+ private AccessPoint getCachedOrCreatePasspoint(
+ List<ScanResult> scanResults,
+ List<AccessPoint> cache,
+ WifiConfiguration config) {
+ AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(config));
+ if (accessPoint == null) {
+ accessPoint = new AccessPoint(mContext, config);
+ }
+ accessPoint.setScanResults(scanResults);
+ return accessPoint;
+ }
+
+ private AccessPoint getCachedOrCreateOsu(
+ List<ScanResult> scanResults,
+ List<AccessPoint> cache,
+ OsuProvider provider) {
+ AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(provider));
+ if (accessPoint == null) {
+ accessPoint = new AccessPoint(mContext, provider);
+ }
+ accessPoint.setScanResults(scanResults);
+ return accessPoint;
+ }
+
+ private AccessPoint getCachedByKey(List<AccessPoint> cache, String key) {
+ ListIterator<AccessPoint> lit = cache.listIterator();
+ while (lit.hasNext()) {
+ AccessPoint currentAccessPoint = lit.next();
+ if (currentAccessPoint.getKey().equals(key)) {
+ lit.remove();
+ return currentAccessPoint;
}
}
- final AccessPoint accessPoint = new AccessPoint(mContext, scanResults);
- return accessPoint;
+ return null;
}
private void updateNetworkInfo(NetworkInfo networkInfo) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 86f0438..92ebe44 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -17,7 +17,7 @@
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static com.android.settingslib.Utils.STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY;
+import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
import static com.google.common.truth.Truth.assertThat;
@@ -159,7 +159,7 @@
@Test
public void testIsStorageManagerEnabled_UsesSystemProperties() {
- SystemProperties.set(STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY, "false");
+ SystemProperties.set(STORAGE_MANAGER_ENABLED_PROPERTY, "true");
assertThat(Utils.isStorageManagerEnabled(mContext)).isTrue();
}
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index afb9781..2d7471d 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1953,8 +1953,7 @@
@Override
public void onProgress(int progress) throws RemoteException {
- // TODO(b/111441001): change max argument?
- updateProgressInfo(progress, CAPPED_MAX);
+ updateProgressInfo(progress, 100 /* progress is already a percentage; so max = 100 */);
}
@Override
diff --git a/packages/SystemUI/res-keyguard/layout/stretchanalog_clock.xml b/packages/SystemUI/res-keyguard/layout/stretchanalog_clock.xml
new file mode 100644
index 0000000..9033fce
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/stretchanalog_clock.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<com.android.keyguard.clock.ClockLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <TextClock
+ android:id="@+id/digital_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:letterSpacing="0.03"
+ android:singleLine="true"
+ style="@style/widget_big"
+ android:format12Hour="@string/keyguard_widget_12_hours_format"
+ android:format24Hour="@string/keyguard_widget_24_hours_format"
+ />
+ <com.android.keyguard.clock.StretchAnalogClock
+ android:id="@+id/analog_clock"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+</com.android.keyguard.clock.ClockLayout>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 41e9eba..a055950 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -46,6 +46,7 @@
protected View mEcaView;
protected boolean mEnableHaptics;
private boolean mDismissing;
+ protected boolean mResumed;
private CountDownTimer mCountdownTimer = null;
// To avoid accidental lockout due to events while the device in in the pocket, ignore
@@ -263,6 +264,8 @@
@Override
public void onPause() {
+ mResumed = false;
+
if (mCountdownTimer != null) {
mCountdownTimer.cancel();
mCountdownTimer = null;
@@ -276,6 +279,7 @@
@Override
public void onResume(int reason) {
+ mResumed = true;
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 0ec0bf0..a8094d20 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -19,6 +19,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.keyguard.clock.BubbleClockController;
+import com.android.keyguard.clock.StretchAnalogClockController;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
@@ -146,6 +147,12 @@
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
BubbleClockController.class.getName(),
() -> BubbleClockController.build(mLayoutInflater)))
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ StretchAnalogClockController.class.getName(),
+ () -> StretchAnalogClockController.build(mLayoutInflater)))
.build();
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 41afa9a..3296c10 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -81,6 +81,11 @@
protected void resetState() {
mSecurityMessageDisplay.setMessage("");
final boolean wasDisabled = mPasswordEntry.isEnabled();
+ // Don't set enabled password entry & showSoftInput when PasswordEntry is invisible or in
+ // pausing stage.
+ if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
+ return;
+ }
setPasswordEntryEnabled(true);
setPasswordEntryInputEnabled(true);
if (wasDisabled) {
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java
new file mode 100644
index 0000000..91cec863
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java
@@ -0,0 +1,141 @@
+/*
+ * 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.keyguard.clock;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Analog clock where the minute hand extends off of the screen.
+ */
+public class StretchAnalogClock extends View {
+
+ private static final int DEFAULT_COLOR = Color.parseColor("#F5C983");
+ private static final float HOUR_STROKE_WIDTH = 60f;
+ private static final float MINUTE_STROKE_WIDTH = 20f;
+ private static final float CENTER_GAP_AND_CIRCLE_RADIUS = 80f;
+
+ private final Paint mHourPaint = new Paint();
+ private final Paint mMinutePaint = new Paint();
+ private Calendar mTime;
+ private TimeZone mTimeZone;
+
+ public StretchAnalogClock(Context context) {
+ this(context, null);
+ }
+
+ public StretchAnalogClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public StretchAnalogClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public StretchAnalogClock(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ /**
+ * Call when the time changes to update the clock hands.
+ */
+ public void onTimeChanged() {
+ mTime.setTimeInMillis(System.currentTimeMillis());
+ invalidate();
+ }
+
+ /**
+ * Call when the time zone has changed to update clock hands.
+ *
+ * @param timeZone The updated time zone that will be used.
+ */
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mTime.setTimeZone(timeZone);
+ }
+
+ /**
+ * Set the color of the minute hand.
+ */
+ public void setMinuteHandColor(int color) {
+ mMinutePaint.setColor(color);
+ }
+
+ private void init() {
+ mHourPaint.setColor(DEFAULT_COLOR);
+ mHourPaint.setStrokeWidth(HOUR_STROKE_WIDTH);
+ mHourPaint.setAntiAlias(true);
+ mHourPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mMinutePaint.setColor(Color.WHITE);
+ mMinutePaint.setStrokeWidth(MINUTE_STROKE_WIDTH);
+ mMinutePaint.setAntiAlias(true);
+ mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final float centerX = getWidth() / 2f;
+ final float centerY = getHeight() / 2f;
+
+ final float minutesRotation = mTime.get(Calendar.MINUTE) * 6f;
+ final float hoursRotation = (mTime.get(Calendar.HOUR) * 30);
+
+ // Compute length of clock hands. Hour hand is 60% the length from center to edge
+ // and minute hand is twice the length to make sure it extends past screen edge.
+ double sMinuteHandLengthFactor = Math.sin(2d * Math.PI * minutesRotation / 360d);
+ float sMinuteHandLength = (float) (2d * (centerY + (centerX - centerY)
+ * sMinuteHandLengthFactor * sMinuteHandLengthFactor));
+ double sHourHandLengthFactor = Math.sin(2d * Math.PI * hoursRotation / 360d);
+ float sHourHandLength = (float) (0.6d * (centerY + (centerX - centerY)
+ * sHourHandLengthFactor * sHourHandLengthFactor));
+
+ canvas.save();
+
+ canvas.rotate(minutesRotation, centerX, centerY);
+ canvas.drawLine(
+ centerX,
+ centerY + CENTER_GAP_AND_CIRCLE_RADIUS,
+ centerX,
+ centerY - sMinuteHandLength,
+ mMinutePaint);
+
+ canvas.rotate(hoursRotation - minutesRotation, centerX, centerY);
+ canvas.drawLine(
+ centerX,
+ centerY + CENTER_GAP_AND_CIRCLE_RADIUS,
+ centerX,
+ centerY - sHourHandLength,
+ mHourPaint);
+
+ canvas.restore();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
+ onTimeChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
new file mode 100644
index 0000000..0a39158
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
@@ -0,0 +1,115 @@
+/*
+ * 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.keyguard.clock;
+
+import android.graphics.Paint.Style;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextClock;
+
+import com.android.keyguard.R;
+import com.android.systemui.plugins.ClockPlugin;
+
+import java.util.TimeZone;
+
+/**
+ * Controller for Stretch clock that can appear on lock screen and AOD.
+ */
+public class StretchAnalogClockController implements ClockPlugin {
+
+ /**
+ * Custom clock shown on AOD screen and behind stack scroller on lock.
+ */
+ private View mBigClockView;
+ private TextClock mDigitalClock;
+ private StretchAnalogClock mAnalogClock;
+
+ /**
+ * Small clock shown on lock screen above stack scroller.
+ */
+ private View mView;
+ private TextClock mLockClock;
+
+ /**
+ * Controller for transition to dark state.
+ */
+ private CrossFadeDarkController mDarkController;
+
+ private StretchAnalogClockController() { }
+
+ /**
+ * Create a BubbleClockController instance.
+ *
+ * @param layoutInflater Inflater used to inflate custom clock views.
+ */
+ public static StretchAnalogClockController build(LayoutInflater layoutInflater) {
+ StretchAnalogClockController controller = new StretchAnalogClockController();
+ controller.createViews(layoutInflater);
+ return controller;
+ }
+
+ private void createViews(LayoutInflater layoutInflater) {
+ mBigClockView = layoutInflater.inflate(R.layout.stretchanalog_clock, null);
+ mAnalogClock = mBigClockView.findViewById(R.id.analog_clock);
+ mDigitalClock = mBigClockView.findViewById(R.id.digital_clock);
+
+ mView = layoutInflater.inflate(R.layout.digital_clock, null);
+ mLockClock = mView.findViewById(R.id.lock_screen_clock);
+ mLockClock.setVisibility(View.GONE);
+
+ mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock);
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public View getBigClockView() {
+ return mBigClockView;
+ }
+
+ @Override
+ public void setStyle(Style style) {}
+
+ @Override
+ public void setTextColor(int color) {
+ mLockClock.setTextColor(color);
+ mDigitalClock.setTextColor(color);
+ mAnalogClock.setMinuteHandColor(color);
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ mAnalogClock.onTimeChanged();
+ }
+
+ @Override
+ public void setDarkAmount(float darkAmount) {
+ mDarkController.setDarkAmount(darkAmount);
+ }
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mAnalogClock.onTimeZoneChanged(timeZone);
+ }
+
+ @Override
+ public boolean shouldShowStatusArea() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 9422101..f79ad71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -541,7 +541,8 @@
@VisibleForTesting
void doUpdateMobileControllers() {
- List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
+ List<SubscriptionInfo> subscriptions = mSubscriptionManager
+ .getActiveSubscriptionInfoList(true);
if (subscriptions == null) {
subscriptions = Collections.emptyList();
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java
new file mode 100644
index 0000000..8de8f3d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.keyguard.clock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public final class StretchAnalogClockControllerTest extends SysuiTestCase {
+
+ private StretchAnalogClockController mClockController;
+
+ @Before
+ public void setUp() {
+ LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ mClockController = StretchAnalogClockController.build(layoutInflater);
+ }
+
+ @Test
+ public void setDarkAmount_fadeIn() {
+ ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
+ View smallClock = smallClockFrame.getChildAt(0);
+ // WHEN dark amount is set to AOD
+ mClockController.setDarkAmount(1f);
+ // THEN small clock should not be shown.
+ assertThat(smallClock.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void setTextColor_setDigitalClock() {
+ ViewGroup smallClock = (ViewGroup) mClockController.getView();
+ // WHEN text color is set
+ mClockController.setTextColor(42);
+ // THEN child of small clock should have text color set.
+ TextView digitalClock = (TextView) smallClock.getChildAt(0);
+ assertThat(digitalClock.getCurrentTextColor()).isEqualTo(42);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index fdbf090..c1f8885 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -182,6 +182,7 @@
subs.add(subscription);
}
when(mMockSm.getActiveSubscriptionInfoList()).thenReturn(subs);
+ when(mMockSm.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subs);
mNetworkController.doUpdateMobileControllers();
}
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index 295b3d8..be9ba43 100644
--- a/packages/services/PacProcessor/Android.mk
+++ b/packages/services/PacProcessor/Android.mk
@@ -26,6 +26,6 @@
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
-LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor libpac
+LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor
include $(BUILD_PACKAGE)
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 24fd7b9..ec6d20d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -701,6 +701,11 @@
public void onBackKeyPressed() {
if (sDebug) Slog.d(TAG, "onBackKeyPressed()");
mUi.hideAll(null);
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ getServiceForUserLocked(UserHandle.getCallingUserId());
+ service.onBackKeyPressed();
+ }
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index a6bb049..d037b08 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -187,6 +187,15 @@
}
@GuardedBy("mLock")
+ void onBackKeyPressed() {
+ final RemoteAugmentedAutofillService remoteService =
+ getRemoteAugmentedAutofillServiceLocked();
+ if (remoteService != null) {
+ remoteService.onDestroyAutofillWindowsRequest();
+ }
+ }
+
+ @GuardedBy("mLock")
@Override // from PerUserSystemService
protected boolean updateLocked(boolean disabled) {
destroySessionsLocked();
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index a8ff9b0..5d8d8fa 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -109,8 +109,8 @@
/**
* Called by {@link Session} when it's time to destroy all augmented autofill requests.
*/
- public void onDestroyAutofillWindowsRequest(int sessionId) {
- scheduleAsyncRequest((s) -> s.onDestroyFillWindowRequest(sessionId));
+ public void onDestroyAutofillWindowsRequest() {
+ scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
}
// TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index cf4963c..a5ef21a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2619,7 +2619,7 @@
currentValue);
if (mAugmentedAutofillDestroyer == null) {
- mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(id);
+ mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
}
return mAugmentedAutofillDestroyer;
}
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 529430c..a7bada0 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -562,7 +562,7 @@
private void registerTransportsFromPackage(
String packageName, Predicate<ComponentName> transportComponentFilter) {
try {
- mPackageManager.getPackageInfo(packageName, 0);
+ mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Trying to register transports from package not found " + packageName);
return;
@@ -599,7 +599,8 @@
return false;
}
try {
- PackageInfo packInfo = mPackageManager.getPackageInfo(transport.getPackageName(), 0);
+ PackageInfo packInfo =
+ mPackageManager.getPackageInfoAsUser(transport.getPackageName(), 0, mUserId);
if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
== 0) {
Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 3a8966a..e0e81ff 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -495,7 +495,7 @@
mBaseStateDir = checkNotNull(baseStateDir, "baseStateDir cannot be null");
mBaseStateDir.mkdirs();
- if (!SELinux.restorecon(mBaseStateDir)) {
+ if (!SELinux.restoreconRecursive(mBaseStateDir)) {
Slog.w(TAG, "SELinux restorecon failed on " + mBaseStateDir);
}
@@ -504,7 +504,7 @@
// is managed by init.rc so we don't have to create it below.
if (userId != UserHandle.USER_SYSTEM) {
mDataDir.mkdirs();
- if (!SELinux.restorecon(mDataDir)) {
+ if (!SELinux.restoreconRecursive(mDataDir)) {
Slog.w(TAG, "SELinux restorecon failed on " + mDataDir);
}
}
@@ -584,7 +584,7 @@
mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
// Power management
- mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
+ mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*-" + userId);
}
void initializeBackupEnableState() {
@@ -1352,7 +1352,7 @@
private List<PackageInfo> allAgentPackages() {
// !!! TODO: cache this and regenerate only when necessary
int flags = PackageManager.GET_SIGNING_CERTIFICATES;
- List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags);
+ List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(flags, mUserId);
int numPackages = packages.size();
for (int a = numPackages - 1; a >= 0; a--) {
PackageInfo pkg = packages.get(a);
@@ -1366,8 +1366,8 @@
// we will need the shared library path, so look that up and store it here.
// This is used implicitly when we pass the PackageInfo object off to
// the Activity Manager to launch the app for backup/restore purposes.
- app = mPackageManager.getApplicationInfo(pkg.packageName,
- PackageManager.GET_SHARED_LIBRARY_FILES);
+ app = mPackageManager.getApplicationInfoAsUser(pkg.packageName,
+ PackageManager.GET_SHARED_LIBRARY_FILES, mUserId);
pkg.applicationInfo.sharedLibraryFiles = app.sharedLibraryFiles;
pkg.applicationInfo.sharedLibraryInfos = app.sharedLibraryInfos;
}
@@ -1392,7 +1392,7 @@
notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES
| Intent.FLAG_RECEIVER_FOREGROUND);
notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName);
- mContext.sendBroadcastAsUser(notification, UserHandle.OWNER);
+ mContext.sendBroadcastAsUser(notification, UserHandle.of(mUserId));
}
mProcessedPackagesJournal.addPackage(packageName);
@@ -2208,11 +2208,10 @@
/** Used by both incremental and full restore to restore widget data. */
public void restoreWidgetData(String packageName, byte[] widgetData) {
// Apply the restored widget state and generate the ID update for the app
- // TODO: http://b/22388012
if (MORE_DEBUG) {
Slog.i(TAG, "Incorporating restored widget data");
}
- AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, UserHandle.USER_SYSTEM);
+ AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, mUserId);
}
// *****************************
@@ -2291,20 +2290,6 @@
/** Sent from an app's backup agent to let the service know that there's new data to backup. */
public void dataChanged(final String packageName) {
- final int callingUserHandle = UserHandle.getCallingUserId();
- if (callingUserHandle != UserHandle.USER_SYSTEM) {
- // TODO: http://b/22388012
- // App is running under a non-owner user profile. For now, we do not back
- // up data from secondary user profiles.
- // TODO: backups for all user profiles although don't add backup for profiles
- // without adding admin control in DevicePolicyManager.
- if (MORE_DEBUG) {
- Slog.v(TAG, "dataChanged(" + packageName + ") ignored because it's user "
- + callingUserHandle);
- }
- return;
- }
-
final HashSet<String> targets = dataChangedTargets(packageName);
if (targets == null) {
Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
@@ -2921,7 +2906,7 @@
try {
int transportUid =
mContext.getPackageManager()
- .getPackageUid(transportComponent.getPackageName(), 0);
+ .getPackageUidAsUser(transportComponent.getPackageName(), 0, mUserId);
if (callingUid != transportUid) {
throw new SecurityException("Only the transport can change its description");
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index c7f3315..b3d9fbc 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -68,6 +68,8 @@
public class FullRestoreEngine extends RestoreEngine {
private final UserBackupManagerService mBackupManagerService;
+ private final int mUserId;
+
// Task in charge of monitoring timeouts
private final BackupRestoreTask mMonitorTask;
@@ -146,6 +148,7 @@
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mIsAdbRestore = isAdbRestore;
+ mUserId = backupManagerService.getUserId();
}
public IBackupAgent getAgent() {
@@ -272,7 +275,7 @@
instream, mBackupManagerService.getContext(),
mDeleteObserver, mManifestSignatures,
mPackagePolicies, info, installerPackageName,
- bytesReadListener, mBackupManagerService.getUserId());
+ bytesReadListener, mUserId);
// good to go; promote to ACCEPT
mPackagePolicies.put(pkg, isSuccessfullyInstalled
? RestorePolicy.ACCEPT
@@ -329,9 +332,8 @@
}
try {
- mTargetApp =
- mBackupManagerService.getPackageManager().getApplicationInfo(
- pkg, 0);
+ mTargetApp = mBackupManagerService.getPackageManager()
+ .getApplicationInfoAsUser(pkg, 0, mUserId);
// If we haven't sent any data to this app yet, we probably
// need to clear it first. Check that.
@@ -684,7 +686,7 @@
String packageListString = Settings.Secure.getStringForUser(
mBackupManagerService.getContext().getContentResolver(),
Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE,
- mBackupManagerService.getUserId());
+ mUserId);
if (TextUtils.isEmpty(packageListString)) {
return false;
}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 5284d94..d01f77b 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -360,7 +360,6 @@
// If we're starting a full-system restore, set up to begin widget ID remapping
if (mIsSystemRestore) {
- // TODO: http://b/22388012
AppWidgetBackupBridge.restoreStarting(mUserId);
}
@@ -1079,7 +1078,6 @@
}
// Kick off any work that may be needed regarding app widget restores
- // TODO: http://b/22388012
AppWidgetBackupBridge.restoreFinished(mUserId);
// If this was a full-system restore, record the ancestral
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 09aa421..1dae2ce 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -17,6 +17,9 @@
package com.android.server.contentcapture;
import static android.service.contentcapture.ContentCaptureService.setClientState;
+import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
+import static android.view.contentcapture.ContentCaptureSession.STATE_DUPLICATED_ID;
+import static android.view.contentcapture.ContentCaptureSession.STATE_NO_SERVICE;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
@@ -36,10 +39,11 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.service.contentcapture.ContentCaptureService;
+import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
-import android.view.contentcapture.ContentCaptureSession;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
@@ -48,6 +52,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* Per-user instance of {@link ContentCaptureManagerService}.
@@ -72,6 +77,9 @@
@GuardedBy("mLock")
private RemoteContentCaptureService mRemoteService;
+ private final ContentCaptureServiceRemoteCallback mRemoteServiceCallback =
+ new ContentCaptureServiceRemoteCallback();
+
// TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
ContentCapturePerUserService(@NonNull ContentCaptureManagerService master,
@@ -100,10 +108,10 @@
}
if (!disabled) {
- mRemoteService = new RemoteContentCaptureService(
- mMaster.getContext(),
- ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, mUserId, this,
- mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ mRemoteService = new RemoteContentCaptureService(mMaster.getContext(),
+ ContentCaptureService.SERVICE_INTERFACE, serviceComponentName,
+ mRemoteServiceCallback, mUserId, this, mMaster.isBindInstantServiceAllowed(),
+ mMaster.verbose);
}
}
@@ -165,7 +173,7 @@
if (!isEnabledLocked()) {
// TODO: it would be better to split in differet reasons, like
// STATE_DISABLED_NO_SERVICE and STATE_DISABLED_BY_DEVICE_POLICY
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_NO_SERVICE,
+ setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
/* binder= */ null);
return;
}
@@ -184,7 +192,7 @@
if (existingSession != null) {
Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
+ ": ignoring because it already exists for " + existingSession.mActivityToken);
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_DUPLICATED_ID,
+ setClientState(clientReceiver, STATE_DISABLED | STATE_DUPLICATED_ID,
/* binder=*/ null);
return;
}
@@ -197,8 +205,7 @@
// TODO(b/119613670): log metrics
Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
+ ": ignoring because service is not set");
- // TODO(b/111276913): use a new disabled state?
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_NO_SERVICE,
+ setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
/* binder= */ null);
return;
}
@@ -338,4 +345,50 @@
}
return null;
}
+
+ private final class ContentCaptureServiceRemoteCallback extends
+ IContentCaptureServiceCallback.Stub {
+
+ @Override
+ public void setContentCaptureWhitelist(List<String> packages,
+ List<ComponentName> activities) {
+ if (mMaster.verbose) {
+ Log.v(TAG, "setContentCaptureWhitelist(packages=" + packages + ", activities="
+ + activities + ")");
+ }
+ // TODO(b/122595322): implement
+ // TODO(b/119613670): log metrics
+ }
+
+ @Override
+ public void setActivityContentCaptureEnabled(ComponentName activity, boolean enabled) {
+ if (mMaster.verbose) {
+ Log.v(TAG, "setActivityContentCaptureEnabled(activity=" + activity + ", enabled="
+ + enabled + ")");
+ }
+ // TODO(b/122595322): implement
+ // TODO(b/119613670): log metrics
+ }
+
+ @Override
+ public void setPackageContentCaptureEnabled(String packageName, boolean enabled) {
+ if (mMaster.verbose) {
+ Log.v(TAG,
+ "setPackageContentCaptureEnabled(packageName=" + packageName + ", enabled="
+ + enabled + ")");
+ }
+ // TODO(b/122595322): implement
+ // TODO(b/119613670): log metrics
+ }
+
+ @Override
+ public void getContentCaptureDisabledActivities(IResultReceiver receiver) {
+ // TODO(b/122595322): implement
+ }
+
+ @Override
+ public void getContentCaptureDisabledPackages(IResultReceiver receiver) {
+ // TODO(b/122595322): implement
+ }
+ }
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 8241628..12742ca 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.os.IBinder;
import android.service.contentcapture.IContentCaptureService;
+import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
import android.text.format.DateUtils;
import android.util.Slog;
@@ -35,12 +36,15 @@
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+ private final IBinder mServerCallback;
+
RemoteContentCaptureService(Context context, String serviceInterface,
- ComponentName componentName, int userId,
+ ComponentName serviceComponentName, IContentCaptureServiceCallback callback, int userId,
ContentCaptureServiceCallbacks callbacks, boolean bindInstantServiceAllowed,
boolean verbose) {
- super(context, serviceInterface, componentName, userId, callbacks,
+ super(context, serviceInterface, serviceComponentName, userId, callbacks,
bindInstantServiceAllowed, verbose, /* initialCapacity= */ 2);
+ mServerCallback = callback.asBinder();
// Bind right away, which will trigger a onConnected() on service's
scheduleBind();
@@ -69,7 +73,11 @@
scheduleUnbind();
}
try {
- mService.onConnectedStateChanged(state);
+ if (state) {
+ mService.onConnected(mServerCallback);
+ } else {
+ mService.onDisconnected();
+ }
} catch (Exception e) {
Slog.w(mTag, "Exception calling onConnectedStateChanged(" + state + "): " + e);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d0666b9..d6f3e2b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4879,7 +4879,7 @@
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
- mContext, mTrackerHandler, new NetworkMisc(networkMisc), this);
+ mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mNMS);
// Make sure the network capabilities reflect what the agent info says.
nai.networkCapabilities = mixInCapabilities(nai, nc);
final String extraInfo = networkInfo.getExtraInfo();
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 869d564..0b4c01e 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -257,6 +257,9 @@
packageManagerInternal.setLocationPackagesProvider(
userId -> mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationProviderPackageNames));
+ packageManagerInternal.setLocationExtraPackagesProvider(
+ userId -> mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_locationExtraPackageNames));
// most startup is deferred until systemRunning()
}
@@ -1041,12 +1044,13 @@
@Override
public void onSetProperties(ProviderProperties properties) {
- // move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
- synchronized (mLock) {
- mProperties = properties;
- }
- });
+ // because this does not invoke any other methods which might result in calling back
+ // into the location provider, it is safe to run this on the calling thread. it is also
+ // currently necessary to run this on the calling thread to ensure that property changes
+ // are publicly visibly immediately, ie for mock providers which are created.
+ synchronized (mLock) {
+ mProperties = properties;
+ }
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index b0ca2df..aed0684 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -17,13 +17,11 @@
package com.android.server;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
-import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.SHUTDOWN;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
-import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_NONE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE;
@@ -40,6 +38,7 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_TETHERING;
+
import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
@@ -53,11 +52,9 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
-import android.content.ContentResolver;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetd;
-import android.net.TetherStatsParcel;
import android.net.INetworkManagementEventObserver;
import android.net.ITetheringStatsProvider;
import android.net.InterfaceConfiguration;
@@ -69,18 +66,15 @@
import android.net.NetworkStats;
import android.net.NetworkUtils;
import android.net.RouteInfo;
+import android.net.TetherStatsParcel;
import android.net.UidRange;
-import android.net.UidRangeParcel;
import android.net.util.NetdService;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkActivityListener;
import android.os.INetworkManagementService;
-import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -91,12 +85,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
-import android.provider.Settings;
import android.telephony.DataConnectionRealTimeInfo;
-import android.telephony.PhoneStateListener;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -110,13 +99,11 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.HexDump;
import com.android.internal.util.Preconditions;
-import com.android.server.NativeDaemonConnector.Command;
-import com.android.server.NativeDaemonConnector.SensitiveArg;
+
import com.google.android.collect.Maps;
import java.io.BufferedReader;
import java.io.DataInputStream;
-import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
@@ -124,15 +111,11 @@
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
/**
@@ -2153,28 +2136,6 @@
}
@Override
- public void startClatd(String interfaceName) throws IllegalStateException {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
- try {
- mNetdService.clatdStart(interfaceName);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void stopClatd(String interfaceName) throws IllegalStateException {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
- try {
- mNetdService.clatdStop(interfaceName);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
public void registerNetworkActivityListener(INetworkActivityListener listener) {
mNetworkActivityListeners.register(listener);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 1c04a94..dd2b33a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -70,6 +70,12 @@
static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
static final String KEY_USE_COMPACTION = "use_compaction";
+ static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
+ static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
+ static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+ static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+ static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+ static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -101,6 +107,12 @@
private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
private static final boolean DEFAULT_USE_COMPACTION = false;
+ public static final int DEFAULT_COMPACT_ACTION_1 = 1;
+ public static final int DEFAULT_COMPACT_ACTION_2 = 3;
+ public static final long DEFAULT_COMPACT_THROTTLE_1 = 5000;
+ public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000;
+ public static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
+ public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000;
// Maximum number of cached processes we will allow.
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -223,6 +235,20 @@
// Use compaction for background apps.
public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION;
+ // Action for compactAppSome.
+ public int COMPACT_ACTION_1 = DEFAULT_COMPACT_ACTION_1;
+ // Action for compactAppFull;
+ public int COMPACT_ACTION_2 = DEFAULT_COMPACT_ACTION_2;
+
+ // How long we'll skip second compactAppSome after first compactAppSome
+ public long COMPACT_THROTTLE_1 = DEFAULT_COMPACT_THROTTLE_1;
+ // How long we'll skip compactAppSome after compactAppFull
+ public long COMPACT_THROTTLE_2 = DEFAULT_COMPACT_THROTTLE_2;
+ // How long we'll skip compactAppFull after compactAppSome
+ public long COMPACT_THROTTLE_3 = DEFAULT_COMPACT_THROTTLE_3;
+ // How long we'll skip second compactAppFull after first compactAppFull
+ public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4;
+
// Indicates whether the activity starts logging is enabled.
// Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -381,6 +407,12 @@
TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
DEFAULT_TOP_TO_FGS_GRACE_DURATION);
USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
+ COMPACT_ACTION_1 = mParser.getInt(KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
+ COMPACT_ACTION_2 = mParser.getInt(KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
+ COMPACT_THROTTLE_1 = mParser.getLong(KEY_COMPACT_THROTTLE_1, DEFAULT_COMPACT_THROTTLE_1);
+ COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2);
+ COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3);
+ COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4);
updateMaxCachedProcesses();
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd6fa49..43deb11 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -32,6 +32,7 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
import static android.content.pm.PackageManager.GET_PROVIDERS;
+import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -318,7 +319,6 @@
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.AlarmManagerInternal;
-import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.DeviceIdleController;
import com.android.server.DisplayThread;
@@ -337,6 +337,7 @@
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.appop.AppOpsService;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.pm.Installer;
@@ -658,9 +659,47 @@
/**
* When an app has restrictions on the other apps that can have associations with it,
- * it appears here with a set of the allowed apps.
+ * it appears here with a set of the allowed apps and also track debuggability of the app.
*/
- ArrayMap<String, ArraySet<String>> mAllowedAssociations;
+ ArrayMap<String, PackageAssociationInfo> mAllowedAssociations;
+
+ /**
+ * Tracks association information for a particular package along with debuggability.
+ * <p> Associations for a package A are allowed to package B if B is part of the
+ * allowed associations for A or if A is debuggable.
+ */
+ private final class PackageAssociationInfo {
+ private final String mSourcePackage;
+ private final ArraySet<String> mAllowedPackageAssociations;
+ private boolean mIsDebuggable;
+
+ PackageAssociationInfo(String sourcePackage, ArraySet<String> allowedPackages,
+ boolean isDebuggable) {
+ mSourcePackage = sourcePackage;
+ mAllowedPackageAssociations = allowedPackages;
+ mIsDebuggable = isDebuggable;
+ }
+
+ /**
+ * Returns true if {@code mSourcePackage} is allowed association with
+ * {@code targetPackage}.
+ */
+ boolean isPackageAssociationAllowed(String targetPackage) {
+ return mIsDebuggable || mAllowedPackageAssociations.contains(targetPackage);
+ }
+
+ boolean isDebuggable() {
+ return mIsDebuggable;
+ }
+
+ void setDebuggable(boolean isDebuggable) {
+ mIsDebuggable = isDebuggable;
+ }
+
+ ArraySet<String> getAllowedPackageAssociations() {
+ return mAllowedPackageAssociations;
+ }
+ }
/**
* All of the processes we currently have running organized by pid.
@@ -2392,12 +2431,10 @@
* If it does not, give it an empty set.
*/
void requireAllowedAssociationsLocked(String packageName) {
- if (mAllowedAssociations == null) {
- mAllowedAssociations = new ArrayMap<>(
- SystemConfig.getInstance().getAllowedAssociations());
- }
+ ensureAllowedAssociations();
if (mAllowedAssociations.get(packageName) == null) {
- mAllowedAssociations.put(packageName, new ArraySet<>());
+ mAllowedAssociations.put(packageName, new PackageAssociationInfo(packageName,
+ new ArraySet<>(), /* isDebuggable = */ false));
}
}
@@ -2408,10 +2445,7 @@
* association is implicitly allowed.
*/
boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) {
- if (mAllowedAssociations == null) {
- mAllowedAssociations = new ArrayMap<>(
- SystemConfig.getInstance().getAllowedAssociations());
- }
+ ensureAllowedAssociations();
// Interactions with the system uid are always allowed, since that is the core system
// that everyone needs to be able to interact with. Also allow reflexive associations
// within the same uid.
@@ -2419,24 +2453,57 @@
|| UserHandle.getAppId(uid2) == SYSTEM_UID) {
return true;
}
- // We won't allow this association if either pkg1 or pkg2 has a limit on the
- // associations that are allowed with it, and the other package is not explicitly
- // specified as one of those associations.
- ArraySet<String> pkgs = mAllowedAssociations.get(pkg1);
- if (pkgs != null) {
- if (!pkgs.contains(pkg2)) {
- return false;
- }
+
+ // Check for association on both source and target packages.
+ PackageAssociationInfo pai = mAllowedAssociations.get(pkg1);
+ if (pai != null && !pai.isPackageAssociationAllowed(pkg2)) {
+ return false;
}
- pkgs = mAllowedAssociations.get(pkg2);
- if (pkgs != null) {
- return pkgs.contains(pkg1);
+ pai = mAllowedAssociations.get(pkg2);
+ if (pai != null && !pai.isPackageAssociationAllowed(pkg1)) {
+ return false;
}
// If no explicit associations are provided in the manifest, then assume the app is
// allowed associations with any package.
return true;
}
+ /** Sets up allowed associations for system prebuilt packages from system config (if needed). */
+ private void ensureAllowedAssociations() {
+ if (mAllowedAssociations == null) {
+ ArrayMap<String, ArraySet<String>> allowedAssociations =
+ SystemConfig.getInstance().getAllowedAssociations();
+ mAllowedAssociations = new ArrayMap<>(allowedAssociations.size());
+ PackageManagerInternal pm = getPackageManagerInternalLocked();
+ for (int i = 0; i < allowedAssociations.size(); i++) {
+ final String pkg = allowedAssociations.keyAt(i);
+ final ArraySet<String> asc = allowedAssociations.valueAt(i);
+
+ // Query latest debuggable flag from package-manager.
+ boolean isDebuggable = false;
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager()
+ .getApplicationInfo(pkg, MATCH_ALL, 0);
+ if (ai != null) {
+ isDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ }
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ mAllowedAssociations.put(pkg, new PackageAssociationInfo(pkg, asc, isDebuggable));
+ }
+ }
+ }
+
+ /** Updates allowed associations for app info (specifically, based on debuggability). */
+ private void updateAssociationForApp(ApplicationInfo appInfo) {
+ ensureAllowedAssociations();
+ PackageAssociationInfo pai = mAllowedAssociations.get(appInfo.packageName);
+ if (pai != null) {
+ pai.setDebuggable((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+ }
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -10628,7 +10695,6 @@
ProtoUtils.toDuration(proto, ActivityManagerServiceDumpProcessesProto.LAST_IDLE_TIME, mLastIdleTime, now);
proto.write(ActivityManagerServiceDumpProcessesProto.LOW_RAM_SINCE_LAST_IDLE_MS, getLowRamTimeSinceIdle(now));
}
-
}
void writeProcessesToGcToProto(ProtoOutputStream proto, long fieldId, String dumpPackage) {
@@ -10910,14 +10976,14 @@
void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
boolean needSep = false;
- boolean printedAnything = false;
pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
boolean printed = false;
if (mAllowedAssociations != null) {
for (int i = 0; i < mAllowedAssociations.size(); i++) {
final String pkg = mAllowedAssociations.keyAt(i);
- final ArraySet<String> asc = mAllowedAssociations.valueAt(i);
+ final ArraySet<String> asc =
+ mAllowedAssociations.valueAt(i).getAllowedPackageAssociations();
boolean printedHeader = false;
for (int j = 0; j < asc.size(); j++) {
if (dumpPackage == null || pkg.equals(dumpPackage)
@@ -10926,7 +10992,6 @@
pw.println(" Allowed associations (by restricted package):");
printed = true;
needSep = true;
- printedAnything = true;
}
if (!printedHeader) {
pw.print(" * ");
@@ -10938,6 +11003,9 @@
pw.println(asc.valueAt(j));
}
}
+ if (mAllowedAssociations.valueAt(i).isDebuggable()) {
+ pw.println(" (debuggable)");
+ }
}
}
if (!printed) {
@@ -14519,6 +14587,7 @@
+ " ssp=" + ssp + " data=" + data);
return ActivityManager.BROADCAST_SUCCESS;
}
+ updateAssociationForApp(aInfo);
mAtmInternal.onPackageReplaced(aInfo);
mServices.updateServiceApplicationInfoLocked(aInfo);
sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index aee16c3..fe27c49 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -44,7 +44,11 @@
*/
final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>();
- /*
+ static final int COMPACT_PROCESS_SOME = 1;
+ static final int COMPACT_PROCESS_FULL = 2;
+ static final int COMPACT_PROCESS_MSG = 1;
+
+ /**
* This thread must be moved to the system background cpuset.
* If that doesn't happen, it's probably going to draw a lot of power.
* However, this has to happen after the first updateOomAdjLocked, because
@@ -53,13 +57,22 @@
*/
final ServiceThread mCompactionThread;
- static final int COMPACT_PROCESS_SOME = 1;
- static final int COMPACT_PROCESS_FULL = 2;
- static final int COMPACT_PROCESS_MSG = 1;
- final Handler mCompactionHandler;
+ final private Handler mCompactionHandler;
- final ActivityManagerService mAm;
- final ActivityManagerConstants mConstants;
+ final private ActivityManagerService mAm;
+ final private ActivityManagerConstants mConstants;
+
+ final private String COMPACT_ACTION_FILE = "file";
+ final private String COMPACT_ACTION_ANON = "anon";
+ final private String COMPACT_ACTION_FULL = "full";
+
+ final private String compactActionSome;
+ final private String compactActionFull;
+
+ final private long throttleSomeSome;
+ final private long throttleSomeFull;
+ final private long throttleFullSome;
+ final private long throttleFullFull;
public AppCompactor(ActivityManagerService am) {
mAm = am;
@@ -69,6 +82,41 @@
THREAD_PRIORITY_FOREGROUND, true);
mCompactionThread.start();
mCompactionHandler = new MemCompactionHandler(this);
+
+ switch(mConstants.COMPACT_ACTION_1) {
+ case 1:
+ compactActionSome = COMPACT_ACTION_FILE;
+ break;
+ case 2:
+ compactActionSome = COMPACT_ACTION_ANON;
+ break;
+ case 3:
+ compactActionSome = COMPACT_ACTION_FULL;
+ break;
+ default:
+ compactActionSome = COMPACT_ACTION_FILE;
+ break;
+ }
+
+ switch(mConstants.COMPACT_ACTION_2) {
+ case 1:
+ compactActionFull = COMPACT_ACTION_FILE;
+ break;
+ case 2:
+ compactActionFull = COMPACT_ACTION_ANON;
+ break;
+ case 3:
+ compactActionFull = COMPACT_ACTION_FULL;
+ break;
+ default:
+ compactActionFull = COMPACT_ACTION_FULL;
+ break;
+ }
+
+ throttleSomeSome = mConstants.COMPACT_THROTTLE_1;
+ throttleSomeFull = mConstants.COMPACT_THROTTLE_2;
+ throttleFullSome = mConstants.COMPACT_THROTTLE_3;
+ throttleFullFull = mConstants.COMPACT_THROTTLE_4;
}
// Must be called while holding AMS lock.
@@ -128,18 +176,16 @@
}
// basic throttling
+ // use the ActivityManagerConstants knobs to determine whether current/prevous
+ // compaction combo should be throtted or not
if (pendingAction == COMPACT_PROCESS_SOME) {
- // if we're compacting some, then compact if >10s after last full
- // or >5s after last some
- if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 5000)) ||
- (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000))) {
+ if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleSomeSome)) ||
+ (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleSomeFull))) {
return;
}
} else {
- // if we're compacting full, then compact if >10s after last full
- // or >.5s after last some
- if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 500)) ||
- (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000))) {
+ if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleFullSome)) ||
+ (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleFullFull))) {
return;
}
}
@@ -151,9 +197,9 @@
long[] rssBefore = Process.getRss(pid);
FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
if (pendingAction == COMPACT_PROCESS_SOME) {
- action = "file";
+ action = compactActionSome;
} else {
- action = "all";
+ action = compactActionFull;
}
fos.write(action.getBytes());
fos.close();
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 6596d27..9d9b1cf 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -16,8 +16,9 @@
package com.android.server.connectivity;
-import android.net.InterfaceConfiguration;
import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
@@ -59,6 +60,7 @@
NetworkInfo.State.SUSPENDED,
};
+ private final INetd mNetd;
private final INetworkManagementService mNMService;
// The network we're running on, and its type.
@@ -76,7 +78,8 @@
private String mIface;
private State mState = State.IDLE;
- public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) {
+ public Nat464Xlat(NetworkAgentInfo nai, INetd netd, INetworkManagementService nmService) {
+ mNetd = netd;
mNMService = nmService;
mNetwork = nai;
}
@@ -140,7 +143,7 @@
return;
}
try {
- mNMService.startClatd(baseIface);
+ mNetd.clatdStart(baseIface);
} catch(RemoteException|IllegalStateException e) {
Slog.e(TAG, "Error starting clatd on " + baseIface, e);
}
@@ -162,7 +165,7 @@
*/
private void enterStoppingState() {
try {
- mNMService.stopClatd(mBaseIface);
+ mNetd.clatdStop(mBaseIface);
} catch(RemoteException|IllegalStateException e) {
Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
}
@@ -204,7 +207,7 @@
Slog.e(TAG, "startClat: Can't start clat on null interface");
return;
}
- // TODO: should we only do this if mNMService.startClatd() succeeds?
+ // TODO: should we only do this if mNetd.clatdStart() succeeds?
Slog.i(TAG, "Starting clatd on " + baseIface);
enterStartingState(baseIface);
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 54c89aa..9ea73fb 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import android.content.Context;
+import android.net.INetd;
import android.net.INetworkMonitor;
import android.net.LinkProperties;
import android.net.Network;
@@ -239,12 +240,15 @@
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean VDBG = false;
private final ConnectivityService mConnService;
+ private final INetd mNetd;
+ private final INetworkManagementService mNMS;
private final Context mContext;
private final Handler mHandler;
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
- NetworkMisc misc, ConnectivityService connService) {
+ NetworkMisc misc, ConnectivityService connService, INetd netd,
+ INetworkManagementService nms) {
this.messenger = messenger;
asyncChannel = ac;
network = net;
@@ -253,6 +257,8 @@
networkCapabilities = nc;
currentScore = score;
mConnService = connService;
+ mNetd = netd;
+ mNMS = nms;
mContext = context;
mHandler = handler;
networkMisc = misc;
@@ -587,18 +593,18 @@
public void updateClat(INetworkManagementService netd) {
if (Nat464Xlat.requiresClat(this)) {
- maybeStartClat(netd);
+ maybeStartClat();
} else {
maybeStopClat();
}
}
/** Ensure clat has started for this network. */
- public void maybeStartClat(INetworkManagementService netd) {
+ public void maybeStartClat() {
if (clatd != null && clatd.isStarted()) {
return;
}
- clatd = new Nat464Xlat(netd, this);
+ clatd = new Nat464Xlat(this, mNetd, mNMS);
clatd.start();
}
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index bfcc629..aaf9cbc 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -141,10 +141,7 @@
final long runtime = nowUptime - startUptime;
- if (startUptime == 0) {
- wtf("Job " + jobId + " start uptime not found: "
- + " params=" + jobParametersToString(params));
- } else if (runtime > 60 * 1000) {
+ if (runtime > 60 * 1000) {
// WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
// (1 minute threshold.)
// Also don't wtf when it's not ready to sync.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2340b77..cb3f91b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -792,6 +792,16 @@
}
}
+ private void setVirtualDisplayStateInternal(IBinder appToken, boolean isOn) {
+ synchronized (mSyncRoot) {
+ if (mVirtualDisplayAdapter == null) {
+ return;
+ }
+
+ mVirtualDisplayAdapter.setVirtualDisplayStateLocked(appToken, isOn);
+ }
+ }
+
private void registerDefaultDisplayAdapters() {
// Register default display adapters.
synchronized (mSyncRoot) {
@@ -1930,6 +1940,16 @@
}
@Override // Binder call
+ public void setVirtualDisplayState(IVirtualDisplayCallback callback, boolean isOn) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setVirtualDisplayStateInternal(callback.asBinder(), isOn);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 5aa585f..1ca8dd3 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -147,6 +147,13 @@
return device;
}
+ void setVirtualDisplayStateLocked(IBinder appToken, boolean isOn) {
+ VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
+ if (device != null) {
+ device.setDisplayState(isOn);
+ }
+ }
+
/**
* Returns the next unique index for the uniqueIdPrefix
*/
@@ -206,6 +213,7 @@
private int mPendingChanges;
private int mUniqueIndex;
private Display.Mode mMode;
+ private boolean mIsDisplayOn;
public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
int ownerUid, String ownerPackageName,
@@ -226,6 +234,7 @@
mDisplayState = Display.STATE_UNKNOWN;
mPendingChanges |= PENDING_SURFACE_CHANGE;
mUniqueIndex = uniqueIndex;
+ mIsDisplayOn = surface != null;
}
@Override
@@ -304,6 +313,14 @@
}
}
+ void setDisplayState(boolean isOn) {
+ if (mIsDisplayOn != isOn) {
+ mIsDisplayOn = isOn;
+ mInfo = null;
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
+ }
+ }
+
public void stopLocked() {
setSurfaceLocked(null);
mStopped = true;
@@ -375,7 +392,9 @@
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
DisplayDeviceInfo.TOUCH_NONE : DisplayDeviceInfo.TOUCH_VIRTUAL;
- mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
+
+ mInfo.state = mIsDisplayOn ? Display.STATE_ON : Display.STATE_OFF;
+
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
}
diff --git a/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java
index 18d328d..137833c 100644
--- a/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java
@@ -51,6 +51,13 @@
}
switch (cmd.getOpcode()) {
case Constants.MESSAGE_FEATURE_ABORT:
+ if ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_INITIATE_ARC) {
+ audioSystem().setArcStatus(false);
+ finish();
+ return true;
+ } else {
+ return false;
+ }
case Constants.MESSAGE_REPORT_ARC_TERMINATED:
audioSystem().setArcStatus(false);
finish();
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index ff029c1..297a418 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -17,6 +17,7 @@
package com.android.server.hdmi;
import android.annotation.IntDef;
+import android.annotation.StringDef;
import android.hardware.hdmi.HdmiDeviceInfo;
import java.lang.annotation.Retention;
@@ -222,6 +223,15 @@
static final int AUDIO_CODEC_WMAPRO = 0xE; // Support WMA-Pro
static final int AUDIO_CODEC_MAX = 0xF;
+ @StringDef({
+ AUDIO_DEVICE_ARC_IN,
+ AUDIO_DEVICE_SPDIF,
+ })
+ public @interface AudioDevice {}
+
+ static final String AUDIO_DEVICE_ARC_IN = "ARC_IN";
+ static final String AUDIO_DEVICE_SPDIF = "SPDIF";
+
// Bit mask used to get the routing path of the top level device.
// When &'d with the path 1.2.2.0 (0x1220), for instance, gives 1.0.0.0.
static final int ROUTING_PATH_TOP_MASK = 0xF000;
diff --git a/services/core/java/com/android/server/hdmi/DetectTvSystemAudioModeSupportAction.java b/services/core/java/com/android/server/hdmi/DetectTvSystemAudioModeSupportAction.java
index 0495ff8..7187319 100644
--- a/services/core/java/com/android/server/hdmi/DetectTvSystemAudioModeSupportAction.java
+++ b/services/core/java/com/android/server/hdmi/DetectTvSystemAudioModeSupportAction.java
@@ -50,8 +50,10 @@
if (mState != STATE_WAITING_FOR_FEATURE_ABORT) {
return false;
}
- finishAction(false);
- return true;
+ if ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE) {
+ finishAction(false);
+ return true;
+ }
}
return false;
}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index ba21b78..df0dc77 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.util.Slog;
@@ -51,6 +52,10 @@
private static final int STATE_WAITING_FOR_OSD_NAME = 3;
// State in which the action is waiting for gathering vendor id of non-local devices.
private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
+ // State in which the action is waiting for devices to be ready.
+ private static final int STATE_WAITING_FOR_DEVICES = 5;
+ // State in which the action is waiting for gathering power status of non-local devices.
+ private static final int STATE_WAITING_FOR_POWER = 6;
/**
* Interface used to report result of device discovery.
@@ -72,6 +77,7 @@
private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
private int mPortId = Constants.INVALID_PORT_ID;
private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
+ private int mPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
private String mDisplayName = "";
private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE;
@@ -81,7 +87,7 @@
private HdmiDeviceInfo toHdmiDeviceInfo() {
return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
- mVendorId, mDisplayName);
+ mVendorId, mDisplayName, mPowerStatus);
}
}
@@ -89,7 +95,20 @@
private final DeviceDiscoveryCallback mCallback;
private int mProcessedDeviceCount = 0;
private int mTimeoutRetry = 0;
- private boolean mIsTvDevice = source().mService.isTvDevice();
+ private boolean mIsTvDevice = localDevice().mService.isTvDevice();
+ private final int mDelayPeriod;
+
+ /**
+ * Constructor.
+ *
+ * @param source an instance of {@link HdmiCecLocalDevice}.
+ * @param delay delay action for this period between query Physical Address and polling
+ */
+ DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback, int delay) {
+ super(source);
+ mCallback = Preconditions.checkNotNull(callback);
+ mDelayPeriod = delay;
+ }
/**
* Constructor.
@@ -97,8 +116,7 @@
* @param source an instance of {@link HdmiCecLocalDevice}.
*/
DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
- super(source);
- mCallback = Preconditions.checkNotNull(callback);
+ this(source, callback, 0);
}
@Override
@@ -117,7 +135,11 @@
Slog.v(TAG, "Device detected: " + ackedAddress);
allocateDevices(ackedAddress);
- startPhysicalAddressStage();
+ if (mDelayPeriod > 0) {
+ startToDelayAction();
+ } else {
+ startPhysicalAddressStage();
+ }
}
}, Constants.POLL_ITERATION_REVERSE_ORDER
| Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
@@ -131,6 +153,13 @@
}
}
+ private void startToDelayAction() {
+ Slog.v(TAG, "Waiting for connected devices to be ready");
+ mState = STATE_WAITING_FOR_DEVICES;
+
+ checkAndProceedStage();
+ }
+
private void startPhysicalAddressStage() {
Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
mProcessedDeviceCount = 0;
@@ -159,6 +188,11 @@
addTimer(mState, HdmiConfig.TIMEOUT_MS);
}
+ private void delayActionWithTimePeriod(int timeDelay) {
+ mActionTimer.clearTimerMessage();
+ addTimer(mState, timeDelay);
+ }
+
private void startOsdNameStage() {
Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
mProcessedDeviceCount = 0;
@@ -207,6 +241,29 @@
addTimer(mState, HdmiConfig.TIMEOUT_MS);
}
+ private void startPowerStatusStage() {
+ Slog.v(TAG, "Start [Power Status Stage]:" + mDevices.size());
+ mProcessedDeviceCount = 0;
+ mState = STATE_WAITING_FOR_POWER;
+
+ checkAndProceedStage();
+ }
+
+ private void queryPowerStatus(int address) {
+ if (!verifyValidLogicalAddress(address)) {
+ checkAndProceedStage();
+ return;
+ }
+
+ mActionTimer.clearTimerMessage();
+
+ if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_POWER_STATUS)) {
+ return;
+ }
+ sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address));
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ }
+
private boolean mayProcessMessageIfCached(int address, int opcode) {
HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
if (message != null) {
@@ -245,6 +302,16 @@
return true;
}
return false;
+ case STATE_WAITING_FOR_POWER:
+ if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
+ handleReportPowerStatus(cmd);
+ return true;
+ } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)
+ && ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_REPORT_POWER_STATUS)) {
+ handleReportPowerStatus(cmd);
+ return true;
+ }
+ return false;
case STATE_WAITING_FOR_DEVICE_POLLING:
// Fall through.
default:
@@ -266,6 +333,7 @@
current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
current.mPortId = getPortId(current.mPhysicalAddress);
current.mDeviceType = params[2] & 0xFF;
+ current.mDisplayName = HdmiUtils.getDefaultDeviceName(current.mDeviceType);
// TODO(amyjojo): check if non-TV device needs to update cec switch info.
// This is to manager CEC device separately in case they don't have address.
@@ -329,6 +397,26 @@
checkAndProceedStage();
}
+ private void handleReportPowerStatus(HdmiCecMessage cmd) {
+ Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+ DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+ if (current.mLogicalAddress != cmd.getSource()) {
+ Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:"
+ + cmd.getSource());
+ return;
+ }
+
+ if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
+ byte[] params = cmd.getParams();
+ int powerStatus = params[0] & 0xFF;
+ current.mPowerStatus = powerStatus;
+ }
+
+ increaseProcessedDeviceCount();
+ checkAndProceedStage();
+ }
+
private void increaseProcessedDeviceCount() {
mProcessedDeviceCount++;
mTimeoutRetry = 0;
@@ -372,6 +460,9 @@
startVendorIdStage();
return;
case STATE_WAITING_FOR_VENDOR_ID:
+ startPowerStatusStage();
+ return;
+ case STATE_WAITING_FOR_POWER:
wrapUpAndFinish();
return;
default:
@@ -385,6 +476,9 @@
private void sendQueryCommand() {
int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
switch (mState) {
+ case STATE_WAITING_FOR_DEVICES:
+ delayActionWithTimePeriod(mDelayPeriod);
+ return;
case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
queryPhysicalAddress(address);
return;
@@ -394,6 +488,9 @@
case STATE_WAITING_FOR_VENDOR_ID:
queryVendorId(address);
return;
+ case STATE_WAITING_FOR_POWER:
+ queryPowerStatus(address);
+ return;
default:
return;
}
@@ -405,13 +502,24 @@
return;
}
+ if (mState == STATE_WAITING_FOR_DEVICES) {
+ startPhysicalAddressStage();
+ return;
+ }
if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
sendQueryCommand();
return;
}
mTimeoutRetry = 0;
Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
- removeDevice(mProcessedDeviceCount);
+ if (mState != STATE_WAITING_FOR_POWER && mState != STATE_WAITING_FOR_OSD_NAME) {
+ // We don't need to remove the device info if the power status is unknown.
+ // Some device does not have preferred OSD name and does not respond to Give OSD name.
+ // Like LG TV. We can give it default device name and not remove it.
+ removeDevice(mProcessedDeviceCount);
+ } else {
+ increaseProcessedDeviceCount();
+ }
checkAndProceedStage();
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index e777ce8..86be585 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -77,7 +77,7 @@
private static final int NUM_LOGICAL_ADDRESS = 16;
- private static final int MAX_CEC_MESSAGE_HISTORY = 20;
+ private static final int MAX_CEC_MESSAGE_HISTORY = 200;
// Predicate for whether the given logical address is remote device's one or not.
private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
@@ -682,7 +682,7 @@
void dump(final IndentingPrintWriter pw) {
for (int i = 0; i < mLocalDevices.size(); ++i) {
- pw.println("HdmiCecLocalDevice #" + i + ":");
+ pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":");
pw.increaseIndent();
mLocalDevices.valueAt(i).dump(pw);
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 32dc0261..eb4e662 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -66,6 +66,7 @@
protected final int mDeviceType;
protected int mAddress;
protected int mPreferredAddress;
+ @GuardedBy("mLock")
protected HdmiDeviceInfo mDeviceInfo;
protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
protected int mLastKeyRepeatCount = 0;
@@ -717,16 +718,18 @@
return mDeviceType;
}
- @ServiceThreadOnly
+ @GuardedBy("mLock")
HdmiDeviceInfo getDeviceInfo() {
- assertRunOnServiceThread();
- return mDeviceInfo;
+ synchronized (mLock) {
+ return mDeviceInfo;
+ }
}
- @ServiceThreadOnly
+ @GuardedBy("mLock")
void setDeviceInfo(HdmiDeviceInfo info) {
- assertRunOnServiceThread();
- mDeviceInfo = info;
+ synchronized (mLock) {
+ mDeviceInfo = info;
+ }
}
// Returns true if the logical address is same as the argument.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 0e4e334..71075f3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -26,22 +26,30 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.tv.TvContract;
import android.os.SystemProperties;
+import android.provider.Settings.Global;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.Constants.AudioCodec;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.stream.Collectors;
+
/**
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
@@ -77,23 +85,24 @@
// processing.
private final HashMap<Integer, String> mTvInputs = new HashMap<>();
+ // Copy of mDeviceInfos to guarantee thread-safety.
+ @GuardedBy("mLock")
+ private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
+
// Map-like container of all cec devices.
// device id is used as key of container.
private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- mSystemAudioControlFeatureEnabled = true;
- // TODO(amyjojo) make System Audio Control controllable by users
- /*mSystemAudioControlFeatureEnabled =
- mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);*/
- // TODO(amyjojo): make the map ro property.
- mTvInputs.put(Constants.CEC_SWITCH_HDMI1,
- "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
- mTvInputs.put(Constants.CEC_SWITCH_HDMI2,
- "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
- mTvInputs.put(Constants.CEC_SWITCH_HDMI3,
- "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
+ mRoutingControlFeatureEnabled =
+ mService.readBooleanSetting(Global.HDMI_CEC_SWITCH_ENABLED, false);
+ mSystemAudioControlFeatureEnabled =
+ mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
+ // TODO(amyjojo): Maintain a portId to TvinputId map.
+ mTvInputs.put(2, "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
+ mTvInputs.put(4, "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
+ mTvInputs.put(1, "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
}
/**
@@ -167,6 +176,7 @@
removeDeviceInfo(deviceInfo.getId());
}
mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
+ updateSafeDeviceInfoList();
return oldDeviceInfo;
}
@@ -184,6 +194,7 @@
if (deviceInfo != null) {
mDeviceInfos.remove(id);
}
+ updateSafeDeviceInfoList();
return deviceInfo;
}
@@ -200,6 +211,24 @@
return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
}
+ @ServiceThreadOnly
+ private void updateSafeDeviceInfoList() {
+ assertRunOnServiceThread();
+ List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
+ synchronized (mLock) {
+ mSafeAllDeviceInfos = copiedDevices;
+ }
+ }
+
+ @GuardedBy("mLock")
+ List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
+ ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
+ for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
+ infoList.add(info);
+ }
+ return infoList;
+ }
+
private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
mService.invokeDeviceEventListeners(info, status);
}
@@ -240,6 +269,10 @@
@ServiceThreadOnly
protected void onAddressAllocated(int logicalAddress, int reason) {
assertRunOnServiceThread();
+ if (reason == mService.INITIATED_BY_ENABLE_CEC) {
+ mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
+ getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
+ }
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
mAddress, mService.getPhysicalAddress(), mDeviceType));
@@ -259,7 +292,10 @@
@Override
protected int findKeyReceiverAddress() {
- return Constants.ADDR_TV;
+ if (getActiveSource().isValid()) {
+ return getActiveSource().logicalAddress;
+ }
+ return Constants.ADDR_INVALID;
}
@VisibleForTesting
@@ -267,7 +303,7 @@
int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
|| ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
- && lastSystemAudioControlStatus)) {
+ && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
}
}
@@ -400,8 +436,11 @@
@ServiceThreadOnly
protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
-
- reportAudioStatus(message);
+ if (isSystemAudioControlFeatureEnabled()) {
+ reportAudioStatus(message.getSource());
+ } else {
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ }
return true;
}
@@ -478,14 +517,91 @@
private byte[] getSupportedShortAudioDescriptors(
AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) {
- // TODO(b/80297701) implement
- return new byte[] {};
+ ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
+ for (@AudioCodec int audioFormatCode : audioFormatCodes) {
+ byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode);
+ if (sad != null) {
+ if (sad.length == 3) {
+
+ sads.add(sad);
+ } else {
+ HdmiLogger.warning(
+ "Dropping Short Audio Descriptor with length %d for requested codec %x",
+ sad.length, audioFormatCode);
+ }
+ }
+ }
+ // Short Audio Descriptors are always 3 bytes long.
+ byte[] bytes = new byte[sads.size() * 3];
+ int index = 0;
+ for (byte[] sad : sads) {
+ System.arraycopy(sad, 0, bytes, index, 3);
+ index += 3;
+ }
+ return bytes;
+ }
+
+ /**
+ * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
+ * audioFormatCode is not supported.
+ */
+ @Nullable
+ private byte[] getSupportedShortAudioDescriptor(
+ AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) {
+ switch (audioFormatCode) {
+ case Constants.AUDIO_CODEC_NONE: {
+ return null;
+ }
+ case Constants.AUDIO_CODEC_LPCM: {
+ return getLpcmShortAudioDescriptor(deviceInfo);
+ }
+ // TODO(b/80297701): implement the rest of the codecs
+ case Constants.AUDIO_CODEC_DD:
+ case Constants.AUDIO_CODEC_MPEG1:
+ case Constants.AUDIO_CODEC_MP3:
+ case Constants.AUDIO_CODEC_MPEG2:
+ case Constants.AUDIO_CODEC_AAC:
+ case Constants.AUDIO_CODEC_DTS:
+ case Constants.AUDIO_CODEC_ATRAC:
+ case Constants.AUDIO_CODEC_ONEBITAUDIO:
+ case Constants.AUDIO_CODEC_DDP:
+ case Constants.AUDIO_CODEC_DTSHD:
+ case Constants.AUDIO_CODEC_TRUEHD:
+ case Constants.AUDIO_CODEC_DST:
+ case Constants.AUDIO_CODEC_WMAPRO:
+ default: {
+ return null;
+ }
+ }
+ }
+
+ @Nullable
+ private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) {
+ // TODO(b/80297701): implement
+ return null;
}
@Nullable
private AudioDeviceInfo getSystemAudioDeviceInfo() {
- // TODO(b/80297701) implement
- // Get the audio device used for system audio mode.
+ AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class);
+ if (audioManager == null) {
+ HdmiLogger.error(
+ "Error getting system audio device because AudioManager not available.");
+ return null;
+ }
+ AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ HdmiLogger.debug("Found %d audio input devices", devices.length);
+ for (AudioDeviceInfo device : devices) {
+ HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort());
+ HdmiLogger.debug("Supported encodings are %s",
+ Arrays.stream(device.getEncodings()).mapToObj(
+ AudioFormat::toLogFriendlyEncoding
+ ).collect(Collectors.joining(", ")));
+ // TODO(b/80297701) use the actual device type that system audio mode is connected to.
+ if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) {
+ return device;
+ }
+ }
return null;
}
@@ -583,17 +699,20 @@
.setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
}
- private void reportAudioStatus(HdmiCecMessage message) {
+ void reportAudioStatus(int source) {
assertRunOnServiceThread();
int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC);
int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+ HdmiLogger.debug("Reporting volume %i (%i-%i) as CEC volume %i", volume,
+ minVolume, maxVolume, scaledVolume);
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportAudioStatus(
- mAddress, message.getSource(), scaledVolume, mute));
+ mAddress, source, scaledVolume, mute));
}
/**
@@ -633,7 +752,7 @@
*/
private void setSystemAudioMode(boolean newSystemAudioMode) {
int targetPhysicalAddress = getActiveSource().physicalAddress;
- int port = getLocalPortFromPhysicalAddress(targetPhysicalAddress);
+ int port = mService.pathToPortId(targetPhysicalAddress);
if (newSystemAudioMode && port >= 0) {
switchToAudioInput();
}
@@ -641,16 +760,18 @@
// PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE is false when device never needs to be muted.
boolean currentMuteStatus =
mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
- if (SystemProperties.getBoolean(
- Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
- && currentMuteStatus == newSystemAudioMode) {
- mService.getAudioManager()
- .adjustStreamVolume(
- AudioManager.STREAM_MUSIC,
- newSystemAudioMode
- ? AudioManager.ADJUST_UNMUTE
- : AudioManager.ADJUST_MUTE,
- 0);
+ if (currentMuteStatus == newSystemAudioMode) {
+ if (SystemProperties.getBoolean(
+ Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
+ || newSystemAudioMode) {
+ mService.getAudioManager()
+ .adjustStreamVolume(
+ AudioManager.STREAM_MUSIC,
+ newSystemAudioMode
+ ? AudioManager.ADJUST_UNMUTE
+ : AudioManager.ADJUST_MUTE,
+ 0);
+ }
}
updateAudioManagerForSystemAudio(newSystemAudioMode);
synchronized (mLock) {
@@ -688,6 +809,13 @@
HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
}
+ void onSystemAduioControlFeatureSupportChanged(boolean enabled) {
+ setSystemAudioControlFeatureEnabled(enabled);
+ if (enabled) {
+ addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
+ }
+ }
+
@ServiceThreadOnly
void setSystemAudioControlFeatureEnabled(boolean enabled) {
assertRunOnServiceThread();
@@ -697,6 +825,14 @@
}
@ServiceThreadOnly
+ void setRoutingControlFeatureEnables(boolean enabled) {
+ assertRunOnServiceThread();
+ synchronized (mLock) {
+ mRoutingControlFeatureEnabled = enabled;
+ }
+ }
+
+ @ServiceThreadOnly
void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
assertRunOnServiceThread();
// TODO: validate port ID
@@ -817,7 +953,7 @@
@Override
protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
- int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ int port = mService.pathToPortId(physicalAddress);
if (isSystemAudioActivated() && port < 0) {
// If system audio mode is on and the new active source is not under the current device,
// Will switch to ARC input.
@@ -831,6 +967,10 @@
}
protected void routeToInputFromPortId(int portId) {
+ if (!isRoutingControlFeatureEnabled()) {
+ HdmiLogger.debug("Routing Control Feature is not enabled.");
+ return;
+ }
if (mArcIntentUsed) {
routeToTvInputFromPortId(portId);
} else {
@@ -885,7 +1025,7 @@
@Override
protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
- int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ int port = mService.pathToPortId(physicalAddress);
// Routing change or information sent from switches under the current device can be ignored.
if (port > 0) {
return;
@@ -918,8 +1058,7 @@
return;
}
- int routingInformationPath =
- getActivePathOnSwitchFromActivePortId(getRoutingPort());
+ int routingInformationPath = mService.portIdToPath(getRoutingPort());
// If current device is already the leaf of the whole HDMI system, will do nothing.
if (routingInformationPath == mService.getPhysicalAddress()) {
HdmiLogger.debug("Current device can't assign valid physical address"
@@ -954,6 +1093,10 @@
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
+ if (hasAction(DeviceDiscoveryAction.class)) {
+ Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
+ removeAction(DeviceDiscoveryAction.class);
+ }
DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
new DeviceDiscoveryCallback() {
@Override
@@ -977,5 +1120,25 @@
invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
}
mDeviceInfos.clear();
+ updateSafeDeviceInfoList();
}
+
+ @Override
+ protected void dump(IndentingPrintWriter pw) {
+ pw.println("HdmiCecLocalDeviceAudioSystem:");
+ pw.increaseIndent();
+ pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
+ pw.println("isRoutingFeatureEnabled " + isRoutingControlFeatureEnabled());
+ pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
+ pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport);
+ pw.println("mArcEstablished: " + mArcEstablished);
+ pw.println("mArcIntentUsed: " + mArcIntentUsed);
+ pw.println("mRoutingPort: " + getRoutingPort());
+ pw.println("mLocalActivePort: " + getLocalActivePort());
+ HdmiUtils.dumpMap(pw, "mTvInputs:", mTvInputs);
+ HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos);
+ pw.decreaseIndent();
+ super.dump(pw);
+ }
+
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 07db971..32288de 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -77,6 +77,10 @@
@ServiceThreadOnly
protected void onAddressAllocated(int logicalAddress, int reason) {
assertRunOnServiceThread();
+ if (reason == mService.INITIATED_BY_ENABLE_CEC) {
+ mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
+ getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
+ }
mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
mAddress, mService.getPhysicalAddress(), mDeviceType));
mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index a95f7f1..ae008b4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -66,6 +66,10 @@
@LocalActivePort
protected int mLocalActivePort = Constants.CEC_SWITCH_HOME;
+ // Whether the Routing Coutrol feature is enabled or not. False by default.
+ @GuardedBy("mLock")
+ protected boolean mRoutingControlFeatureEnabled;
+
protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
super(service, deviceType);
}
@@ -123,7 +127,9 @@
}
setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
- switchInputOnReceivingNewActivePath(physicalAddress);
+ if (isRoutingControlFeatureEnabled()) {
+ switchInputOnReceivingNewActivePath(physicalAddress);
+ }
return true;
}
@@ -153,6 +159,10 @@
@ServiceThreadOnly
protected boolean handleRoutingChange(HdmiCecMessage message) {
assertRunOnServiceThread();
+ if (!isRoutingControlFeatureEnabled()) {
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return true;
+ }
int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
// if the current device is a pure playback device
if (!mIsSwitchDevice
@@ -168,6 +178,10 @@
@ServiceThreadOnly
protected boolean handleRoutingInformation(HdmiCecMessage message) {
assertRunOnServiceThread();
+ if (!isRoutingControlFeatureEnabled()) {
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return true;
+ }
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
// if the current device is a pure playback device
if (!mIsSwitchDevice
@@ -204,7 +218,7 @@
// This method should only be called when the device can be the active source.
protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
mService.setAndBroadcastActiveSource(
- message, physicalAddress, getDeviceInfo().getDeviceType());
+ physicalAddress, getDeviceInfo().getDeviceType(), message.getSource());
}
@ServiceThreadOnly
@@ -279,6 +293,12 @@
}
}
+ boolean isRoutingControlFeatureEnabled() {
+ synchronized (mLock) {
+ return mRoutingControlFeatureEnabled;
+ }
+ }
+
// Check if the device is trying to switch to the same input that is active right now.
// This can help avoid redundant port switching.
protected boolean isSwitchingToTheSameInput(@LocalActivePort int activePort) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index c005615..f8b3962 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -17,6 +17,7 @@
package com.android.server.hdmi;
import android.annotation.Nullable;
+
import libcore.util.EmptyArray;
import java.util.Arrays;
@@ -111,12 +112,11 @@
@Override
public String toString() {
StringBuffer s = new StringBuffer();
- s.append(String.format("<%s> src: %d, dst: %d",
- opcodeToString(mOpcode), mSource, mDestination));
+ s.append(String.format("<%s> %X%X:%02X",
+ opcodeToString(mOpcode), mSource, mDestination, mOpcode));
if (mParams.length > 0) {
- s.append(", params:");
for (byte data : mParams) {
- s.append(String.format(" %02X", data));
+ s.append(String.format(":%02X", data));
}
}
return s.toString();
@@ -133,7 +133,7 @@
case Constants.MESSAGE_TUNER_STEP_DECREMENT:
return "Tuner Step Decrement";
case Constants.MESSAGE_TUNER_DEVICE_STATUS:
- return "Tuner Device Staus";
+ return "Tuner Device Status";
case Constants.MESSAGE_GIVE_TUNER_DEVICE_STATUS:
return "Give Tuner Device Status";
case Constants.MESSAGE_RECORD_ON:
@@ -207,7 +207,7 @@
case Constants.MESSAGE_DEVICE_VENDOR_ID:
return "Device Vendor Id";
case Constants.MESSAGE_VENDOR_COMMAND:
- return "Vendor Commandn";
+ return "Vendor Command";
case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_DOWN:
return "Vendor Remote Button Down";
case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_UP:
@@ -215,7 +215,7 @@
case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
return "Give Device Vendor Id";
case Constants.MESSAGE_MENU_REQUEST:
- return "Menu REquest";
+ return "Menu Request";
case Constants.MESSAGE_MENU_STATUS:
return "Menu Status";
case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
@@ -247,7 +247,7 @@
case Constants.MESSAGE_SET_EXTERNAL_TIMER:
return "Set External Timer";
case Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR:
- return "Repot Short Audio Descriptor";
+ return "Report Short Audio Descriptor";
case Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR:
return "Request Short Audio Descriptor";
case Constants.MESSAGE_INITIATE_ARC:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 2d6e762..486faf3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -20,12 +20,14 @@
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
+import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
import static com.android.server.hdmi.Constants.DISABLED;
import static com.android.server.hdmi.Constants.ENABLED;
import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
+import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -184,9 +186,10 @@
@Override
public void onReceive(Context context, Intent intent) {
assertRunOnServiceThread();
+ boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
switch (intent.getAction()) {
case Intent.ACTION_SCREEN_OFF:
- if (isPowerOnOrTransient()) {
+ if (isPowerOnOrTransient() && !isReboot) {
onStandby(STANDBY_SCREEN_OFF);
}
break;
@@ -202,7 +205,7 @@
}
break;
case Intent.ACTION_SHUTDOWN:
- if (isPowerOnOrTransient()) {
+ if (isPowerOnOrTransient() && !isReboot) {
onStandby(STANDBY_SHUTDOWN);
}
break;
@@ -345,6 +348,10 @@
@Nullable
private Looper mIoLooper;
+ // Thread safe physical address
+ @GuardedBy("mLock")
+ private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
+
// Last input port before switching to the MHL port. Should switch back to this port
// when the mobile device sends the request one touch play with off.
// Gets invalidated if we go to other port/input.
@@ -564,7 +571,8 @@
Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Global.MHL_INPUT_SWITCHING_ENABLED,
- Global.MHL_POWER_CHARGE_ENABLED
+ Global.MHL_POWER_CHARGE_ENABLED,
+ Global.HDMI_CEC_SWITCH_ENABLED
};
for (String s : settings) {
resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
@@ -605,6 +613,24 @@
if (isTvDeviceEnabled()) {
tv().setSystemAudioControlFeatureEnabled(enabled);
}
+ if (isAudioSystemDevice()) {
+ if (audioSystem() == null) {
+ Slog.e(TAG, "Audio System device has not registered yet."
+ + " Can't turn system audio mode on.");
+ break;
+ }
+ audioSystem().onSystemAduioControlFeatureSupportChanged(enabled);
+ }
+ break;
+ case Global.HDMI_CEC_SWITCH_ENABLED:
+ if (isAudioSystemDevice()) {
+ if (audioSystem() == null) {
+ Slog.w(TAG, "Switch device has not registered yet."
+ + " Can't turn routing on.");
+ break;
+ }
+ audioSystem().setRoutingControlFeatureEnables(enabled);
+ }
break;
case Global.MHL_INPUT_SWITCHING_ENABLED:
setMhlInputChangeEnabled(enabled);
@@ -734,6 +760,10 @@
assertRunOnServiceThread();
HdmiPortInfo[] cecPortInfo = null;
+ synchronized (mLock) {
+ mPhysicalAddress = getPhysicalAddress();
+ }
+
// CEC HAL provides majority of the info while MHL does only MHL support flag for
// each port. Return empty array if CEC HAL didn't provide the info.
if (mCecController != null) {
@@ -827,7 +857,10 @@
int pathToPortId(int path) {
int mask = 0xF000;
int finalMask = 0xF000;
- int physicalAddress = getPhysicalAddress();
+ int physicalAddress;
+ synchronized (mLock) {
+ physicalAddress = mPhysicalAddress;
+ }
int maskedAddress = physicalAddress;
while (maskedAddress != 0) {
@@ -1135,7 +1168,7 @@
String displayName = Build.MODEL;
return new HdmiDeviceInfo(logicalAddress,
getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
- getVendorId(), displayName);
+ getVendorId(), displayName, powerStatus);
}
@ServiceThreadOnly
@@ -1354,6 +1387,33 @@
HdmiCecLocalDeviceTv tv = tv();
if (tv == null) {
Slog.w(TAG, "Local tv device not available");
+ if (isPlaybackDevice()) {
+ // if playback device itself is the active source,
+ // return its own device info.
+ if (playback() != null && playback().mIsActiveSource) {
+ return playback().getDeviceInfo();
+ }
+ // Otherwise get the active source and look for it from the device list
+ ActiveSource activeSource = mActiveSource;
+ // If the active source is not set yet, return null
+ if (!activeSource.isValid()) {
+ return null;
+ }
+ if (audioSystem() != null) {
+ HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+ for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) {
+ if (info.getLogicalAddress() == activeSource.logicalAddress) {
+ return info;
+ }
+ }
+ }
+ // If the device info is not in the list yet, return a device info with minimum
+ // information from mActiveSource.
+ return new HdmiDeviceInfo(activeSource.logicalAddress,
+ activeSource.physicalAddress, pathToPortId(activeSource.physicalAddress),
+ HdmiUtils.getTypeFromAddress(activeSource.logicalAddress), 0,
+ HdmiUtils.getDefaultDeviceName(activeSource.logicalAddress));
+ }
return null;
}
ActiveSource activeSource = tv.getActiveSource();
@@ -1532,6 +1592,14 @@
}
@Override
+ public int getPhysicalAddress() {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ return mPhysicalAddress;
+ }
+ }
+
+ @Override
public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
enforceAccessPermission();
runOnServiceThread(new Runnable() {
@@ -1588,14 +1656,62 @@
public List<HdmiDeviceInfo> getDeviceList() {
enforceAccessPermission();
HdmiCecLocalDeviceTv tv = tv();
- synchronized (mLock) {
- return (tv == null)
+ if (tv != null) {
+ synchronized (mLock) {
+ return tv.getSafeCecDevicesLocked();
+ }
+ } else {
+ HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+ synchronized (mLock) {
+ return (audioSystem == null)
? Collections.<HdmiDeviceInfo>emptyList()
- : tv.getSafeCecDevicesLocked();
+ : audioSystem.getSafeCecDevicesLocked();
+ }
}
}
@Override
+ public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
+ enforceAccessPermission();
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ Slog.w(TAG, "Device "
+ + logicalAddress + " power status is " + powerStatus
+ + " before standby command sent out");
+ sendCecCommand(HdmiCecMessageBuilder.buildStandby(
+ getRemoteControlSourceAddress(), logicalAddress));
+ }
+ });
+ }
+
+ @Override
+ public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
+ // TODO(amyjojo): implement the method
+ }
+
+ @Override
+ // TODO(AMYJOJO): add a result callback
+ public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
+ enforceAccessPermission();
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+ getRemoteControlSourceAddress(), physicalAddress);
+ if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) {
+ if (getSwitchDevice() != null) {
+ getSwitchDevice().handleSetStreamPath(setStreamPath);
+ } else {
+ Slog.e(TAG, "Can't get the correct local device to handle routing.");
+ }
+ }
+ sendCecCommand(setStreamPath);
+ }
+ });
+ }
+
+ @Override
public void setSystemAudioVolume(final int oldIndex, final int newIndex,
final int maxIndex) {
enforceAccessPermission();
@@ -1834,14 +1950,32 @@
Slog.w(TAG, "audio system is not in system audio mode");
return;
}
- int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+ audioSystem().reportAudioStatus(Constants.ADDR_TV);
+ }
+ });
+ }
- sendCecCommand(HdmiCecMessageBuilder
- .buildReportAudioStatus(
- device.getDeviceInfo().getLogicalAddress(),
- Constants.ADDR_TV,
- scaledVolume,
- isMute));
+ @Override
+ public void setSystemAudioModeOnForAudioOnlySource() {
+ enforceAccessPermission();
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isAudioSystemDevice()) {
+ Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
+ return;
+ }
+ if (audioSystem() == null) {
+ Slog.e(TAG, "Audio System local device is not registered");
+ return;
+ }
+ if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
+ Slog.e(TAG, "System Audio Mode is not supported.");
+ return;
+ }
+ sendCecCommand(
+ HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
}
});
}
@@ -1851,30 +1985,54 @@
if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
pw.println("mProhibitMode: " + mProhibitMode);
- if (mCecController != null) {
- pw.println("mCecController: ");
- pw.increaseIndent();
- mCecController.dump(pw);
- pw.decreaseIndent();
- }
+ pw.println("mPowerStatus: " + mPowerStatus);
+
+ // System settings
+ pw.println("System_settings:");
+ pw.increaseIndent();
+ pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
+ pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
+ pw.decreaseIndent();
pw.println("mMhlController: ");
pw.increaseIndent();
mMhlController.dump(pw);
pw.decreaseIndent();
- pw.println("mPortInfo: ");
- pw.increaseIndent();
- for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
- pw.println("- " + hdmiPortInfo);
+ HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
+ if (mCecController != null) {
+ pw.println("mCecController: ");
+ pw.increaseIndent();
+ mCecController.dump(pw);
+ pw.decreaseIndent();
}
- pw.decreaseIndent();
- pw.println("mPowerStatus: " + mPowerStatus);
}
}
+ // Get the source address to send out commands to devices connected to the current device
+ // when other services interact with HdmiControlService.
+ private int getRemoteControlSourceAddress() {
+ if (isAudioSystemDevice()) {
+ return audioSystem().getDeviceInfo().getLogicalAddress();
+ } else if (isPlaybackDevice()) {
+ return playback().getDeviceInfo().getLogicalAddress();
+ }
+ return ADDR_UNREGISTERED;
+ }
+
+ // Get the switch device to do CEC routing control
+ @Nullable
+ private HdmiCecLocalDeviceSource getSwitchDevice() {
+ if (isAudioSystemDevice()) {
+ return audioSystem();
+ }
+ if (isPlaybackDevice()) {
+ return playback();
+ }
+ return null;
+ }
+
@ServiceThreadOnly
private void oneTouchPlay(final IHdmiControlCallback callback) {
assertRunOnServiceThread();
@@ -2549,14 +2707,14 @@
// For example, when receiving broadcast messages, all the device types will call this
// method but only one of them will be the Active Source.
protected void setAndBroadcastActiveSource(
- HdmiCecMessage message, int physicalAddress, int deviceType) {
+ int physicalAddress, int deviceType, int source) {
// If the device has both playback and audio system logical addresses,
// playback will claim active source. Otherwise audio system will.
if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
HdmiCecLocalDevicePlayback playback = playback();
playback.setIsActiveSource(true);
playback.wakeUpIfActiveSource();
- playback.maySendActiveSource(message.getSource());
+ playback.maySendActiveSource(source);
setActiveSource(playback.mAddress, physicalAddress);
}
@@ -2567,7 +2725,7 @@
} else {
audioSystem.setIsActiveSource(true);
audioSystem.wakeUpIfActiveSource();
- audioSystem.maySendActiveSource(message.getSource());
+ audioSystem.maySendActiveSource(source);
setActiveSource(audioSystem.mAddress, physicalAddress);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 2a8117f..11e557c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -20,9 +20,13 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.util.IndentingPrintWriter;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+
/**
* Various utilities to handle HDMI CEC messages.
@@ -317,4 +321,103 @@
info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(),
info.getVendorId(), info.getDisplayName(), newPowerStatus);
}
+
+ /**
+ * Dump a {@link SparseArray} to the print writer.
+ *
+ * <p>The dump is formatted:
+ * <pre>
+ * name:
+ * key = value
+ * key = value
+ * ...
+ * </pre>
+ */
+ static <T> void dumpSparseArray(IndentingPrintWriter pw, String name,
+ SparseArray<T> sparseArray) {
+ printWithTrailingColon(pw, name);
+ pw.increaseIndent();
+ int size = sparseArray.size();
+ for (int i = 0; i < size; i++) {
+ int key = sparseArray.keyAt(i);
+ T value = sparseArray.get(key);
+ pw.printPair(Integer.toString(key), value);
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
+ private static void printWithTrailingColon(IndentingPrintWriter pw, String name) {
+ pw.println(name.endsWith(":") ? name : name.concat(":"));
+ }
+
+ /**
+ * Dump a {@link Map} to the print writer.
+ *
+ * <p>The dump is formatted:
+ * <pre>
+ * name:
+ * key = value
+ * key = value
+ * ...
+ * </pre>
+ */
+ static <K, V> void dumpMap(IndentingPrintWriter pw, String name, Map<K, V> map) {
+ printWithTrailingColon(pw, name);
+ pw.increaseIndent();
+ for (Map.Entry<K, V> entry: map.entrySet()) {
+ pw.printPair(entry.getKey().toString(), entry.getValue());
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
+ /**
+ * Dump a {@link Map} to the print writer.
+ *
+ * <p>The dump is formatted:
+ * <pre>
+ * name:
+ * value
+ * value
+ * ...
+ * </pre>
+ */
+ static <T> void dumpIterable(IndentingPrintWriter pw, String name, Iterable<T> values) {
+ printWithTrailingColon(pw, name);
+ pw.increaseIndent();
+ for (T value : values) {
+ pw.println(value);
+ }
+ pw.decreaseIndent();
+ }
+
+ // Device configuration of its supported Codecs and their Short Audio Descriptors.
+ public static class DeviceConfig {
+ /** Name of the device. Should be {@link Constants.AudioDevice}. **/
+ public final String name;
+ /** List of a {@link CodecSad}. **/
+ public final List<CodecSad> supportedCodecs;
+
+ private DeviceConfig(String name, List<CodecSad> supportedCodecs) {
+ this.name = name;
+ this.supportedCodecs = supportedCodecs;
+ }
+ }
+
+ // Short Audio Descriptor of a specific Codec
+ public static class CodecSad {
+ /** Audio Codec. Should be {@link Constants.AudioCodec}. **/
+ public final int audioCodec;
+ /**
+ * Three-byte Short Audio Descriptor. See HDMI Specification 1.4b CEC 13.15.3 and
+ * ANSI-CTA-861-F-FINAL 7.5.2 Audio Data Block for more details.
+ */
+ public final byte[] sad;
+
+ public CodecSad(int audioCodec, byte[] sad) {
+ this.audioCodec = audioCodec;
+ this.sad = sad;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 41bf01f..354d8d1 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -92,6 +92,9 @@
if (source.mService.audioSystem() != null) {
source = source.mService.audioSystem();
}
+ if (source.getLocalActivePort() != Constants.CEC_SWITCH_HOME) {
+ source.switchInputOnReceivingNewActivePath(getSourcePath());
+ }
source.setRoutingPort(Constants.CEC_SWITCH_HOME);
source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
}
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 ac2dbdf..c16d1b4 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -26,7 +26,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
+import android.app.IUidObserver;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.content.BroadcastReceiver;
@@ -38,12 +41,14 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -69,6 +74,11 @@
* bucket, it will be eligible to run. When a job's bucket changes, its new quota is immediately
* applied to it.
*
+ * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
+ * freely when an app enters the foreground state and are restricted when the app leaves the
+ * foreground state. However, jobs that are started while the app is in the TOP state are not
+ * restricted regardless of the app's state change.
+ *
* Test: atest com.android.server.job.controllers.QuotaControllerTest
*/
public final class QuotaController extends StateController {
@@ -97,6 +107,12 @@
data.put(packageName, obj);
}
+ public void clear() {
+ for (int i = 0; i < mData.size(); ++i) {
+ mData.valueAt(i).clear();
+ }
+ }
+
/** Removes all the data for the user, if there was any. */
public void delete(int userId) {
mData.delete(userId);
@@ -119,6 +135,11 @@
return null;
}
+ /** @see SparseArray#indexOfKey */
+ public int indexOfKey(int userId) {
+ return mData.indexOfKey(userId);
+ }
+
/** Returns the userId at the given index. */
public int keyAt(int index) {
return mData.keyAt(index);
@@ -294,6 +315,17 @@
/** Cached calculation results for each app, with the standby buckets as the array indices. */
private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>();
+ /** List of UIDs currently in the foreground. */
+ private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
+
+ /**
+ * List of jobs that started while the UID was in the TOP state. There will be no more than
+ * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
+ * fine.
+ */
+ private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
+
+ private final ActivityManagerInternal mActivityManagerInternal;
private final AlarmManager mAlarmManager;
private final ChargingTracker mChargeTracker;
private final Handler mHandler;
@@ -343,6 +375,29 @@
}
};
+ private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+ mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget();
+ }
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ }
+
+ @Override
+ public void onUidActive(int uid) {
+ }
+
+ @Override
+ public void onUidIdle(int uid, boolean disabled) {
+ }
+
+ @Override
+ public void onUidCachedChanged(int uid, boolean cached) {
+ }
+ };
+
/**
* The rolling window size for each standby bucket. Within each window, an app will have 10
* minutes to run its jobs.
@@ -363,12 +418,15 @@
private static final int MSG_CLEAN_UP_SESSIONS = 1;
/** Check if a package is now within its quota. */
private static final int MSG_CHECK_PACKAGE = 2;
+ /** Process state for a UID has changed. */
+ private static final int MSG_UID_PROCESS_STATE_CHANGED = 3;
public QuotaController(JobSchedulerService service) {
super(service);
mHandler = new QcHandler(mContext.getMainLooper());
mChargeTracker = new ChargingTracker();
mChargeTracker.startTracking();
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
// Set up the app standby bucketing tracker
@@ -376,6 +434,14 @@
UsageStatsManagerInternal.class);
usageStats.addAppIdleStateChangeListener(new StandbyTracker());
+ try {
+ ActivityManager.getService().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
+
onConstantsUpdatedLocked();
}
@@ -399,11 +465,15 @@
if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
final int userId = jobStatus.getSourceUserId();
final String packageName = jobStatus.getSourcePackageName();
+ final int uid = jobStatus.getSourceUid();
Timer timer = mPkgTimers.get(userId, packageName);
if (timer == null) {
- timer = new Timer(userId, packageName);
+ timer = new Timer(uid, userId, packageName);
mPkgTimers.add(userId, packageName, timer);
}
+ if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) {
+ mTopStartedJobs.add(jobStatus);
+ }
timer.startTrackingJob(jobStatus);
}
@@ -421,6 +491,7 @@
if (jobs != null) {
jobs.remove(jobStatus);
}
+ mTopStartedJobs.remove(jobStatus);
}
}
@@ -511,6 +582,7 @@
mInQuotaAlarmListeners.delete(userId, packageName);
}
mExecutionStatsCache.delete(userId, packageName);
+ mForegroundUids.delete(uid);
}
@Override
@@ -522,6 +594,20 @@
mExecutionStatsCache.delete(userId);
}
+ private boolean isUidInForeground(int uid) {
+ if (UserHandle.isCore(uid)) {
+ return true;
+ }
+ synchronized (mLock) {
+ return mForegroundUids.get(uid);
+ }
+ }
+
+ /** @return true if the job was started while the app was in the TOP state. */
+ private boolean isTopStartedJob(@NonNull final JobStatus jobStatus) {
+ return mTopStartedJobs.contains(jobStatus);
+ }
+
/**
* Returns an appropriate standby bucket for the job, taking into account any standby
* exemptions.
@@ -537,9 +623,15 @@
private boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
- // Jobs for the active app should always be able to run.
- return jobStatus.uidActive || isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+ // A job is within quota if one of the following is true:
+ // 1. it was started while the app was in the TOP state
+ // 2. the app is currently in the foreground
+ // 3. the app overall is within its quota
+ return isTopStartedJob(jobStatus)
+ || isUidInForeground(jobStatus.getSourceUid())
+ || isWithinQuotaLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
}
private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
@@ -800,7 +892,7 @@
final boolean isCharging = mChargeTracker.isCharging();
if (DEBUG) Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
// Deal with Timers first.
- mPkgTimers.forEach((t) -> t.onChargingChanged(nowElapsed, isCharging));
+ mPkgTimers.forEach((t) -> t.onStateChanged(nowElapsed, isCharging));
// Now update jobs.
maybeUpdateAllConstraintsLocked();
}
@@ -837,10 +929,15 @@
boolean changed = false;
for (int i = jobs.size() - 1; i >= 0; --i) {
final JobStatus js = jobs.valueAt(i);
- if (js.uidActive) {
- // Jobs for the active app should always be able to run.
+ if (isTopStartedJob(js)) {
+ // Job was started while the app was in the TOP state so we should allow it to
+ // finish.
changed |= js.setQuotaConstraintSatisfied(true);
- } else if (realStandbyBucket == getEffectiveStandbyBucket(js)) {
+ } else if (realStandbyBucket != ACTIVE_INDEX
+ && realStandbyBucket == getEffectiveStandbyBucket(js)) {
+ // An app in the ACTIVE bucket may be out of quota while the job could be in quota
+ // for some reason. Therefore, avoid setting the real value here and check each job
+ // individually.
changed |= js.setQuotaConstraintSatisfied(realInQuota);
} else {
// This job is somehow exempted. Need to determine its own quota status.
@@ -854,7 +951,7 @@
maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
} else {
QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (alarmListener != null) {
+ if (alarmListener != null && alarmListener.isWaiting()) {
mAlarmManager.cancel(alarmListener);
// Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
alarmListener.setTriggerTime(0);
@@ -863,6 +960,56 @@
return changed;
}
+ private class UidConstraintUpdater implements Consumer<JobStatus> {
+ private final UserPackageMap<Integer> mToScheduleStartAlarms = new UserPackageMap<>();
+ public boolean wasJobChanged;
+
+ @Override
+ public void accept(JobStatus jobStatus) {
+ wasJobChanged |= jobStatus.setQuotaConstraintSatisfied(isWithinQuotaLocked(jobStatus));
+ final int userId = jobStatus.getSourceUserId();
+ final String packageName = jobStatus.getSourcePackageName();
+ final int realStandbyBucket = jobStatus.getStandbyBucket();
+ if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
+ QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
+ if (alarmListener != null && alarmListener.isWaiting()) {
+ mAlarmManager.cancel(alarmListener);
+ // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
+ alarmListener.setTriggerTime(0);
+ }
+ } else {
+ mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
+ }
+ }
+
+ void postProcess() {
+ for (int u = 0; u < mToScheduleStartAlarms.numUsers(); ++u) {
+ final int userId = mToScheduleStartAlarms.keyAt(u);
+ for (int p = 0; p < mToScheduleStartAlarms.numPackagesForUser(userId); ++p) {
+ final String packageName = mToScheduleStartAlarms.keyAt(u, p);
+ final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName);
+ maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket);
+ }
+ }
+ }
+
+ void reset() {
+ wasJobChanged = false;
+ mToScheduleStartAlarms.clear();
+ }
+ }
+
+ private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater();
+
+ private boolean maybeUpdateConstraintForUidLocked(final int uid) {
+ mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints);
+
+ mUpdateUidConstraints.postProcess();
+ boolean changed = mUpdateUidConstraints.wasJobChanged;
+ mUpdateUidConstraints.reset();
+ return changed;
+ }
+
/**
* Maybe schedule a non-wakeup alarm for the next time this package will have quota to run
* again. This should only be called if the package is already out of quota.
@@ -1052,6 +1199,7 @@
private final class Timer {
private final Package mPkg;
+ private final int mUid;
// List of jobs currently running for this app that started when the app wasn't in the
// foreground.
@@ -1059,16 +1207,18 @@
private long mStartTimeElapsed;
private int mBgJobCount;
- Timer(int userId, String packageName) {
+ Timer(int uid, int userId, String packageName) {
mPkg = new Package(userId, packageName);
+ mUid = uid;
}
void startTrackingJob(@NonNull JobStatus jobStatus) {
- if (jobStatus.uidActive) {
- // We intentionally don't pay attention to fg state changes after a job has started.
+ if (isTopStartedJob(jobStatus)) {
+ // We intentionally don't pay attention to fg state changes after a TOP job has
+ // started.
if (DEBUG) {
Slog.v(TAG,
- "Timer ignoring " + jobStatus.toShortString() + " because uidActive");
+ "Timer ignoring " + jobStatus.toShortString() + " because isTop");
}
return;
}
@@ -1076,7 +1226,7 @@
synchronized (mLock) {
// Always track jobs, even when charging.
mRunningBgJobs.add(jobStatus);
- if (!mChargeTracker.isCharging()) {
+ if (shouldTrackLocked()) {
mBgJobCount++;
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
@@ -1142,6 +1292,10 @@
}
}
+ boolean isRunning(JobStatus jobStatus) {
+ return mRunningBgJobs.contains(jobStatus);
+ }
+
long getCurrentDuration(long nowElapsed) {
synchronized (mLock) {
return !isActive() ? 0 : nowElapsed - mStartTimeElapsed;
@@ -1154,17 +1308,21 @@
}
}
- void onChargingChanged(long nowElapsed, boolean isCharging) {
+ private boolean shouldTrackLocked() {
+ return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
+ }
+
+ void onStateChanged(long nowElapsed, boolean isQuotaFree) {
synchronized (mLock) {
- if (isCharging) {
+ if (isQuotaFree) {
emitSessionLocked(nowElapsed);
- } else {
+ } else if (shouldTrackLocked()) {
// Start timing from unplug.
if (mRunningBgJobs.size() > 0) {
mStartTimeElapsed = nowElapsed;
// NOTE: this does have the unfortunate consequence that if the device is
- // repeatedly plugged in and unplugged, the job count for a package may be
- // artificially high.
+ // repeatedly plugged in and unplugged, or an app changes foreground state
+ // very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
// Starting the timer means that all cached execution stats are now
// incorrect.
@@ -1371,6 +1529,38 @@
}
break;
}
+ case MSG_UID_PROCESS_STATE_CHANGED: {
+ final int uid = msg.arg1;
+ final int procState = msg.arg2;
+ final int userId = UserHandle.getUserId(uid);
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
+ synchronized (mLock) {
+ boolean isQuotaFree;
+ if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ mForegroundUids.put(uid, true);
+ isQuotaFree = true;
+ } else {
+ mForegroundUids.delete(uid);
+ isQuotaFree = false;
+ }
+ // Update Timers first.
+ final int userIndex = mPkgTimers.indexOfKey(userId);
+ if (userIndex != -1) {
+ final int numPkgs = mPkgTimers.numPackagesForUser(userId);
+ for (int p = 0; p < numPkgs; ++p) {
+ Timer t = mPkgTimers.valueAt(userIndex, p);
+ if (t != null) {
+ t.onStateChanged(nowElapsed, isQuotaFree);
+ }
+ }
+ }
+ if (maybeUpdateConstraintForUidLocked(uid)) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+ break;
+ }
}
}
}
@@ -1420,6 +1610,12 @@
@VisibleForTesting
@NonNull
+ SparseBooleanArray getForegroundUids() {
+ return mForegroundUids;
+ }
+
+ @VisibleForTesting
+ @NonNull
Handler getHandler() {
return mHandler;
}
@@ -1450,6 +1646,10 @@
pw.println("In parole: " + mInParole);
pw.println();
+ pw.print("Foreground UIDs: ");
+ pw.println(mForegroundUids.toString());
+ pw.println();
+
mTrackedJobs.forEach((jobs) -> {
for (int j = 0; j < jobs.size(); j++) {
final JobStatus js = jobs.valueAt(j);
@@ -1460,6 +1660,9 @@
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
+ if (mTopStartedJobs.contains(js)) {
+ pw.print(" (TOP)");
+ }
pw.println();
pw.increaseIndent();
@@ -1511,6 +1714,11 @@
proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
proto.write(StateControllerProto.QuotaController.IS_IN_PAROLE, mInParole);
+ for (int i = 0; i < mForegroundUids.size(); ++i) {
+ proto.write(StateControllerProto.QuotaController.FOREGROUND_UIDS,
+ mForegroundUids.keyAt(i));
+ }
+
mTrackedJobs.forEach((jobs) -> {
for (int j = 0; j < jobs.size(); j++) {
final JobStatus js = jobs.valueAt(j);
@@ -1526,6 +1734,8 @@
proto.write(
StateControllerProto.QuotaController.TrackedJob.EFFECTIVE_STANDBY_BUCKET,
getEffectiveStandbyBucket(js));
+ proto.write(StateControllerProto.QuotaController.TrackedJob.IS_TOP_STARTED_JOB,
+ mTopStartedJobs.contains(js));
proto.write(StateControllerProto.QuotaController.TrackedJob.HAS_QUOTA,
js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
proto.write(StateControllerProto.QuotaController.TrackedJob.REMAINING_QUOTA_MS,
diff --git a/services/core/java/com/android/server/location/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java
index 29465ad..9e33943 100644
--- a/services/core/java/com/android/server/location/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/GnssConfiguration.java
@@ -29,7 +29,10 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
@@ -66,6 +69,7 @@
"USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL";
private static final String CONFIG_GPS_LOCK = "GPS_LOCK";
private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC";
+ public static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS";
// Limit on NI emergency mode time extension after emergency sessions ends
private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300; // 5 minute maximum
@@ -171,6 +175,32 @@
}
/**
+ * Returns the list of proxy apps from the value of config parameter NFW_PROXY_APPS or
+ * {@Collections.EMPTY_LIST} if no value is provided.
+ */
+ List<String> getProxyApps() {
+ // Space separated list of Android proxy app package names.
+ String proxyAppsStr = mProperties.getProperty(CONFIG_NFW_PROXY_APPS);
+ if (TextUtils.isEmpty(proxyAppsStr)) {
+ return Collections.EMPTY_LIST;
+ }
+
+ String[] proxyAppsArray = proxyAppsStr.trim().split("\\s+");
+ if (proxyAppsArray.length == 0) {
+ return Collections.EMPTY_LIST;
+ }
+
+ // TODO(b/122856486): Validate proxy app names so that a system app or some popular app
+ // with location permission is not specified as a proxy app.
+ ArrayList proxyApps = new ArrayList(proxyAppsArray.length);
+ for (String proxyApp : proxyAppsArray) {
+ proxyApps.add(proxyApp);
+ }
+
+ return proxyApps;
+ }
+
+ /**
* Updates the GNSS HAL satellite blacklist.
*/
void setSatelliteBlacklist(int[] constellations, int[] svids) {
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index dfb98c3..d346ddc 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -372,6 +372,7 @@
private boolean mSuplEsEnabled = false;
private final Context mContext;
+ private final Looper mLooper;
private final LocationExtras mLocationExtras = new LocationExtras();
private final GnssStatusListenerHelper mGnssStatusListenerHelper;
private final GnssSatelliteBlacklistHelper mGnssSatelliteBlacklistHelper;
@@ -382,6 +383,7 @@
private final NtpTimeHelper mNtpTimeHelper;
private final GnssBatchingProvider mGnssBatchingProvider;
private final GnssGeofenceProvider mGnssGeofenceProvider;
+ private GnssVisibilityControl mGnssVisibilityControl;
// Handler for processing events
private Handler mHandler;
@@ -556,6 +558,9 @@
mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT);
mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec());
mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1;
+ if (mGnssVisibilityControl != null) {
+ mGnssVisibilityControl.updateProxyApps(mGnssConfiguration.getProxyApps());
+ }
}
public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
@@ -563,6 +568,7 @@
super(locationProviderManager);
mContext = context;
+ mLooper = looper;
// Create a wake lock
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -1869,6 +1875,27 @@
}
}
+ // Implements method nfwNotifyCb() in IGnssVisibilityControlCallback.hal.
+ @NativeEntryPoint
+ private void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
+ String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
+ boolean inEmergencyMode, boolean isCachedLocation) {
+ if (mGnssVisibilityControl == null) {
+ Log.e(TAG, "reportNfwNotification: mGnssVisibilityControl is not initialized.");
+ return;
+ }
+
+ mGnssVisibilityControl.reportNfwNotification(proxyAppPackageName, protocolStack,
+ otherProtocolStackName, requestor, requestorId, responseType, inEmergencyMode,
+ isCachedLocation);
+ }
+
+ // Implements method isInEmergencySession() in IGnssVisibilityControlCallback.hal.
+ @NativeEntryPoint
+ boolean isInEmergencySession() {
+ return mNIHandler.getInEmergency();
+ }
+
private void sendMessage(int message, int arg, Object obj) {
// hold a wake lock until this message is delivered
// note that this assumes the message will not be removed from the queue before
@@ -1957,6 +1984,10 @@
native_cleanup();
}
+ if (native_is_gnss_visibility_control_supported()) {
+ mGnssVisibilityControl = new GnssVisibilityControl(mContext, mLooper);
+ }
+
// load default GPS configuration
// (this configuration might change in the future based on SIM changes)
reloadGpsProperties();
@@ -2114,6 +2145,8 @@
private static native boolean native_is_supported();
+ private static native boolean native_is_gnss_visibility_control_supported();
+
private static native void native_init_once();
private native boolean native_init();
diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
new file mode 100644
index 0000000..845aa9d
--- /dev/null
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.SuppressLint;
+import android.app.AppOpsManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Handles GNSS non-framework location access user visibility and control.
+ *
+ * The state of the GnssVisibilityControl object must be accessed/modified through the Handler
+ * thread only.
+ */
+class GnssVisibilityControl {
+ private static final String TAG = "GnssVisibilityControl";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // Constants related to non-framework (NFW) location access permission proxy apps.
+ private static final String NFW_PROXY_APP_PKG_ACTIVITY_NAME_SUFFIX =
+ ".NonFrameworkLocationAccessActivity";
+ private static final String NFW_INTENT_ACTION_NFW_LOCATION_ACCESS_SUFFIX =
+ ".intent.action.NON_FRAMEWORK_LOCATION_ACCESS";
+ private static final String NFW_INTENT_TYPE = "text/plain";
+
+ private static final String LOCATION_PERMISSION_NAME =
+ "android.permission.ACCESS_FINE_LOCATION";
+
+ // Wakelocks
+ private static final String WAKELOCK_KEY = TAG;
+ private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
+ private final PowerManager.WakeLock mWakeLock;
+
+ private final AppOpsManager mAppOps;
+ private final PackageManager mPackageManager;
+
+ private final Handler mHandler;
+ private final Context mContext;
+
+ // Number of non-framework location access proxy apps is expected to be small (< 5).
+ private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7;
+ private HashMap<String, Boolean> mProxyAppToLocationPermissions = new HashMap<>(
+ HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS);
+
+ private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener =
+ uid -> postEvent(() -> handlePermissionsChanged(uid));
+
+ GnssVisibilityControl(Context context, Looper looper) {
+ mContext = context;
+ PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
+ mHandler = new Handler(looper);
+ mAppOps = mContext.getSystemService(AppOpsManager.class);
+ mPackageManager = mContext.getPackageManager();
+ // TODO(b/122855984): Handle global location settings on/off.
+ // TODO(b/122856189): Handle roaming case.
+ }
+
+ void updateProxyApps(List<String> nfwLocationAccessProxyApps) {
+ // NOTE: This class doesn't explicitly register and listen for SIM_STATE_CHANGED event
+ // but rather piggy backs on the GnssLocationProvider SIM_STATE_CHANGED handling
+ // so that the order of processing is preserved. GnssLocationProvider should
+ // first load the new config parameters for the new SIM and then call this method.
+ postEvent(() -> handleSubscriptionOrSimChanged(nfwLocationAccessProxyApps));
+ }
+
+ void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
+ String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
+ boolean inEmergencyMode, boolean isCachedLocation) {
+ postEvent(() -> handleNfwNotification(
+ new NfwNotification(proxyAppPackageName, protocolStack, otherProtocolStackName,
+ requestor, requestorId, responseType, inEmergencyMode, isCachedLocation)));
+ }
+
+ private void handleSubscriptionOrSimChanged(List<String> nfwLocationAccessProxyApps) {
+ if (nfwLocationAccessProxyApps.isEmpty()) {
+ // Stop listening for app permission changes. Clear the app list in GNSS HAL.
+ if (!mProxyAppToLocationPermissions.isEmpty()) {
+ mPackageManager.removeOnPermissionsChangeListener(mOnPermissionsChangedListener);
+ mProxyAppToLocationPermissions.clear();
+ updateNfwLocationAccessProxyAppsInGnssHal();
+ }
+ return;
+ }
+
+ if (mProxyAppToLocationPermissions.isEmpty()) {
+ mPackageManager.addOnPermissionsChangeListener(mOnPermissionsChangedListener);
+ } else {
+ mProxyAppToLocationPermissions.clear();
+ }
+
+ for (String proxApp : nfwLocationAccessProxyApps) {
+ mProxyAppToLocationPermissions.put(proxApp, hasLocationPermission(proxApp));
+ }
+
+ updateNfwLocationAccessProxyAppsInGnssHal();
+ }
+
+ // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal
+ private static class NfwNotification {
+ private static final String KEY_PROTOCOL_STACK = "ProtocolStack";
+ private static final String KEY_OTHER_PROTOCOL_STACK_NAME = "OtherProtocolStackName";
+ private static final String KEY_REQUESTOR = "Requestor";
+ private static final String KEY_REQUESTOR_ID = "RequestorId";
+ private static final String KEY_RESPONSE_TYPE = "ResponseType";
+ private static final String KEY_IN_EMERGENCY_MODE = "InEmergencyMode";
+ private static final String KEY_IS_CACHED_LOCATION = "IsCachedLocation";
+
+ // This must match with NfwResponseType enum in IGnssVisibilityControlCallback.hal.
+ private static final byte NFW_RESPONSE_TYPE_REJECTED = 0;
+ private static final byte NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED = 1;
+ private static final byte NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED = 2;
+
+ private final String mProxyAppPackageName;
+ private final byte mProtocolStack;
+ private final String mOtherProtocolStackName;
+ private final byte mRequestor;
+ private final String mRequestorId;
+ private final byte mResponseType;
+ private final boolean mInEmergencyMode;
+ private final boolean mIsCachedLocation;
+
+ NfwNotification(String proxyAppPackageName, byte protocolStack,
+ String otherProtocolStackName, byte requestor, String requestorId,
+ byte responseType, boolean inEmergencyMode, boolean isCachedLocation) {
+ mProxyAppPackageName = proxyAppPackageName;
+ mProtocolStack = protocolStack;
+ mOtherProtocolStackName = otherProtocolStackName;
+ mRequestor = requestor;
+ mRequestorId = requestorId;
+ mResponseType = responseType;
+ mInEmergencyMode = inEmergencyMode;
+ mIsCachedLocation = isCachedLocation;
+ }
+
+ void copyFieldsToIntent(Intent intent) {
+ intent.putExtra(KEY_PROTOCOL_STACK, mProtocolStack);
+ if (!TextUtils.isEmpty(mOtherProtocolStackName)) {
+ intent.putExtra(KEY_OTHER_PROTOCOL_STACK_NAME, mOtherProtocolStackName);
+ }
+ intent.putExtra(KEY_REQUESTOR, mRequestor);
+ if (!TextUtils.isEmpty(mRequestorId)) {
+ intent.putExtra(KEY_REQUESTOR_ID, mRequestorId);
+ }
+ intent.putExtra(KEY_RESPONSE_TYPE, mResponseType);
+ intent.putExtra(KEY_IN_EMERGENCY_MODE, mInEmergencyMode);
+ if (mResponseType == NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED) {
+ intent.putExtra(KEY_IS_CACHED_LOCATION, mIsCachedLocation);
+ }
+ }
+
+ @SuppressLint("DefaultLocale")
+ public String toString() {
+ return String.format(
+ "[Notification] proxyAppPackageName: %s, protocolStack: %d"
+ + ", otherProtocolStackName: %s, requestor: %d, requestorId: %s"
+ + ", responseType: %d, inEmergencyMode: %b, isCachedLocation: %b",
+ mProxyAppPackageName, mProtocolStack, mOtherProtocolStackName,
+ mRequestor, mRequestorId, mResponseType, mInEmergencyMode, mIsCachedLocation);
+ }
+
+ String getResponseTypeAsString() {
+ switch (mResponseType) {
+ case NFW_RESPONSE_TYPE_REJECTED:
+ return "REJECTED";
+ case NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED:
+ return "ACCEPTED_NO_LOCATION_PROVIDED";
+ case NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED:
+ return "ACCEPTED_LOCATION_PROVIDED";
+ default:
+ return "<Unknown>";
+ }
+ }
+ }
+
+ private void handlePermissionsChanged(int uid) {
+ if (mProxyAppToLocationPermissions.isEmpty()) {
+ return;
+ }
+
+ for (Map.Entry<String, Boolean> entry : mProxyAppToLocationPermissions.entrySet()) {
+ // Cannot cache uid since the application could be uninstalled and reinstalled.
+ final String proxyApp = entry.getKey();
+ final Integer nfwProxyAppUid = getApplicationUid(proxyApp);
+ if (nfwProxyAppUid == null || nfwProxyAppUid != uid) {
+ continue;
+ }
+
+ final boolean isLocationPermissionEnabled = hasLocationPermission(proxyApp);
+ if (isLocationPermissionEnabled != entry.getValue()) {
+ Log.i(TAG, "Location permission setting is changed to "
+ + (isLocationPermissionEnabled ? "enabled" : "disabled")
+ + " for non-framework location access proxy app "
+ + proxyApp);
+ entry.setValue(isLocationPermissionEnabled);
+ updateNfwLocationAccessProxyAppsInGnssHal();
+ return;
+ }
+ }
+ }
+
+ private Integer getApplicationUid(String pkgName) {
+ try {
+ return mPackageManager.getApplicationInfo(pkgName, 0).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ if (DEBUG) {
+ Log.d(TAG, "Non-framework location access proxy app "
+ + pkgName + " is not found.");
+ }
+ return null;
+ }
+ }
+
+ private boolean hasLocationPermission(String pkgName) {
+ return mPackageManager.checkPermission(LOCATION_PERMISSION_NAME, pkgName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void updateNfwLocationAccessProxyAppsInGnssHal() {
+ // Get a count of proxy apps with location permission enabled to array creation size.
+ int countLocationPermissionEnabledProxyApps = 0;
+ for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) {
+ if (hasLocationPermissionEnabled) {
+ ++countLocationPermissionEnabledProxyApps;
+ }
+ }
+
+ int i = 0;
+ String[] locationPermissionEnabledProxyApps =
+ new String[countLocationPermissionEnabledProxyApps];
+ for (Map.Entry<String, Boolean> entry : mProxyAppToLocationPermissions.entrySet()) {
+ final String proxyApp = entry.getKey();
+ final boolean hasLocationPermissionEnabled = entry.getValue();
+ if (hasLocationPermissionEnabled) {
+ locationPermissionEnabledProxyApps[i++] = proxyApp;
+ }
+ }
+
+ String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps);
+ Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: "
+ + proxyAppsStr);
+ boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps);
+ if (!result) {
+ Log.e(TAG, "Failed to update non-framework location access proxy apps in the"
+ + " GNSS HAL to: " + proxyAppsStr);
+ }
+ }
+
+ private void handleNfwNotification(NfwNotification nfwNotification) {
+ if (DEBUG) Log.d(TAG, nfwNotification.toString());
+
+ final String proxyAppPackageName = nfwNotification.mProxyAppPackageName;
+ if (TextUtils.isEmpty(proxyAppPackageName)) {
+ Log.e(TAG, "ProxyAppPackageName field is not set. Not sending intent to proxy app for "
+ + nfwNotification);
+ return;
+ }
+
+ Boolean isLocationPermissionEnabled = mProxyAppToLocationPermissions.get(
+ proxyAppPackageName);
+ if (isLocationPermissionEnabled == null) {
+ // App is not in the configured list.
+ Log.e(TAG, "Could not find proxy app with name: " + proxyAppPackageName + " in the "
+ + "value specified for config parameter: "
+ + GnssConfiguration.CONFIG_NFW_PROXY_APPS + ". Not sending intent to proxy app"
+ + " for " + nfwNotification);
+ return;
+ }
+
+ // Send intent to non-framework location proxy app with notification information.
+ final Intent intent = new Intent(
+ proxyAppPackageName + NFW_INTENT_ACTION_NFW_LOCATION_ACCESS_SUFFIX);
+ final String proxAppActivityName =
+ proxyAppPackageName + NFW_PROXY_APP_PKG_ACTIVITY_NAME_SUFFIX;
+ intent.setClassName(proxyAppPackageName, proxAppActivityName);
+ intent.setType(NFW_INTENT_TYPE);
+ nfwNotification.copyFieldsToIntent(intent);
+
+ // Check if the proxy app is still installed.
+ final Integer clsAppUid = getApplicationUid(proxyAppPackageName);
+ if (clsAppUid == null || intent.resolveActivity(mPackageManager) == null) {
+ Log.i(TAG, "Proxy application " + proxyAppPackageName + " and/or activity "
+ + proxAppActivityName + " is not found. Not sending"
+ + " intent to proxy app for " + nfwNotification);
+ return;
+ }
+
+ // Display location icon attributed to this proxy app.
+ mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, clsAppUid, proxyAppPackageName);
+
+ // Log proxy app permission mismatch between framework and GNSS HAL.
+ boolean isLocationRequestAccepted =
+ nfwNotification.mResponseType != NfwNotification.NFW_RESPONSE_TYPE_REJECTED;
+ if (isLocationPermissionEnabled != isLocationRequestAccepted) {
+ Log.w(TAG, "Permission mismatch. Framework proxy app " + proxyAppPackageName
+ + " location permission is set to " + isLocationPermissionEnabled
+ + " but GNSS non-framework location access response type is "
+ + nfwNotification.getResponseTypeAsString() + " for " + nfwNotification);
+ }
+
+ // Notify proxy app.
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Sending non-framework location access notification intent: " + intent);
+ }
+ mContext.startActivityAsUser(intent, UserHandle.getUserHandleForUid(clsAppUid));
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity not found. Failed to send non-framework location access"
+ + " notification intent to proxy app activity: " + proxAppActivityName);
+ }
+ }
+
+ private void postEvent(Runnable event) {
+ // Hold a wake lock until this message is delivered.
+ // Note that this assumes the message will not be removed from the queue before
+ // it is handled (otherwise the wake lock would be leaked).
+ mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
+ if (!mHandler.post(runEventAndReleaseWakeLock(event))) {
+ mWakeLock.release();
+ }
+ }
+
+ private Runnable runEventAndReleaseWakeLock(Runnable event) {
+ return () -> {
+ try {
+ event.run();
+ } finally {
+ mWakeLock.release();
+ }
+ };
+ }
+
+ private native boolean native_enable_nfw_location_access(String[] proxyApps);
+}
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index 86bc9f3..fe91c63 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -52,7 +52,6 @@
mExtras = null;
setProperties(properties);
- setEnabled(true);
}
/** Sets the enabled state of this mock provider. */
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index d611a17..7fffe8e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -80,7 +80,7 @@
private final ControllerStub mController;
private final SessionStub mSession;
private final SessionCb mSessionCb;
- private final MediaSessionService mService;
+ private final MediaSessionService.ServiceImpl mService;
private final Context mContext;
private final Object mLock = new Object();
@@ -120,7 +120,8 @@
private String mMetadataDescription;
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
- SessionCallbackLink cb, String tag, MediaSessionService service, Looper handlerLooper) {
+ SessionCallbackLink cb, String tag, MediaSessionService.ServiceImpl service,
+ Looper handlerLooper) {
mOwnerPid = ownerPid;
mOwnerUid = ownerUid;
mUserId = userId;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index ba7b87e..d20ed8c 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,79 +16,15 @@
package com.android.server.media;
-import static android.os.UserHandle.USER_ALL;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.INotificationManager;
-import android.app.KeyguardManager;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.media.AudioManager;
-import android.media.AudioManagerInternal;
-import android.media.AudioPlaybackConfiguration;
-import android.media.AudioSystem;
-import android.media.IAudioService;
-import android.media.IRemoteVolumeController;
-import android.media.MediaController2;
-import android.media.Session2CommandGroup;
import android.media.Session2Token;
-import android.media.session.IActiveSessionsListener;
-import android.media.session.ICallback;
-import android.media.session.IOnMediaKeyListener;
-import android.media.session.IOnVolumeKeyLongPressListener;
-import android.media.session.ISession;
-import android.media.session.ISession2TokensListener;
-import android.media.session.ISessionManager;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.SessionCallbackLink;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.speech.RecognizerIntent;
-import android.text.TextUtils;
import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.DumpUtils;
-import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.Watchdog.Monitor;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -97,2040 +33,124 @@
public class MediaSessionService extends SystemService implements Monitor {
private static final String TAG = "MediaSessionService";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // Leave log for key event always.
- private static final boolean DEBUG_KEY_EVENT = true;
- private static final int WAKELOCK_TIMEOUT = 5000;
- private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
-
- private final SessionManagerImpl mSessionManagerImpl;
- private final MessageHandler mHandler = new MessageHandler();
- private final PowerManager.WakeLock mMediaEventWakeLock;
- private final int mLongPressTimeout;
- private final INotificationManager mNotificationManager;
- private final Object mLock = new Object();
- // Keeps the full user id for each user.
- @GuardedBy("mLock")
- private final SparseIntArray mFullUserIds = new SparseIntArray();
- @GuardedBy("mLock")
- private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
- @GuardedBy("mLock")
- private final ArrayList<SessionsListenerRecord> mSessionsListeners
- = new ArrayList<SessionsListenerRecord>();
- // Map user id as index to list of Session2Tokens
- // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
- // one place.
- @GuardedBy("mLock")
- private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
- @GuardedBy("mLock")
- private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
- new ArrayList<>();
-
- private KeyguardManager mKeyguardManager;
- private IAudioService mAudioService;
- private AudioManagerInternal mAudioManagerInternal;
- private ActivityManager mActivityManager;
- private ContentResolver mContentResolver;
- private SettingsObserver mSettingsObserver;
- private boolean mHasFeatureLeanback;
-
- // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
- // It's always not null after the MediaSessionService is started.
- private FullUserRecord mCurrentFullUserRecord;
- private MediaSessionRecord mGlobalPrioritySession;
- private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
-
- // Used to notify system UI when remote volume was changed. TODO find a
- // better way to handle this.
- private IRemoteVolumeController mRvc;
+ private final ServiceImpl mImpl;
public MediaSessionService(Context context) {
super(context);
- mSessionManagerImpl = new SessionManagerImpl();
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
- mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
- mNotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ mImpl = new MediaSessionServiceImpl(context);
}
@Override
public void onStart() {
- publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
+ publishBinderService(Context.MEDIA_SESSION_SERVICE, mImpl.getServiceBinder());
Watchdog.getInstance().addMonitor(this);
- mKeyguardManager =
- (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
- mAudioService = getAudioService();
- mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
- mActivityManager =
- (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
- mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
- mAudioPlayerStateMonitor.registerListener(
- (config, isRemoved) -> {
- if (isRemoved || !config.isActive() || config.getPlayerType()
- == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
- return;
- }
- synchronized (mLock) {
- FullUserRecord user = getFullUserRecordLocked(
- UserHandle.getUserId(config.getClientUid()));
- if (user != null) {
- user.mPriorityStack.updateMediaButtonSessionIfNeeded();
- }
- }
- }, null /* handler */);
- mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
- mContentResolver = getContext().getContentResolver();
- mSettingsObserver = new SettingsObserver();
- mSettingsObserver.observe();
- mHasFeatureLeanback = getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LEANBACK);
- updateUser();
+ mImpl.onStart();
}
- private IAudioService getAudioService() {
- IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
- return IAudioService.Stub.asInterface(b);
+ @Override
+ public void onStartUser(int userId) {
+ mImpl.onStartUser(userId);
}
- private boolean isGlobalPriorityActiveLocked() {
- return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
+ @Override
+ public void onSwitchUser(int userId) {
+ mImpl.onSwitchUser(userId);
}
+ // Called when the user with the userId is removed.
+ @Override
+ public void onStopUser(int userId) {
+ mImpl.onStopUser(userId);
+ }
+
+ @Override
+ public void monitor() {
+ mImpl.monitor();
+ }
+
+ /**
+ * Updates session.
+ */
public void updateSession(MediaSessionRecord record) {
- synchronized (mLock) {
- FullUserRecord user = getFullUserRecordLocked(record.getUserId());
- if (user == null) {
- Log.w(TAG, "Unknown session updated. Ignoring.");
- return;
- }
- if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
- }
- user.pushAddressedPlayerChangedLocked();
- } else {
- if (!user.mPriorityStack.contains(record)) {
- Log.w(TAG, "Unknown session updated. Ignoring.");
- return;
- }
- user.mPriorityStack.onSessionStateChange(record);
- }
- mHandler.postSessionsChanged(record.getUserId());
- }
+ mImpl.updateSession(record);
}
+ /**
+ * Sets global priority session.
+ */
public void setGlobalPrioritySession(MediaSessionRecord record) {
- synchronized (mLock) {
- FullUserRecord user = getFullUserRecordLocked(record.getUserId());
- if (mGlobalPrioritySession != record) {
- Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
- + " to " + record);
- mGlobalPrioritySession = record;
- if (user != null && user.mPriorityStack.contains(record)) {
- // Handle the global priority session separately.
- // Otherwise, it can be the media button session regardless of the active state
- // because it or other system components might have been the lastly played media
- // app.
- user.mPriorityStack.removeSession(record);
- }
- }
- }
- }
-
- private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
- List<MediaSessionRecord> records = new ArrayList<>();
- if (userId == USER_ALL) {
- int size = mUserRecords.size();
- for (int i = 0; i < size; i++) {
- records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
- }
- } else {
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user == null) {
- Log.w(TAG, "getSessions failed. Unknown user " + userId);
- return records;
- }
- records.addAll(user.mPriorityStack.getActiveSessions(userId));
- }
-
- // Return global priority session at the first whenever it's asked.
- if (isGlobalPriorityActiveLocked()
- && (userId == USER_ALL || userId == mGlobalPrioritySession.getUserId())) {
- records.add(0, mGlobalPrioritySession);
- }
- return records;
+ mImpl.setGlobalPrioritySession(record);
}
List<Session2Token> getSession2TokensLocked(int userId) {
- List<Session2Token> list = new ArrayList<>();
- if (userId == USER_ALL) {
- for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
- list.addAll(mSession2TokensPerUser.valueAt(i));
- }
- } else {
- list.addAll(mSession2TokensPerUser.get(userId));
- }
- return list;
+ return mImpl.getSession2TokensLocked(userId);
}
/**
* Tells the system UI that volume has changed on an active remote session.
*/
public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
- if (mRvc == null || !session.isActive()) {
- return;
- }
- try {
- mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
- } catch (Exception e) {
- Log.wtf(TAG, "Error sending volume change to system UI.", e);
- }
+ mImpl.notifyRemoteVolumeChanged(flags, session);
}
+ /**
+ * Called when session playstate is changed.
+ */
public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
- synchronized (mLock) {
- FullUserRecord user = getFullUserRecordLocked(record.getUserId());
- if (user == null || !user.mPriorityStack.contains(record)) {
- Log.d(TAG, "Unknown session changed playback state. Ignoring.");
- return;
- }
- user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
- }
+ mImpl.onSessionPlaystateChanged(record, oldState, newState);
}
+ /**
+ * Called when session playback type is changed.
+ */
public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
- synchronized (mLock) {
- FullUserRecord user = getFullUserRecordLocked(record.getUserId());
- if (user == null || !user.mPriorityStack.contains(record)) {
- Log.d(TAG, "Unknown session changed playback type. Ignoring.");
- return;
- }
- pushRemoteVolumeUpdateLocked(record.getUserId());
- }
- }
-
- @Override
- public void onStartUser(int userId) {
- if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
- updateUser();
- }
-
- @Override
- public void onSwitchUser(int userId) {
- if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
- updateUser();
- }
-
- // Called when the user with the userId is removed.
- @Override
- public void onStopUser(int userId) {
- if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
- synchronized (mLock) {
- // TODO: Also handle removing user in updateUser() because adding/switching user is
- // handled in updateUser().
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user != null) {
- if (user.mFullUserId == userId) {
- user.destroySessionsForUserLocked(USER_ALL);
- mUserRecords.remove(userId);
- } else {
- user.destroySessionsForUserLocked(userId);
- }
- }
- mSession2TokensPerUser.remove(userId);
- updateUser();
- }
- }
-
- @Override
- public void monitor() {
- synchronized (mLock) {
- // Check for deadlock
- }
+ mImpl.onSessionPlaybackTypeChanged(record);
}
protected void enforcePhoneStatePermission(int pid, int uid) {
- if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
- }
+ mImpl.enforcePhoneStatePermission(pid, uid);
}
void sessionDied(MediaSessionRecord session) {
- synchronized (mLock) {
- destroySessionLocked(session);
- }
+ mImpl.sessionDied(session);
}
void destroySession(MediaSessionRecord session) {
- synchronized (mLock) {
- destroySessionLocked(session);
- }
- }
-
- private void updateUser() {
- synchronized (mLock) {
- UserManager manager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
- mFullUserIds.clear();
- List<UserInfo> allUsers = manager.getUsers();
- if (allUsers != null) {
- for (UserInfo userInfo : allUsers) {
- if (userInfo.isManagedProfile()) {
- mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
- } else {
- mFullUserIds.put(userInfo.id, userInfo.id);
- if (mUserRecords.get(userInfo.id) == null) {
- mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
- }
- }
- if (mSession2TokensPerUser.get(userInfo.id) == null) {
- mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
- }
- }
- }
- // Ensure that the current full user exists.
- int currentFullUserId = ActivityManager.getCurrentUser();
- mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
- if (mCurrentFullUserRecord == null) {
- Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
- mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
- mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
- if (mSession2TokensPerUser.get(currentFullUserId) == null) {
- mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
- }
- }
- mFullUserIds.put(currentFullUserId, currentFullUserId);
- }
- }
-
- private void updateActiveSessionListeners() {
- synchronized (mLock) {
- for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
- SessionsListenerRecord listener = mSessionsListeners.get(i);
- try {
- enforceMediaPermissions(listener.componentName, listener.pid, listener.uid,
- listener.userId);
- } catch (SecurityException e) {
- Log.i(TAG, "ActiveSessionsListener " + listener.componentName
- + " is no longer authorized. Disconnecting.");
- mSessionsListeners.remove(i);
- try {
- listener.listener
- .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
- } catch (Exception e1) {
- // ignore
- }
- }
- }
- }
- }
-
- /*
- * When a session is removed several things need to happen.
- * 1. We need to remove it from the relevant user.
- * 2. We need to remove it from the priority stack.
- * 3. We need to remove it from all sessions.
- * 4. If this is the system priority session we need to clear it.
- * 5. We need to unlink to death from the cb binder
- * 6. We need to tell the session to do any final cleanup (onDestroy)
- */
- private void destroySessionLocked(MediaSessionRecord session) {
- if (DEBUG) {
- Log.d(TAG, "Destroying " + session);
- }
- FullUserRecord user = getFullUserRecordLocked(session.getUserId());
- if (mGlobalPrioritySession == session) {
- mGlobalPrioritySession = null;
- if (session.isActive() && user != null) {
- user.pushAddressedPlayerChangedLocked();
- }
- } else {
- if (user != null) {
- user.mPriorityStack.removeSession(session);
- }
- }
-
- try {
- session.getCallback().getBinder().unlinkToDeath(session, 0);
- } catch (Exception e) {
- // ignore exceptions while destroying a session.
- }
- session.onDestroy();
- mHandler.postSessionsChanged(session.getUserId());
- }
-
- private void enforcePackageName(String packageName, int uid) {
- if (TextUtils.isEmpty(packageName)) {
- throw new IllegalArgumentException("packageName may not be empty");
- }
- String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
- final int packageCount = packages.length;
- for (int i = 0; i < packageCount; i++) {
- if (packageName.equals(packages[i])) {
- return;
- }
- }
- throw new IllegalArgumentException("packageName is not owned by the calling process");
- }
-
- /**
- * Checks a caller's authorization to register an IRemoteControlDisplay.
- * Authorization is granted if one of the following is true:
- * <ul>
- * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
- * permission</li>
- * <li>the caller's listener is one of the enabled notification listeners
- * for the caller's user</li>
- * </ul>
- */
- private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
- int resolvedUserId) {
- if (isCurrentVolumeController(pid, uid)) return;
- if (getContext()
- .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
- != PackageManager.PERMISSION_GRANTED
- && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
- resolvedUserId)) {
- throw new SecurityException("Missing permission to control media.");
- }
- }
-
- private boolean isCurrentVolumeController(int pid, int uid) {
- return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
- pid, uid) == PackageManager.PERMISSION_GRANTED;
- }
-
- private void enforceSystemUiPermission(String action, int pid, int uid) {
- if (!isCurrentVolumeController(pid, uid)) {
- throw new SecurityException("Only system ui may " + action);
- }
- }
-
- /**
- * This checks if the component is an enabled notification listener for the
- * specified user. Enabled components may only operate on behalf of the user
- * they're running as.
- *
- * @param compName The component that is enabled.
- * @param userId The user id of the caller.
- * @param forUserId The user id they're making the request on behalf of.
- * @return True if the component is enabled, false otherwise
- */
- private boolean isEnabledNotificationListener(ComponentName compName, int userId,
- int forUserId) {
- if (userId != forUserId) {
- // You may not access another user's content as an enabled listener.
- return false;
- }
- if (DEBUG) {
- Log.d(TAG, "Checking if enabled notification listener " + compName);
- }
- if (compName != null) {
- try {
- return mNotificationManager.isNotificationListenerAccessGrantedForUser(
- compName, userId);
- } catch(RemoteException e) {
- Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
- }
- }
- return false;
- }
-
- private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
- String callerPackageName, SessionCallbackLink cb, String tag) throws RemoteException {
- synchronized (mLock) {
- return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
- }
- }
-
- /*
- * When a session is created the following things need to happen.
- * 1. Its callback binder needs a link to death
- * 2. It needs to be added to all sessions.
- * 3. It needs to be added to the priority stack.
- * 4. It needs to be added to the relevant user record.
- */
- private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
- String callerPackageName, SessionCallbackLink cb, String tag) {
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user == null) {
- Log.wtf(TAG, "Request from invalid user: " + userId);
- throw new RuntimeException("Session request from invalid user.");
- }
-
- final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
- callerPackageName, cb, tag, this, mHandler.getLooper());
- try {
- cb.getBinder().linkToDeath(session, 0);
- } catch (RemoteException e) {
- throw new RuntimeException("Media Session owner died prematurely.", e);
- }
-
- user.mPriorityStack.addSession(session);
- mHandler.postSessionsChanged(userId);
-
- if (DEBUG) {
- Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
- }
- return session;
- }
-
- private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
- for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
- if (mSessionsListeners.get(i).listener.asBinder() == listener.asBinder()) {
- return i;
- }
- }
- return -1;
- }
-
- private int findIndexOfSession2TokensListenerLocked(ISession2TokensListener listener) {
- for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
- if (mSession2TokensListenerRecords.get(i).listener.asBinder() == listener.asBinder()) {
- return i;
- }
- }
- return -1;
- }
-
-
- private void pushSessionsChanged(int userId) {
- synchronized (mLock) {
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user == null) {
- Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
- return;
- }
- List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
- int size = records.size();
- ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
- for (int i = 0; i < size; i++) {
- tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
- }
- pushRemoteVolumeUpdateLocked(userId);
- for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
- SessionsListenerRecord record = mSessionsListeners.get(i);
- if (record.userId == USER_ALL || record.userId == userId) {
- try {
- record.listener.onActiveSessionsChanged(tokens);
- } catch (RemoteException e) {
- Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
- e);
- mSessionsListeners.remove(i);
- }
- }
- }
- }
- }
-
- private void pushRemoteVolumeUpdateLocked(int userId) {
- if (mRvc != null) {
- try {
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user == null) {
- Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
- return;
- }
- MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
- mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
- }
- }
+ mImpl.destroySession(session);
}
void pushSession2TokensChangedLocked(int userId) {
- List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
- List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
-
- for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
- Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
- try {
- if (listenerRecord.userId == USER_ALL) {
- listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
- } else if (listenerRecord.userId == userId) {
- listenerRecord.listener.onSession2TokensChanged(session2Tokens);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
- mSession2TokensListenerRecords.remove(i);
- }
- }
+ mImpl.pushSession2TokensChangedLocked(userId);
}
/**
- * Called when the media button receiver for the {@param record} is changed.
- *
- * @param record the media session whose media button receiver is updated.
+ * Called when media button receiver changed.
*/
public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
- synchronized (mLock) {
- FullUserRecord user = getFullUserRecordLocked(record.getUserId());
- MediaSessionRecord mediaButtonSession =
- user.mPriorityStack.getMediaButtonSession();
- if (record == mediaButtonSession) {
- user.rememberMediaButtonReceiverLocked(mediaButtonSession);
- }
- }
+ mImpl.onMediaButtonReceiverChanged(record);
}
- private String getCallingPackageName(int uid) {
- String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
- if (packages != null && packages.length > 0) {
- return packages[0];
- }
- return "";
- }
-
- private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
- if (mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
- return;
- }
- try {
- mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
- }
- }
-
- private FullUserRecord getFullUserRecordLocked(int userId) {
- int fullUserId = mFullUserIds.get(userId, -1);
- if (fullUserId < 0) {
- return null;
- }
- return mUserRecords.get(fullUserId);
- }
-
- /**
- * Information about a full user and its corresponding managed profiles.
- *
- * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
- * them when he/she presses a media/volume button. So keeping media sessions for them in one
- * place makes more sense and increases the readability.</p>
- * <p>The contents of this object is guarded by {@link #mLock}.
- */
- final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
- public static final int COMPONENT_TYPE_INVALID = 0;
- public static final int COMPONENT_TYPE_BROADCAST = 1;
- public static final int COMPONENT_TYPE_ACTIVITY = 2;
- public static final int COMPONENT_TYPE_SERVICE = 3;
- private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
-
- private final int mFullUserId;
- private final MediaSessionStack mPriorityStack;
- private PendingIntent mLastMediaButtonReceiver;
- private ComponentName mRestoredMediaButtonReceiver;
- private int mRestoredMediaButtonReceiverComponentType;
- private int mRestoredMediaButtonReceiverUserId;
-
- private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
- private int mOnVolumeKeyLongPressListenerUid;
- private KeyEvent mInitialDownVolumeKeyEvent;
- private int mInitialDownVolumeStream;
- private boolean mInitialDownMusicOnly;
-
- private IOnMediaKeyListener mOnMediaKeyListener;
- private int mOnMediaKeyListenerUid;
- private ICallback mCallback;
-
- public FullUserRecord(int fullUserId) {
- mFullUserId = fullUserId;
- mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
- // Restore the remembered media button receiver before the boot.
- String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver,
- Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
- if (mediaButtonReceiverInfo == null) {
- return;
- }
- String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
- if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
- return;
- }
- mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
- mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
- if (tokens.length == 3) {
- mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]);
- } else {
- mRestoredMediaButtonReceiverComponentType =
- getComponentType(mRestoredMediaButtonReceiver);
- }
- }
-
- public void destroySessionsForUserLocked(int userId) {
- List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
- for (MediaSessionRecord session : sessions) {
- MediaSessionService.this.destroySessionLocked(session);
- }
- }
-
- public void dumpLocked(PrintWriter pw, String prefix) {
- pw.print(prefix + "Record for full_user=" + mFullUserId);
- // Dump managed profile user ids associated with this user.
- int size = mFullUserIds.size();
- for (int i = 0; i < size; i++) {
- if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
- && mFullUserIds.valueAt(i) == mFullUserId) {
- pw.print(", profile_user=" + mFullUserIds.keyAt(i));
- }
- }
- pw.println();
- String indent = prefix + " ";
- pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
- pw.println(indent + "Volume key long-press listener package: " +
- getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
- pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
- pw.println(indent + "Media key listener package: " +
- getCallingPackageName(mOnMediaKeyListenerUid));
- pw.println(indent + "Callback: " + mCallback);
- pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
- pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
- pw.println(indent + "Restored MediaButtonReceiverComponentType: "
- + mRestoredMediaButtonReceiverComponentType);
- mPriorityStack.dump(pw, indent);
- pw.println(indent + "Session2Tokens:");
- for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
- List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
- if (list == null || list.size() == 0) {
- continue;
- }
- for (Session2Token token : list) {
- pw.println(indent + " " + token);
- }
- }
- }
-
- @Override
- public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
- MediaSessionRecord newMediaButtonSession) {
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
- }
- synchronized (mLock) {
- if (oldMediaButtonSession != null) {
- mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
- }
- if (newMediaButtonSession != null) {
- rememberMediaButtonReceiverLocked(newMediaButtonSession);
- mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
- }
- pushAddressedPlayerChangedLocked();
- }
- }
-
- // Remember media button receiver and keep it in the persistent storage.
- public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
- PendingIntent receiver = record.getMediaButtonReceiver();
- mLastMediaButtonReceiver = receiver;
- mRestoredMediaButtonReceiver = null;
- mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
-
- String mediaButtonReceiverInfo = "";
- if (receiver != null) {
- ComponentName component = receiver.getIntent().getComponent();
- if (component != null
- && record.getPackageName().equals(component.getPackageName())) {
- String componentName = component.flattenToString();
- int componentType = getComponentType(component);
- mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM,
- componentName, String.valueOf(record.getUserId()),
- String.valueOf(componentType));
- }
- }
- Settings.Secure.putStringForUser(mContentResolver,
- Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo,
- mFullUserId);
- }
-
- private void pushAddressedPlayerChangedLocked() {
- if (mCallback == null) {
- return;
- }
- try {
- MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
- if (mediaButtonSession != null) {
- mCallback.onAddressedPlayerChangedToMediaSession(
- new MediaSession.Token(mediaButtonSession.getControllerBinder()));
- } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
- mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
- mCurrentFullUserRecord.mLastMediaButtonReceiver
- .getIntent().getComponent());
- } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
- mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
- mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
- }
- }
-
- private MediaSessionRecord getMediaButtonSessionLocked() {
- return isGlobalPriorityActiveLocked()
- ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
- }
-
- private int getComponentType(@Nullable ComponentName componentName) {
- if (componentName == null) {
- return COMPONENT_TYPE_INVALID;
- }
- PackageManager pm = getContext().getPackageManager();
- try {
- ActivityInfo activityInfo = pm.getActivityInfo(componentName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.GET_ACTIVITIES);
- if (activityInfo != null) {
- return COMPONENT_TYPE_ACTIVITY;
- }
- } catch (NameNotFoundException e) {
- }
- try {
- ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.GET_SERVICES);
- if (serviceInfo != null) {
- return COMPONENT_TYPE_SERVICE;
- }
- } catch (NameNotFoundException e) {
- }
- // Pick legacy behavior for BroadcastReceiver or unknown.
- return COMPONENT_TYPE_BROADCAST;
- }
- }
-
- final class SessionsListenerRecord implements IBinder.DeathRecipient {
- public final IActiveSessionsListener listener;
- public final ComponentName componentName;
- public final int userId;
- public final int pid;
- public final int uid;
-
- public SessionsListenerRecord(IActiveSessionsListener listener,
- ComponentName componentName,
- int userId, int pid, int uid) {
- this.listener = listener;
- this.componentName = componentName;
- this.userId = userId;
- this.pid = pid;
- this.uid = uid;
- }
-
- @Override
- public void binderDied() {
- synchronized (mLock) {
- mSessionsListeners.remove(this);
- }
- }
- }
-
- final class Session2TokensListenerRecord implements IBinder.DeathRecipient {
- public final ISession2TokensListener listener;
- public final int userId;
-
- Session2TokensListenerRecord(ISession2TokensListener listener,
- int userId) {
- this.listener = listener;
- this.userId = userId;
- }
-
- @Override
- public void binderDied() {
- synchronized (mLock) {
- mSession2TokensListenerRecords.remove(this);
- }
- }
- }
-
- final class SettingsObserver extends ContentObserver {
- private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
- private SettingsObserver() {
- super(null);
- }
-
- private void observe() {
- mContentResolver.registerContentObserver(mSecureSettingsUri,
- false, this, USER_ALL);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- updateActiveSessionListeners();
- }
- }
-
- class SessionManagerImpl extends ISessionManager.Stub {
- private static final String EXTRA_WAKELOCK_ACQUIRED =
- "android.media.AudioService.WAKELOCK_ACQUIRED";
- private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
-
- private boolean mVoiceButtonDown = false;
- private boolean mVoiceButtonHandled = false;
-
- @Override
- public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
- int userId) throws RemoteException {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- enforcePackageName(packageName, uid);
- int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
- false /* allowAll */, true /* requireFull */, "createSession", packageName);
- if (cb == null) {
- throw new IllegalArgumentException("Controller callback cannot be null");
- }
- return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
- .getSessionBinder();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void notifySession2Created(Session2Token sessionToken) throws RemoteException {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- if (DEBUG) {
- Log.d(TAG, "Session2 is created " + sessionToken);
- }
- if (uid != sessionToken.getUid()) {
- throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
- + " but actually=" + sessionToken.getUid());
- }
- Controller2Callback callback = new Controller2Callback(sessionToken);
- // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
- // it's closed.
- // TODO: Keep controller as well for better readability
- // because the GC behavior isn't straightforward.
- MediaController2 controller = new MediaController2(getContext(), sessionToken,
- new HandlerExecutor(mHandler), callback);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public List<IBinder> getSessions(ComponentName componentName, int userId) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- try {
- int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
- ArrayList<IBinder> binders = new ArrayList<IBinder>();
- synchronized (mLock) {
- List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
- for (MediaSessionRecord record : records) {
- binders.add(record.getControllerBinder().asBinder());
- }
- }
- return binders;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public List<Session2Token> getSession2Tokens(int userId) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- try {
- // Check that they can make calls on behalf of the user and
- // get the final user id
- int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
- true /* allowAll */, true /* requireFull */, "getSession2Tokens",
- null /* optional packageName */);
- List<Session2Token> result;
- synchronized (mLock) {
- result = getSession2TokensLocked(resolvedUserId);
- }
- return result;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void addSessionsListener(IActiveSessionsListener listener,
- ComponentName componentName, int userId) throws RemoteException {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- try {
- int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
- synchronized (mLock) {
- int index = findIndexOfSessionsListenerLocked(listener);
- if (index != -1) {
- Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
- return;
- }
- SessionsListenerRecord record = new SessionsListenerRecord(listener,
- componentName, resolvedUserId, pid, uid);
- try {
- listener.asBinder().linkToDeath(record, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
- return;
- }
- mSessionsListeners.add(record);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void removeSessionsListener(IActiveSessionsListener listener)
- throws RemoteException {
- synchronized (mLock) {
- int index = findIndexOfSessionsListenerLocked(listener);
- if (index != -1) {
- SessionsListenerRecord record = mSessionsListeners.remove(index);
- try {
- record.listener.asBinder().unlinkToDeath(record, 0);
- } catch (Exception e) {
- // ignore exceptions, the record is being removed
- }
- }
- }
- }
-
- @Override
- public void addSession2TokensListener(ISession2TokensListener listener,
- int userId) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- try {
- // Check that they can make calls on behalf of the user and get the final user id.
- int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
- true /* allowAll */, true /* requireFull */, "addSession2TokensListener",
- null /* optional packageName */);
- synchronized (mLock) {
- int index = findIndexOfSession2TokensListenerLocked(listener);
- if (index >= 0) {
- Log.w(TAG, "addSession2TokensListener is already added, ignoring");
- return;
- }
- mSession2TokensListenerRecords.add(
- new Session2TokensListenerRecord(listener, resolvedUserId));
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void removeSession2TokensListener(ISession2TokensListener listener) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- try {
- synchronized (mLock) {
- int index = findIndexOfSession2TokensListenerLocked(listener);
- if (index >= 0) {
- Session2TokensListenerRecord listenerRecord =
- mSession2TokensListenerRecords.remove(index);
- try {
- listenerRecord.listener.asBinder().unlinkToDeath(listenerRecord, 0);
- } catch (Exception e) {
- // Ignore exception.
- }
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Handles the dispatching of the media button events to one of the
- * registered listeners, or if there was none, broadcast an
- * ACTION_MEDIA_BUTTON intent to the rest of the system.
- *
- * @param packageName The caller package
- * @param asSystemService {@code true} if the event sent to the session as if it was come
- * from the system service instead of the app process. This helps sessions to
- * distinguish between the key injection by the app and key events from the
- * hardware devices. Should be used only when the volume key events aren't handled
- * by foreground activity. {@code false} otherwise to tell session about the real
- * caller.
- * @param keyEvent a non-null KeyEvent whose key code is one of the
- * supported media buttons
- * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
- * while this key event is dispatched.
- */
- @Override
- public void dispatchMediaKeyEvent(String packageName, boolean asSystemService,
- KeyEvent keyEvent, boolean needWakeLock) {
- if (keyEvent == null || !KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
- Log.w(TAG, "Attempted to dispatch null or non-media key event.");
- return;
- }
-
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- if (DEBUG) {
- Log.d(TAG, "dispatchMediaKeyEvent, pkg=" + packageName + " pid=" + pid
- + ", uid=" + uid + ", asSystem=" + asSystemService + ", event="
- + keyEvent);
- }
- if (!isUserSetupComplete()) {
- // Global media key handling can have the side-effect of starting new
- // activities which is undesirable while setup is in progress.
- Slog.i(TAG, "Not dispatching media key event because user "
- + "setup is in progress.");
- return;
- }
-
- synchronized (mLock) {
- boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
- if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
- // Prevent dispatching key event through reflection while the global
- // priority session is active.
- Slog.i(TAG, "Only the system can dispatch media key event "
- + "to the global priority session.");
- return;
- }
- if (!isGlobalPriorityActive) {
- if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Send " + keyEvent + " to the media key listener");
- }
- try {
- mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
- new MediaKeyListenerResultReceiver(packageName, pid, uid,
- asSystemService, keyEvent, needWakeLock));
- return;
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to send " + keyEvent
- + " to the media key listener");
- }
- }
- }
- if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
- handleVoiceKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
- needWakeLock);
- } else {
- dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
- keyEvent, needWakeLock);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setCallback(ICallback callback) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
- throw new SecurityException("Only Bluetooth service processes can set"
- + " Callback");
- }
- synchronized (mLock) {
- int userId = UserHandle.getUserId(uid);
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user == null || user.mFullUserId != userId) {
- Log.w(TAG, "Only the full user can set the callback"
- + ", userId=" + userId);
- return;
- }
- user.mCallback = callback;
- Log.d(TAG, "The callback " + user.mCallback
- + " is set by " + getCallingPackageName(uid));
- if (user.mCallback == null) {
- return;
- }
- try {
- user.mCallback.asBinder().linkToDeath(
- new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- synchronized (mLock) {
- user.mCallback = null;
- }
- }
- }, 0);
- user.pushAddressedPlayerChangedLocked();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to set callback", e);
- user.mCallback = null;
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
- if (getContext().checkPermission(
- android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
- " permission.");
- }
-
- synchronized (mLock) {
- int userId = UserHandle.getUserId(uid);
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user == null || user.mFullUserId != userId) {
- Log.w(TAG, "Only the full user can set the volume key long-press listener"
- + ", userId=" + userId);
- return;
- }
- if (user.mOnVolumeKeyLongPressListener != null &&
- user.mOnVolumeKeyLongPressListenerUid != uid) {
- Log.w(TAG, "The volume key long-press listener cannot be reset"
- + " by another app , mOnVolumeKeyLongPressListener="
- + user.mOnVolumeKeyLongPressListenerUid
- + ", uid=" + uid);
- return;
- }
-
- user.mOnVolumeKeyLongPressListener = listener;
- user.mOnVolumeKeyLongPressListenerUid = uid;
-
- Log.d(TAG, "The volume key long-press listener "
- + listener + " is set by " + getCallingPackageName(uid));
-
- if (user.mOnVolumeKeyLongPressListener != null) {
- try {
- user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
- new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- synchronized (mLock) {
- user.mOnVolumeKeyLongPressListener = null;
- }
- }
- }, 0);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to set death recipient "
- + user.mOnVolumeKeyLongPressListener);
- user.mOnVolumeKeyLongPressListener = null;
- }
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- // Enforce SET_MEDIA_KEY_LISTENER permission.
- if (getContext().checkPermission(
- android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
- " permission.");
- }
-
- synchronized (mLock) {
- int userId = UserHandle.getUserId(uid);
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user == null || user.mFullUserId != userId) {
- Log.w(TAG, "Only the full user can set the media key listener"
- + ", userId=" + userId);
- return;
- }
- if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
- Log.w(TAG, "The media key listener cannot be reset by another app. "
- + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
- + ", uid=" + uid);
- return;
- }
-
- user.mOnMediaKeyListener = listener;
- user.mOnMediaKeyListenerUid = uid;
-
- Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
- + " is set by " + getCallingPackageName(uid));
-
- if (user.mOnMediaKeyListener != null) {
- try {
- user.mOnMediaKeyListener.asBinder().linkToDeath(
- new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- synchronized (mLock) {
- user.mOnMediaKeyListener = null;
- }
- }
- }, 0);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
- user.mOnMediaKeyListener = null;
- }
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Handles the dispatching of the volume button events to one of the
- * registered listeners. If there's a volume key long-press listener and
- * there's no active global priority session, long-pressess will be sent to the
- * long-press listener instead of adjusting volume.
- *
- * @param packageName The caller's package name, obtained by Context#getPackageName()
- * @param opPackageName The caller's op package name, obtained by Context#getOpPackageName()
- * @param asSystemService {@code true} if the event sent to the session as if it was come
- * from the system service instead of the app process. This helps sessions to
- * distinguish between the key injection by the app and key events from the
- * hardware devices. Should be used only when the volume key events aren't handled
- * by foreground activity. {@code false} otherwise to tell session about the real
- * caller.
- * @param keyEvent a non-null KeyEvent whose key code is one of the
- * {@link KeyEvent#KEYCODE_VOLUME_UP},
- * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
- * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
- * @param stream stream type to adjust volume.
- * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
- */
- @Override
- public void dispatchVolumeKeyEvent(String packageName, String opPackageName,
- boolean asSystemService, KeyEvent keyEvent, int stream, boolean musicOnly) {
- if (keyEvent == null ||
- (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
- && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
- && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
- Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
- return;
- }
-
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + ", pid=" + pid + ", uid="
- + uid + ", asSystem=" + asSystemService + ", event=" + keyEvent);
- }
-
- try {
- synchronized (mLock) {
- if (isGlobalPriorityActiveLocked()
- || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
- dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
- asSystemService, keyEvent, stream, musicOnly);
- } else {
- // TODO: Consider the case when both volume up and down keys are pressed
- // at the same time.
- if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
- if (keyEvent.getRepeatCount() == 0) {
- // Keeps the copy of the KeyEvent because it can be reused.
- mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
- KeyEvent.obtain(keyEvent);
- mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
- mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(
- MessageHandler.MSG_VOLUME_INITIAL_DOWN,
- mCurrentFullUserRecord.mFullUserId, 0),
- mLongPressTimeout);
- }
- if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
- mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
- if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
- dispatchVolumeKeyLongPressLocked(
- mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
- // Mark that the key is already handled.
- mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
- }
- dispatchVolumeKeyLongPressLocked(keyEvent);
- }
- } else { // if up
- mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
- if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
- && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
- .getDownTime() == keyEvent.getDownTime()) {
- // Short-press. Should change volume.
- dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
- asSystemService,
- mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
- mCurrentFullUserRecord.mInitialDownVolumeStream,
- mCurrentFullUserRecord.mInitialDownMusicOnly);
- dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
- asSystemService, keyEvent, stream, musicOnly);
- } else {
- dispatchVolumeKeyLongPressLocked(keyEvent);
- }
- }
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void dispatchVolumeKeyEventLocked(String packageName, String opPackageName, int pid,
- int uid, boolean asSystemService, KeyEvent keyEvent, int stream,
- boolean musicOnly) {
- boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
- boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
- int direction = 0;
- boolean isMute = false;
- switch (keyEvent.getKeyCode()) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- direction = AudioManager.ADJUST_RAISE;
- break;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- direction = AudioManager.ADJUST_LOWER;
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- isMute = true;
- break;
- }
- if (down || up) {
- int flags = AudioManager.FLAG_FROM_KEY;
- if (musicOnly) {
- // This flag is used when the screen is off to only affect active media.
- flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
- } else {
- // These flags are consistent with the home screen
- if (up) {
- flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
- } else {
- flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
- }
- }
- if (direction != 0) {
- // If this is action up we want to send a beep for non-music events
- if (up) {
- direction = 0;
- }
- dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
- asSystemService, stream, direction, flags);
- } else if (isMute) {
- if (down && keyEvent.getRepeatCount() == 0) {
- dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
- asSystemService, stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
- }
- }
- }
- }
-
- @Override
- public void dispatchAdjustVolume(String packageName, String opPackageName,
- int suggestedStream, int delta, int flags) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid, false,
- suggestedStream, delta, flags);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setRemoteVolumeController(IRemoteVolumeController rvc) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- enforceSystemUiPermission("listen for volume changes", pid, uid);
- mRvc = rvc;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public boolean isGlobalPriorityActive() {
- synchronized (mLock) {
- return isGlobalPriorityActiveLocked();
- }
- }
-
- @Override
- public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
-
- pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
- pw.println();
-
- synchronized (mLock) {
- pw.println(mSessionsListeners.size() + " sessions listeners.");
- pw.println("Global priority session is " + mGlobalPrioritySession);
- if (mGlobalPrioritySession != null) {
- mGlobalPrioritySession.dump(pw, " ");
- }
- pw.println("User Records:");
- int count = mUserRecords.size();
- for (int i = 0; i < count; i++) {
- mUserRecords.valueAt(i).dumpLocked(pw, "");
- }
- mAudioPlayerStateMonitor.dump(getContext(), pw, "");
- }
- }
-
- /**
- * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
- * permission or an enabled notification listener)
- *
- * @param controllerPackageName package name of the controller app
- * @param controllerPid pid of the controller app
- * @param controllerUid uid of the controller app
- */
- @Override
- public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
- throws RemoteException {
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- // Don't perform sanity check between controllerPackageName and controllerUid.
- // When an (activity|service) runs on the another apps process by specifying
- // android:process in the AndroidManifest.xml, then PID and UID would have the
- // running process' information instead of the (activity|service) that has created
- // MediaController.
- // Note that we can use Context#getOpPackageName() instead of
- // Context#getPackageName() for getting package name that matches with the PID/UID,
- // but it doesn't tell which package has created the MediaController, so useless.
- return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
- controllerPid, controllerUid);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- // For MediaSession
- private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
- final int uid) {
- String packageName = null;
- if (componentName != null) {
- // If they gave us a component name verify they own the
- // package
- packageName = componentName.getPackageName();
- enforcePackageName(packageName, uid);
- }
- // Check that they can make calls on behalf of the user and
- // get the final user id
- int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
- true /* allowAll */, true /* requireFull */, "getSessions", packageName);
- // Check if they have the permissions or their component is
- // enabled for the user they're calling from.
- enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
- return resolvedUserId;
- }
-
- private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
- int pid, int uid) throws RemoteException {
- // Allow API calls from the System UI
- if (isCurrentVolumeController(pid, uid)) {
- return true;
- }
-
- // Check if it's system server or has MEDIA_CONTENT_CONTROL.
- // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
- // check here.
- if (uid == Process.SYSTEM_UID || getContext().checkPermission(
- android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
- } else if (DEBUG) {
- Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
- }
-
- // You may not access another user's content as an enabled listener.
- final int userId = UserHandle.getUserId(uid);
- if (resolvedUserId != userId) {
- return false;
- }
-
- // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
- // String pkgName) to notification team for optimization
- final List<ComponentName> enabledNotificationListeners =
- mNotificationManager.getEnabledNotificationListeners(userId);
- if (enabledNotificationListeners != null) {
- for (int i = 0; i < enabledNotificationListeners.size(); i++) {
- if (TextUtils.equals(packageName,
- enabledNotificationListeners.get(i).getPackageName())) {
- return true;
- }
- }
- }
- if (DEBUG) {
- Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
- + "notification listener");
- }
- return false;
- }
-
- private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
- int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
- MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
- : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
-
- boolean preferSuggestedStream = false;
- if (isValidLocalStreamType(suggestedStream)
- && AudioSystem.isStreamActive(suggestedStream, 0)) {
- preferSuggestedStream = true;
- }
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
- + flags + ", suggestedStream=" + suggestedStream
- + ", preferSuggestedStream=" + preferSuggestedStream);
- }
- if (session == null || preferSuggestedStream) {
- if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
- && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
- if (DEBUG) {
- Log.d(TAG, "No active session to adjust, skipping media only volume event");
- }
- return;
- }
-
- // Execute mAudioService.adjustSuggestedStreamVolume() on
- // handler thread of MediaSessionService.
- // This will release the MediaSessionService.mLock sooner and avoid
- // a potential deadlock between MediaSessionService.mLock and
- // ActivityManagerService lock.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- final String callingOpPackageName;
- final int callingUid;
- if (asSystemService) {
- callingOpPackageName = getContext().getOpPackageName();
- callingUid = Process.myUid();
- } else {
- callingOpPackageName = opPackageName;
- callingUid = uid;
- }
- try {
- mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream,
- direction, flags, callingOpPackageName, callingUid);
- } catch (SecurityException | IllegalArgumentException e) {
- Log.e(TAG, "Cannot adjust volume: direction=" + direction
- + ", suggestedStream=" + suggestedStream + ", flags=" + flags
- + ", packageName=" + packageName + ", uid=" + uid
- + ", asSystemService=" + asSystemService, e);
- }
- }
- });
- } else {
- session.adjustVolume(packageName, opPackageName, pid, uid, null, asSystemService,
- direction, flags, true);
- }
- }
-
- private void handleVoiceKeyEventLocked(String packageName, int pid, int uid,
- boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
- int action = keyEvent.getAction();
- boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
- if (action == KeyEvent.ACTION_DOWN) {
- if (keyEvent.getRepeatCount() == 0) {
- mVoiceButtonDown = true;
- mVoiceButtonHandled = false;
- } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
- mVoiceButtonHandled = true;
- startVoiceInput(needWakeLock);
- }
- } else if (action == KeyEvent.ACTION_UP) {
- if (mVoiceButtonDown) {
- mVoiceButtonDown = false;
- if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
- // Resend the down then send this event through
- KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
- dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
- downEvent, needWakeLock);
- dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
- keyEvent, needWakeLock);
- }
- }
- }
- }
-
- private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
- boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
- MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
- if (session != null) {
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Sending " + keyEvent + " to " + session);
- }
- if (needWakeLock) {
- mKeyEventReceiver.aquireWakeLockLocked();
- }
- // If we don't need a wakelock use -1 as the id so we won't release it later.
- session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
- needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
- mKeyEventReceiver);
- if (mCurrentFullUserRecord.mCallback != null) {
- try {
- mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
- keyEvent, new MediaSession.Token(session.getControllerBinder()));
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to send callback", e);
- }
- }
- } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
- || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
- if (needWakeLock) {
- mKeyEventReceiver.aquireWakeLockLocked();
- }
- Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- // TODO: Find a way to also send PID/UID in secure way.
- String callerPackageName =
- (asSystemService) ? getContext().getPackageName() : packageName;
- mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
- try {
- if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
- PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Sending " + keyEvent
- + " to the last known PendingIntent " + receiver);
- }
- receiver.send(getContext(),
- needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
- mediaButtonIntent, mKeyEventReceiver, mHandler);
- if (mCurrentFullUserRecord.mCallback != null) {
- ComponentName componentName = mCurrentFullUserRecord
- .mLastMediaButtonReceiver.getIntent().getComponent();
- if (componentName != null) {
- mCurrentFullUserRecord.mCallback
- .onMediaKeyEventDispatchedToMediaButtonReceiver(
- keyEvent, componentName);
- }
- }
- } else {
- ComponentName receiver =
- mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
- int componentType = mCurrentFullUserRecord
- .mRestoredMediaButtonReceiverComponentType;
- UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord
- .mRestoredMediaButtonReceiverUserId);
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
- + receiver + ", type=" + componentType);
- }
- mediaButtonIntent.setComponent(receiver);
- try {
- switch (componentType) {
- case FullUserRecord.COMPONENT_TYPE_ACTIVITY:
- getContext().startActivityAsUser(mediaButtonIntent, userHandle);
- break;
- case FullUserRecord.COMPONENT_TYPE_SERVICE:
- getContext().startForegroundServiceAsUser(mediaButtonIntent,
- userHandle);
- break;
- default:
- // Legacy behavior for other cases.
- getContext().sendBroadcastAsUser(mediaButtonIntent, userHandle);
- }
- } catch (Exception e) {
- Log.w(TAG, "Error sending media button to the restored intent "
- + receiver + ", type=" + componentType, e);
- }
- if (mCurrentFullUserRecord.mCallback != null) {
- mCurrentFullUserRecord.mCallback
- .onMediaKeyEventDispatchedToMediaButtonReceiver(
- keyEvent, receiver);
- }
- }
- } catch (CanceledException e) {
- Log.i(TAG, "Error sending key event to media button receiver "
- + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to send callback", e);
- }
- }
- }
-
- private void startVoiceInput(boolean needWakeLock) {
- Intent voiceIntent = null;
- // select which type of search to launch:
- // - screen on and device unlocked: action is ACTION_WEB_SEARCH
- // - device locked or screen off: action is
- // ACTION_VOICE_SEARCH_HANDS_FREE
- // with EXTRA_SECURE set to true if the device is securely locked
- PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
- boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
- if (!isLocked && pm.isScreenOn()) {
- voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
- Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
- } else {
- voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
- isLocked && mKeyguardManager.isKeyguardSecure());
- Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
- }
- // start the search activity
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- }
- try {
- if (voiceIntent != null) {
- voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
- getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
- }
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "No activity for search: " + e);
- } finally {
- if (needWakeLock) {
- mMediaEventWakeLock.release();
- }
- }
- }
-
- private boolean isVoiceKey(int keyCode) {
- return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
- || (!mHasFeatureLeanback && keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
- }
-
- private boolean isUserSetupComplete() {
- return Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
- }
-
- // we only handle public stream types, which are 0-5
- private boolean isValidLocalStreamType(int streamType) {
- return streamType >= AudioManager.STREAM_VOICE_CALL
- && streamType <= AudioManager.STREAM_NOTIFICATION;
- }
-
- private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
- private final String mPackageName;
- private final int mPid;
- private final int mUid;
- private final boolean mAsSystemService;
- private final KeyEvent mKeyEvent;
- private final boolean mNeedWakeLock;
- private boolean mHandled;
-
- private MediaKeyListenerResultReceiver(String packageName, int pid, int uid,
- boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
- super(mHandler);
- mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
- mPackageName = packageName;
- mPid = pid;
- mUid = uid;
- mAsSystemService = asSystemService;
- mKeyEvent = keyEvent;
- mNeedWakeLock = needWakeLock;
- }
-
- @Override
- public void run() {
- Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
- dispatchMediaKeyEvent();
- }
-
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
- mHandled = true;
- mHandler.removeCallbacks(this);
- return;
- }
- dispatchMediaKeyEvent();
- }
-
- private void dispatchMediaKeyEvent() {
- if (mHandled) {
- return;
- }
- mHandled = true;
- mHandler.removeCallbacks(this);
- synchronized (mLock) {
- if (!isGlobalPriorityActiveLocked()
- && isVoiceKey(mKeyEvent.getKeyCode())) {
- handleVoiceKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
- mKeyEvent, mNeedWakeLock);
- } else {
- dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
- mKeyEvent, mNeedWakeLock);
- }
- }
- }
- }
-
- private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
-
- class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
- PendingIntent.OnFinished {
- private final Handler mHandler;
- private int mRefCount = 0;
- private int mLastTimeoutId = 0;
-
- public KeyEventWakeLockReceiver(Handler handler) {
- super(handler);
- mHandler = handler;
- }
-
- public void onTimeout() {
- synchronized (mLock) {
- if (mRefCount == 0) {
- // We've already released it, so just return
- return;
- }
- mLastTimeoutId++;
- mRefCount = 0;
- releaseWakeLockLocked();
- }
- }
-
- public void aquireWakeLockLocked() {
- if (mRefCount == 0) {
- mMediaEventWakeLock.acquire();
- }
- mRefCount++;
- mHandler.removeCallbacks(this);
- mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
-
- }
-
- @Override
- public void run() {
- onTimeout();
- }
-
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode < mLastTimeoutId) {
- // Ignore results from calls that were before the last
- // timeout, just in case.
- return;
- } else {
- synchronized (mLock) {
- if (mRefCount > 0) {
- mRefCount--;
- if (mRefCount == 0) {
- releaseWakeLockLocked();
- }
- }
- }
- }
- }
-
- private void releaseWakeLockLocked() {
- mMediaEventWakeLock.release();
- mHandler.removeCallbacks(this);
- }
-
- @Override
- public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
- String resultData, Bundle resultExtras) {
- onReceiveResult(resultCode, null);
- }
- };
-
- BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent == null) {
- return;
- }
- Bundle extras = intent.getExtras();
- if (extras == null) {
- return;
- }
- synchronized (mLock) {
- if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
- && mMediaEventWakeLock.isHeld()) {
- mMediaEventWakeLock.release();
- }
- }
- }
- };
- }
-
- final class MessageHandler extends Handler {
- private static final int MSG_SESSIONS_CHANGED = 1;
- private static final int MSG_VOLUME_INITIAL_DOWN = 2;
- private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SESSIONS_CHANGED:
- pushSessionsChanged((int) msg.obj);
- break;
- case MSG_VOLUME_INITIAL_DOWN:
- synchronized (mLock) {
- FullUserRecord user = mUserRecords.get((int) msg.arg1);
- if (user != null && user.mInitialDownVolumeKeyEvent != null) {
- dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
- // Mark that the key is already handled.
- user.mInitialDownVolumeKeyEvent = null;
- }
- }
- break;
- }
- }
-
- public void postSessionsChanged(int userId) {
- // Use object instead of the arguments when posting message to remove pending requests.
- Integer userIdInteger = mIntegerCache.get(userId);
- if (userIdInteger == null) {
- userIdInteger = Integer.valueOf(userId);
- mIntegerCache.put(userId, userIdInteger);
- }
- removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
- obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
- }
- }
-
- private class Controller2Callback extends MediaController2.ControllerCallback {
- private final Session2Token mToken;
-
- Controller2Callback(Session2Token token) {
- mToken = token;
- }
-
- @Override
- public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mToken.getUid());
- mSession2TokensPerUser.get(userId).add(mToken);
- pushSession2TokensChangedLocked(userId);
- }
- }
-
- @Override
- public void onDisconnected(MediaController2 controller) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mToken.getUid());
- mSession2TokensPerUser.get(userId).remove(mToken);
- pushSession2TokensChangedLocked(userId);
- }
- }
+ abstract static class ServiceImpl {
+ public abstract void onStart();
+ public abstract void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session);
+ public abstract void onSessionPlaystateChanged(
+ MediaSessionRecord record, int oldState, int newState);
+ public abstract void onSessionPlaybackTypeChanged(MediaSessionRecord record);
+ public abstract void onStartUser(int userId);
+ public abstract void onSwitchUser(int userId);
+ public abstract void monitor();
+ public abstract void onMediaButtonReceiverChanged(MediaSessionRecord record);
+ protected abstract void enforcePhoneStatePermission(int pid, int uid);
+ abstract void updateSession(MediaSessionRecord record);
+ abstract void setGlobalPrioritySession(MediaSessionRecord record);
+ abstract List<Session2Token> getSession2TokensLocked(int userId);
+ abstract void onStopUser(int userId);
+ abstract void sessionDied(MediaSessionRecord session);
+ abstract void destroySession(MediaSessionRecord session);
+ abstract void pushSession2TokensChangedLocked(int userId);
+ abstract Context getContext();
+ abstract IBinder getServiceBinder();
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
new file mode 100644
index 0000000..f374c6d
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -0,0 +1,2142 @@
+/*
+ * 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.media;
+
+import static android.os.UserHandle.USER_ALL;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.INotificationManager;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.media.AudioManagerInternal;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioSystem;
+import android.media.IAudioService;
+import android.media.IRemoteVolumeController;
+import android.media.MediaController2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.media.session.IActiveSessionsListener;
+import android.media.session.ICallback;
+import android.media.session.IOnMediaKeyListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
+import android.media.session.ISession;
+import android.media.session.ISession2TokensListener;
+import android.media.session.ISessionManager;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.SessionCallbackLink;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.speech.RecognizerIntent;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * System implementation of MediaSessionManager
+ */
+public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl {
+ private static final String TAG = "MediaSessionService";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // Leave log for key event always.
+ private static final boolean DEBUG_KEY_EVENT = true;
+
+ private static final int WAKELOCK_TIMEOUT = 5000;
+ private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
+
+ private final Context mContext;
+ private final SessionManagerImpl mSessionManagerImpl;
+ private final MessageHandler mHandler = new MessageHandler();
+ private final PowerManager.WakeLock mMediaEventWakeLock;
+ private final int mLongPressTimeout;
+ private final INotificationManager mNotificationManager;
+ private final Object mLock = new Object();
+ // Keeps the full user id for each user.
+ @GuardedBy("mLock")
+ private final SparseIntArray mFullUserIds = new SparseIntArray();
+ @GuardedBy("mLock")
+ private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
+ @GuardedBy("mLock")
+ private final ArrayList<SessionsListenerRecord> mSessionsListeners =
+ new ArrayList<SessionsListenerRecord>();
+ // Map user id as index to list of Session2Tokens
+ // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
+ // one place.
+ @GuardedBy("mLock")
+ private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
+ @GuardedBy("mLock")
+ private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
+ new ArrayList<>();
+
+ private KeyguardManager mKeyguardManager;
+ private IAudioService mAudioService;
+ private AudioManagerInternal mAudioManagerInternal;
+ private ActivityManager mActivityManager;
+ private ContentResolver mContentResolver;
+ private SettingsObserver mSettingsObserver;
+ private boolean mHasFeatureLeanback;
+
+ // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
+ // It's always not null after the MediaSessionService is started.
+ private FullUserRecord mCurrentFullUserRecord;
+ private MediaSessionRecord mGlobalPrioritySession;
+ private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
+
+ // Used to notify system UI when remote volume was changed. TODO find a
+ // better way to handle this.
+ private IRemoteVolumeController mRvc;
+
+ public MediaSessionServiceImpl(Context context) {
+ mContext = context;
+ mSessionManagerImpl = new SessionManagerImpl();
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
+ mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
+ mNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ IBinder getServiceBinder() {
+ return mSessionManagerImpl;
+ }
+
+ @Override
+ public void onStart() {
+ mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mAudioService = getAudioService();
+ mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+ mActivityManager =
+ (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+ mAudioPlayerStateMonitor.registerListener(
+ (config, isRemoved) -> {
+ if (isRemoved || !config.isActive() || config.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ return;
+ }
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(
+ UserHandle.getUserId(config.getClientUid()));
+ if (user != null) {
+ user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+ }
+ }
+ }, null /* handler */);
+ mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
+ mContentResolver = mContext.getContentResolver();
+ mSettingsObserver = new SettingsObserver();
+ mSettingsObserver.observe();
+ mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LEANBACK);
+
+ updateUser();
+ }
+
+ private IAudioService getAudioService() {
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ return IAudioService.Stub.asInterface(b);
+ }
+
+ private boolean isGlobalPriorityActiveLocked() {
+ return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
+ }
+
+ @Override
+ public void updateSession(MediaSessionRecord record) {
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+ if (user == null) {
+ Log.w(TAG, "Unknown session updated. Ignoring.");
+ return;
+ }
+ if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
+ }
+ user.pushAddressedPlayerChangedLocked();
+ } else {
+ if (!user.mPriorityStack.contains(record)) {
+ Log.w(TAG, "Unknown session updated. Ignoring.");
+ return;
+ }
+ user.mPriorityStack.onSessionStateChange(record);
+ }
+ mHandler.postSessionsChanged(record.getUserId());
+ }
+ }
+
+ @Override
+ public void setGlobalPrioritySession(MediaSessionRecord record) {
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+ if (mGlobalPrioritySession != record) {
+ Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
+ + " to " + record);
+ mGlobalPrioritySession = record;
+ if (user != null && user.mPriorityStack.contains(record)) {
+ // Handle the global priority session separately.
+ // Otherwise, it can be the media button session regardless of the active state
+ // because it or other system components might have been the lastly played media
+ // app.
+ user.mPriorityStack.removeSession(record);
+ }
+ }
+ }
+ }
+
+ private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
+ List<MediaSessionRecord> records = new ArrayList<>();
+ if (userId == USER_ALL) {
+ int size = mUserRecords.size();
+ for (int i = 0; i < size; i++) {
+ records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
+ }
+ } else {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.w(TAG, "getSessions failed. Unknown user " + userId);
+ return records;
+ }
+ records.addAll(user.mPriorityStack.getActiveSessions(userId));
+ }
+
+ // Return global priority session at the first whenever it's asked.
+ if (isGlobalPriorityActiveLocked()
+ && (userId == USER_ALL || userId == mGlobalPrioritySession.getUserId())) {
+ records.add(0, mGlobalPrioritySession);
+ }
+ return records;
+ }
+
+ List<Session2Token> getSession2TokensLocked(int userId) {
+ List<Session2Token> list = new ArrayList<>();
+ if (userId == USER_ALL) {
+ for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+ list.addAll(mSession2TokensPerUser.valueAt(i));
+ }
+ } else {
+ list.addAll(mSession2TokensPerUser.get(userId));
+ }
+ return list;
+ }
+
+ /**
+ * Tells the system UI that volume has changed on an active remote session.
+ */
+ public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
+ if (mRvc == null || !session.isActive()) {
+ return;
+ }
+ try {
+ mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Error sending volume change to system UI.", e);
+ }
+ }
+
+ @Override
+ public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+ if (user == null || !user.mPriorityStack.contains(record)) {
+ Log.d(TAG, "Unknown session changed playback state. Ignoring.");
+ return;
+ }
+ user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
+ }
+ }
+
+ @Override
+ public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+ if (user == null || !user.mPriorityStack.contains(record)) {
+ Log.d(TAG, "Unknown session changed playback type. Ignoring.");
+ return;
+ }
+ pushRemoteVolumeUpdateLocked(record.getUserId());
+ }
+ }
+
+ @Override
+ public void onStartUser(int userId) {
+ if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
+ updateUser();
+ }
+
+ @Override
+ public void onSwitchUser(int userId) {
+ if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
+ updateUser();
+ }
+
+ // Called when the user with the userId is removed.
+ @Override
+ public void onStopUser(int userId) {
+ if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
+ synchronized (mLock) {
+ // TODO: Also handle removing user in updateUser() because adding/switching user is
+ // handled in updateUser().
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user != null) {
+ if (user.mFullUserId == userId) {
+ user.destroySessionsForUserLocked(USER_ALL);
+ mUserRecords.remove(userId);
+ } else {
+ user.destroySessionsForUserLocked(userId);
+ }
+ }
+ mSession2TokensPerUser.remove(userId);
+ updateUser();
+ }
+ }
+
+ @Override
+ public void monitor() {
+ synchronized (mLock) {
+ // Check for deadlock
+ }
+ }
+
+ protected void enforcePhoneStatePermission(int pid, int uid) {
+ if (mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
+ }
+ }
+
+ void sessionDied(MediaSessionRecord session) {
+ synchronized (mLock) {
+ destroySessionLocked(session);
+ }
+ }
+
+ void destroySession(MediaSessionRecord session) {
+ synchronized (mLock) {
+ destroySessionLocked(session);
+ }
+ }
+
+ private void updateUser() {
+ synchronized (mLock) {
+ UserManager manager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mFullUserIds.clear();
+ List<UserInfo> allUsers = manager.getUsers();
+ if (allUsers != null) {
+ for (UserInfo userInfo : allUsers) {
+ if (userInfo.isManagedProfile()) {
+ mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
+ } else {
+ mFullUserIds.put(userInfo.id, userInfo.id);
+ if (mUserRecords.get(userInfo.id) == null) {
+ mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
+ }
+ }
+ if (mSession2TokensPerUser.get(userInfo.id) == null) {
+ mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
+ }
+ }
+ }
+ // Ensure that the current full user exists.
+ int currentFullUserId = ActivityManager.getCurrentUser();
+ mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
+ if (mCurrentFullUserRecord == null) {
+ Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
+ mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
+ mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
+ if (mSession2TokensPerUser.get(currentFullUserId) == null) {
+ mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
+ }
+ }
+ mFullUserIds.put(currentFullUserId, currentFullUserId);
+ }
+ }
+
+ private void updateActiveSessionListeners() {
+ synchronized (mLock) {
+ for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
+ SessionsListenerRecord listener = mSessionsListeners.get(i);
+ try {
+ enforceMediaPermissions(listener.componentName, listener.pid, listener.uid,
+ listener.userId);
+ } catch (SecurityException e) {
+ Log.i(TAG, "ActiveSessionsListener " + listener.componentName
+ + " is no longer authorized. Disconnecting.");
+ mSessionsListeners.remove(i);
+ try {
+ listener.listener
+ .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
+ } catch (Exception e1) {
+ // ignore
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * When a session is removed several things need to happen.
+ * 1. We need to remove it from the relevant user.
+ * 2. We need to remove it from the priority stack.
+ * 3. We need to remove it from all sessions.
+ * 4. If this is the system priority session we need to clear it.
+ * 5. We need to unlink to death from the cb binder
+ * 6. We need to tell the session to do any final cleanup (onDestroy)
+ */
+ private void destroySessionLocked(MediaSessionRecord session) {
+ if (DEBUG) {
+ Log.d(TAG, "Destroying " + session);
+ }
+ FullUserRecord user = getFullUserRecordLocked(session.getUserId());
+ if (mGlobalPrioritySession == session) {
+ mGlobalPrioritySession = null;
+ if (session.isActive() && user != null) {
+ user.pushAddressedPlayerChangedLocked();
+ }
+ } else {
+ if (user != null) {
+ user.mPriorityStack.removeSession(session);
+ }
+ }
+
+ try {
+ session.getCallback().getBinder().unlinkToDeath(session, 0);
+ } catch (Exception e) {
+ // ignore exceptions while destroying a session.
+ }
+ session.onDestroy();
+ mHandler.postSessionsChanged(session.getUserId());
+ }
+
+ private void enforcePackageName(String packageName, int uid) {
+ if (TextUtils.isEmpty(packageName)) {
+ throw new IllegalArgumentException("packageName may not be empty");
+ }
+ String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ final int packageCount = packages.length;
+ for (int i = 0; i < packageCount; i++) {
+ if (packageName.equals(packages[i])) {
+ return;
+ }
+ }
+ throw new IllegalArgumentException("packageName is not owned by the calling process");
+ }
+
+ /**
+ * Checks a caller's authorization to register an IRemoteControlDisplay.
+ * Authorization is granted if one of the following is true:
+ * <ul>
+ * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
+ * permission</li>
+ * <li>the caller's listener is one of the enabled notification listeners
+ * for the caller's user</li>
+ * </ul>
+ */
+ private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
+ int resolvedUserId) {
+ if (isCurrentVolumeController(pid, uid)) return;
+ if (mContext
+ .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
+ != PackageManager.PERMISSION_GRANTED
+ && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
+ resolvedUserId)) {
+ throw new SecurityException("Missing permission to control media.");
+ }
+ }
+
+ private boolean isCurrentVolumeController(int pid, int uid) {
+ return mContext.checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ pid, uid) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void enforceSystemUiPermission(String action, int pid, int uid) {
+ if (!isCurrentVolumeController(pid, uid)) {
+ throw new SecurityException("Only system ui may " + action);
+ }
+ }
+
+ /**
+ * This checks if the component is an enabled notification listener for the
+ * specified user. Enabled components may only operate on behalf of the user
+ * they're running as.
+ *
+ * @param compName The component that is enabled.
+ * @param userId The user id of the caller.
+ * @param forUserId The user id they're making the request on behalf of.
+ * @return True if the component is enabled, false otherwise
+ */
+ private boolean isEnabledNotificationListener(ComponentName compName, int userId,
+ int forUserId) {
+ if (userId != forUserId) {
+ // You may not access another user's content as an enabled listener.
+ return false;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Checking if enabled notification listener " + compName);
+ }
+ if (compName != null) {
+ try {
+ return mNotificationManager.isNotificationListenerAccessGrantedForUser(
+ compName, userId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
+ }
+ }
+ return false;
+ }
+
+ private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
+ String callerPackageName, SessionCallbackLink cb, String tag) throws RemoteException {
+ synchronized (mLock) {
+ return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
+ }
+ }
+
+ /*
+ * When a session is created the following things need to happen.
+ * 1. Its callback binder needs a link to death
+ * 2. It needs to be added to all sessions.
+ * 3. It needs to be added to the priority stack.
+ * 4. It needs to be added to the relevant user record.
+ */
+ private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
+ String callerPackageName, SessionCallbackLink cb, String tag) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.wtf(TAG, "Request from invalid user: " + userId);
+ throw new RuntimeException("Session request from invalid user.");
+ }
+
+ final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
+ callerPackageName, cb, tag, this, mHandler.getLooper());
+ try {
+ cb.getBinder().linkToDeath(session, 0);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Media Session owner died prematurely.", e);
+ }
+
+ user.mPriorityStack.addSession(session);
+ mHandler.postSessionsChanged(userId);
+
+ if (DEBUG) {
+ Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
+ }
+ return session;
+ }
+
+ private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
+ for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
+ if (mSessionsListeners.get(i).listener.asBinder() == listener.asBinder()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private int findIndexOfSession2TokensListenerLocked(ISession2TokensListener listener) {
+ for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+ if (mSession2TokensListenerRecords.get(i).listener.asBinder() == listener.asBinder()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+ private void pushSessionsChanged(int userId) {
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
+ return;
+ }
+ List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
+ int size = records.size();
+ ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
+ for (int i = 0; i < size; i++) {
+ tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
+ }
+ pushRemoteVolumeUpdateLocked(userId);
+ for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
+ SessionsListenerRecord record = mSessionsListeners.get(i);
+ if (record.userId == USER_ALL || record.userId == userId) {
+ try {
+ record.listener.onActiveSessionsChanged(tokens);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
+ e);
+ mSessionsListeners.remove(i);
+ }
+ }
+ }
+ }
+ }
+
+ private void pushRemoteVolumeUpdateLocked(int userId) {
+ if (mRvc != null) {
+ try {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
+ return;
+ }
+ MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
+ mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
+ }
+ }
+ }
+
+ void pushSession2TokensChangedLocked(int userId) {
+ List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
+ List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
+
+ for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+ Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
+ try {
+ if (listenerRecord.userId == USER_ALL) {
+ listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
+ } else if (listenerRecord.userId == userId) {
+ listenerRecord.listener.onSession2TokensChanged(session2Tokens);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
+ mSession2TokensListenerRecords.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Called when the media button receiver for the {@code record} is changed.
+ *
+ * @param record the media session whose media button receiver is updated.
+ */
+ public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+ MediaSessionRecord mediaButtonSession =
+ user.mPriorityStack.getMediaButtonSession();
+ if (record == mediaButtonSession) {
+ user.rememberMediaButtonReceiverLocked(mediaButtonSession);
+ }
+ }
+ }
+
+ private String getCallingPackageName(int uid) {
+ String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ return packages[0];
+ }
+ return "";
+ }
+
+ private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
+ if (mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
+ return;
+ }
+ try {
+ mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
+ }
+ }
+
+ private FullUserRecord getFullUserRecordLocked(int userId) {
+ int fullUserId = mFullUserIds.get(userId, -1);
+ if (fullUserId < 0) {
+ return null;
+ }
+ return mUserRecords.get(fullUserId);
+ }
+
+ /**
+ * Information about a full user and its corresponding managed profiles.
+ *
+ * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
+ * them when he/she presses a media/volume button. So keeping media sessions for them in one
+ * place makes more sense and increases the readability.</p>
+ * <p>The contents of this object is guarded by {@link #mLock}.
+ */
+ final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
+ public static final int COMPONENT_TYPE_INVALID = 0;
+ public static final int COMPONENT_TYPE_BROADCAST = 1;
+ public static final int COMPONENT_TYPE_ACTIVITY = 2;
+ public static final int COMPONENT_TYPE_SERVICE = 3;
+ private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
+
+ private final int mFullUserId;
+ private final MediaSessionStack mPriorityStack;
+ private PendingIntent mLastMediaButtonReceiver;
+ private ComponentName mRestoredMediaButtonReceiver;
+ private int mRestoredMediaButtonReceiverComponentType;
+ private int mRestoredMediaButtonReceiverUserId;
+
+ private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
+ private int mOnVolumeKeyLongPressListenerUid;
+ private KeyEvent mInitialDownVolumeKeyEvent;
+ private int mInitialDownVolumeStream;
+ private boolean mInitialDownMusicOnly;
+
+ private IOnMediaKeyListener mOnMediaKeyListener;
+ private int mOnMediaKeyListenerUid;
+ private ICallback mCallback;
+
+ FullUserRecord(int fullUserId) {
+ mFullUserId = fullUserId;
+ mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
+ // Restore the remembered media button receiver before the boot.
+ String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver,
+ Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
+ if (mediaButtonReceiverInfo == null) {
+ return;
+ }
+ String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
+ if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
+ return;
+ }
+ mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
+ mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
+ if (tokens.length == 3) {
+ mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]);
+ } else {
+ mRestoredMediaButtonReceiverComponentType =
+ getComponentType(mRestoredMediaButtonReceiver);
+ }
+ }
+
+ public void destroySessionsForUserLocked(int userId) {
+ List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
+ for (MediaSessionRecord session : sessions) {
+ MediaSessionServiceImpl.this.destroySessionLocked(session);
+ }
+ }
+
+ public void dumpLocked(PrintWriter pw, String prefix) {
+ pw.print(prefix + "Record for full_user=" + mFullUserId);
+ // Dump managed profile user ids associated with this user.
+ int size = mFullUserIds.size();
+ for (int i = 0; i < size; i++) {
+ if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
+ && mFullUserIds.valueAt(i) == mFullUserId) {
+ pw.print(", profile_user=" + mFullUserIds.keyAt(i));
+ }
+ }
+ pw.println();
+ String indent = prefix + " ";
+ pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
+ pw.println(indent + "Volume key long-press listener package: "
+ + getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
+ pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
+ pw.println(indent + "Media key listener package: "
+ + getCallingPackageName(mOnMediaKeyListenerUid));
+ pw.println(indent + "Callback: " + mCallback);
+ pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
+ pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
+ pw.println(indent + "Restored MediaButtonReceiverComponentType: "
+ + mRestoredMediaButtonReceiverComponentType);
+ mPriorityStack.dump(pw, indent);
+ pw.println(indent + "Session2Tokens:");
+ for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+ List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
+ if (list == null || list.size() == 0) {
+ continue;
+ }
+ for (Session2Token token : list) {
+ pw.println(indent + " " + token);
+ }
+ }
+ }
+
+ @Override
+ public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
+ MediaSessionRecord newMediaButtonSession) {
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
+ }
+ synchronized (mLock) {
+ if (oldMediaButtonSession != null) {
+ mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
+ }
+ if (newMediaButtonSession != null) {
+ rememberMediaButtonReceiverLocked(newMediaButtonSession);
+ mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
+ }
+ pushAddressedPlayerChangedLocked();
+ }
+ }
+
+ // Remember media button receiver and keep it in the persistent storage.
+ public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
+ PendingIntent receiver = record.getMediaButtonReceiver();
+ mLastMediaButtonReceiver = receiver;
+ mRestoredMediaButtonReceiver = null;
+ mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
+
+ String mediaButtonReceiverInfo = "";
+ if (receiver != null) {
+ ComponentName component = receiver.getIntent().getComponent();
+ if (component != null
+ && record.getPackageName().equals(component.getPackageName())) {
+ String componentName = component.flattenToString();
+ int componentType = getComponentType(component);
+ mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM,
+ componentName, String.valueOf(record.getUserId()),
+ String.valueOf(componentType));
+ }
+ }
+ Settings.Secure.putStringForUser(mContentResolver,
+ Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo,
+ mFullUserId);
+ }
+
+ private void pushAddressedPlayerChangedLocked() {
+ if (mCallback == null) {
+ return;
+ }
+ try {
+ MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
+ if (mediaButtonSession != null) {
+ mCallback.onAddressedPlayerChangedToMediaSession(
+ new MediaSession.Token(mediaButtonSession.getControllerBinder()));
+ } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
+ mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
+ mCurrentFullUserRecord.mLastMediaButtonReceiver
+ .getIntent().getComponent());
+ } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
+ mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
+ mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
+ }
+ }
+
+ private MediaSessionRecord getMediaButtonSessionLocked() {
+ return isGlobalPriorityActiveLocked()
+ ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
+ }
+
+ private int getComponentType(@Nullable ComponentName componentName) {
+ if (componentName == null) {
+ return COMPONENT_TYPE_INVALID;
+ }
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ActivityInfo activityInfo = pm.getActivityInfo(componentName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.GET_ACTIVITIES);
+ if (activityInfo != null) {
+ return COMPONENT_TYPE_ACTIVITY;
+ }
+ } catch (NameNotFoundException e) {
+ }
+ try {
+ ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.GET_SERVICES);
+ if (serviceInfo != null) {
+ return COMPONENT_TYPE_SERVICE;
+ }
+ } catch (NameNotFoundException e) {
+ }
+ // Pick legacy behavior for BroadcastReceiver or unknown.
+ return COMPONENT_TYPE_BROADCAST;
+ }
+ }
+
+ final class SessionsListenerRecord implements IBinder.DeathRecipient {
+ public final IActiveSessionsListener listener;
+ public final ComponentName componentName;
+ public final int userId;
+ public final int pid;
+ public final int uid;
+
+ SessionsListenerRecord(IActiveSessionsListener listener,
+ ComponentName componentName,
+ int userId, int pid, int uid) {
+ this.listener = listener;
+ this.componentName = componentName;
+ this.userId = userId;
+ this.pid = pid;
+ this.uid = uid;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mSessionsListeners.remove(this);
+ }
+ }
+ }
+
+ final class Session2TokensListenerRecord implements IBinder.DeathRecipient {
+ public final ISession2TokensListener listener;
+ public final int userId;
+
+ Session2TokensListenerRecord(ISession2TokensListener listener,
+ int userId) {
+ this.listener = listener;
+ this.userId = userId;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mSession2TokensListenerRecords.remove(this);
+ }
+ }
+ }
+
+ final class SettingsObserver extends ContentObserver {
+ private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
+ private SettingsObserver() {
+ super(null);
+ }
+
+ private void observe() {
+ mContentResolver.registerContentObserver(mSecureSettingsUri,
+ false, this, USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateActiveSessionListeners();
+ }
+ }
+
+ class SessionManagerImpl extends ISessionManager.Stub {
+ private static final String EXTRA_WAKELOCK_ACQUIRED =
+ "android.media.AudioService.WAKELOCK_ACQUIRED";
+ private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
+
+ private boolean mVoiceButtonDown = false;
+ private boolean mVoiceButtonHandled = false;
+
+ @Override
+ public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
+ int userId) throws RemoteException {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ enforcePackageName(packageName, uid);
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ false /* allowAll */, true /* requireFull */, "createSession", packageName);
+ if (cb == null) {
+ throw new IllegalArgumentException("Controller callback cannot be null");
+ }
+ return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
+ .getSessionBinder();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void notifySession2Created(Session2Token sessionToken) throws RemoteException {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Session2 is created " + sessionToken);
+ }
+ if (uid != sessionToken.getUid()) {
+ throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+ + " but actually=" + sessionToken.getUid());
+ }
+ Controller2Callback callback = new Controller2Callback(sessionToken);
+ // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
+ // it's closed.
+ // TODO: Keep controller as well for better readability
+ // because the GC behavior isn't straightforward.
+ MediaController2 controller = new MediaController2(mContext, sessionToken,
+ new HandlerExecutor(mHandler), callback);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public List<IBinder> getSessions(ComponentName componentName, int userId) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
+ ArrayList<IBinder> binders = new ArrayList<IBinder>();
+ synchronized (mLock) {
+ List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
+ for (MediaSessionRecord record : records) {
+ binders.add(record.getControllerBinder().asBinder());
+ }
+ }
+ return binders;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public List<Session2Token> getSession2Tokens(int userId) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ // Check that they can make calls on behalf of the user and
+ // get the final user id
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ true /* allowAll */, true /* requireFull */, "getSession2Tokens",
+ null /* optional packageName */);
+ List<Session2Token> result;
+ synchronized (mLock) {
+ result = getSession2TokensLocked(resolvedUserId);
+ }
+ return result;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void addSessionsListener(IActiveSessionsListener listener,
+ ComponentName componentName, int userId) throws RemoteException {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
+ synchronized (mLock) {
+ int index = findIndexOfSessionsListenerLocked(listener);
+ if (index != -1) {
+ Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
+ return;
+ }
+ SessionsListenerRecord record = new SessionsListenerRecord(listener,
+ componentName, resolvedUserId, pid, uid);
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
+ return;
+ }
+ mSessionsListeners.add(record);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void removeSessionsListener(IActiveSessionsListener listener)
+ throws RemoteException {
+ synchronized (mLock) {
+ int index = findIndexOfSessionsListenerLocked(listener);
+ if (index != -1) {
+ SessionsListenerRecord record = mSessionsListeners.remove(index);
+ try {
+ record.listener.asBinder().unlinkToDeath(record, 0);
+ } catch (Exception e) {
+ // ignore exceptions, the record is being removed
+ }
+ }
+ }
+ }
+
+ @Override
+ public void addSession2TokensListener(ISession2TokensListener listener,
+ int userId) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ // Check that they can make calls on behalf of the user and get the final user id.
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ true /* allowAll */, true /* requireFull */, "addSession2TokensListener",
+ null /* optional packageName */);
+ synchronized (mLock) {
+ int index = findIndexOfSession2TokensListenerLocked(listener);
+ if (index >= 0) {
+ Log.w(TAG, "addSession2TokensListener is already added, ignoring");
+ return;
+ }
+ mSession2TokensListenerRecords.add(
+ new Session2TokensListenerRecord(listener, resolvedUserId));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void removeSession2TokensListener(ISession2TokensListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ synchronized (mLock) {
+ int index = findIndexOfSession2TokensListenerLocked(listener);
+ if (index >= 0) {
+ Session2TokensListenerRecord listenerRecord =
+ mSession2TokensListenerRecords.remove(index);
+ try {
+ listenerRecord.listener.asBinder().unlinkToDeath(listenerRecord, 0);
+ } catch (Exception e) {
+ // Ignore exception.
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Handles the dispatching of the media button events to one of the
+ * registered listeners, or if there was none, broadcast an
+ * ACTION_MEDIA_BUTTON intent to the rest of the system.
+ *
+ * @param packageName The caller package
+ * @param asSystemService {@code true} if the event sent to the session as if it was come
+ * from the system service instead of the app process. This helps sessions to
+ * distinguish between the key injection by the app and key events from the
+ * hardware devices. Should be used only when the volume key events aren't handled
+ * by foreground activity. {@code false} otherwise to tell session about the real
+ * caller.
+ * @param keyEvent a non-null KeyEvent whose key code is one of the
+ * supported media buttons
+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
+ * while this key event is dispatched.
+ */
+ @Override
+ public void dispatchMediaKeyEvent(String packageName, boolean asSystemService,
+ KeyEvent keyEvent, boolean needWakeLock) {
+ if (keyEvent == null || !KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
+ Log.w(TAG, "Attempted to dispatch null or non-media key event.");
+ return;
+ }
+
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchMediaKeyEvent, pkg=" + packageName + " pid=" + pid
+ + ", uid=" + uid + ", asSystem=" + asSystemService + ", event="
+ + keyEvent);
+ }
+ if (!isUserSetupComplete()) {
+ // Global media key handling can have the side-effect of starting new
+ // activities which is undesirable while setup is in progress.
+ Slog.i(TAG, "Not dispatching media key event because user "
+ + "setup is in progress.");
+ return;
+ }
+
+ synchronized (mLock) {
+ boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
+ if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
+ // Prevent dispatching key event through reflection while the global
+ // priority session is active.
+ Slog.i(TAG, "Only the system can dispatch media key event "
+ + "to the global priority session.");
+ return;
+ }
+ if (!isGlobalPriorityActive) {
+ if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Send " + keyEvent + " to the media key listener");
+ }
+ try {
+ mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
+ new MediaKeyListenerResultReceiver(packageName, pid, uid,
+ asSystemService, keyEvent, needWakeLock));
+ return;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send " + keyEvent
+ + " to the media key listener");
+ }
+ }
+ }
+ if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
+ handleVoiceKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
+ needWakeLock);
+ } else {
+ dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
+ keyEvent, needWakeLock);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setCallback(ICallback callback) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
+ throw new SecurityException("Only Bluetooth service processes can set"
+ + " Callback");
+ }
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(uid);
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null || user.mFullUserId != userId) {
+ Log.w(TAG, "Only the full user can set the callback"
+ + ", userId=" + userId);
+ return;
+ }
+ user.mCallback = callback;
+ Log.d(TAG, "The callback " + user.mCallback
+ + " is set by " + getCallingPackageName(uid));
+ if (user.mCallback == null) {
+ return;
+ }
+ try {
+ user.mCallback.asBinder().linkToDeath(
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ user.mCallback = null;
+ }
+ }
+ }, 0);
+ user.pushAddressedPlayerChangedLocked();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set callback", e);
+ user.mCallback = null;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
+ if (mContext.checkPermission(
+ android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER"
+ + " permission.");
+ }
+
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(uid);
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null || user.mFullUserId != userId) {
+ Log.w(TAG, "Only the full user can set the volume key long-press listener"
+ + ", userId=" + userId);
+ return;
+ }
+ if (user.mOnVolumeKeyLongPressListener != null
+ && user.mOnVolumeKeyLongPressListenerUid != uid) {
+ Log.w(TAG, "The volume key long-press listener cannot be reset"
+ + " by another app , mOnVolumeKeyLongPressListener="
+ + user.mOnVolumeKeyLongPressListenerUid
+ + ", uid=" + uid);
+ return;
+ }
+
+ user.mOnVolumeKeyLongPressListener = listener;
+ user.mOnVolumeKeyLongPressListenerUid = uid;
+
+ Log.d(TAG, "The volume key long-press listener "
+ + listener + " is set by " + getCallingPackageName(uid));
+
+ if (user.mOnVolumeKeyLongPressListener != null) {
+ try {
+ user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ user.mOnVolumeKeyLongPressListener = null;
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set death recipient "
+ + user.mOnVolumeKeyLongPressListener);
+ user.mOnVolumeKeyLongPressListener = null;
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Enforce SET_MEDIA_KEY_LISTENER permission.
+ if (mContext.checkPermission(
+ android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER permission.");
+ }
+
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(uid);
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null || user.mFullUserId != userId) {
+ Log.w(TAG, "Only the full user can set the media key listener"
+ + ", userId=" + userId);
+ return;
+ }
+ if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
+ Log.w(TAG, "The media key listener cannot be reset by another app. "
+ + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
+ + ", uid=" + uid);
+ return;
+ }
+
+ user.mOnMediaKeyListener = listener;
+ user.mOnMediaKeyListenerUid = uid;
+
+ Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
+ + " is set by " + getCallingPackageName(uid));
+
+ if (user.mOnMediaKeyListener != null) {
+ try {
+ user.mOnMediaKeyListener.asBinder().linkToDeath(
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ user.mOnMediaKeyListener = null;
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
+ user.mOnMediaKeyListener = null;
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Handles the dispatching of the volume button events to one of the
+ * registered listeners. If there's a volume key long-press listener and
+ * there's no active global priority session, long-pressess will be sent to the
+ * long-press listener instead of adjusting volume.
+ *
+ * @param packageName The caller's package name, obtained by Context#getPackageName()
+ * @param opPackageName The caller's op package name, obtained by Context#getOpPackageName()
+ * @param asSystemService {@code true} if the event sent to the session as if it was come
+ * from the system service instead of the app process. This helps sessions to
+ * distinguish between the key injection by the app and key events from the
+ * hardware devices. Should be used only when the volume key events aren't handled
+ * by foreground activity. {@code false} otherwise to tell session about the real
+ * caller.
+ * @param keyEvent a non-null KeyEvent whose key code is one of the
+ * {@link KeyEvent#KEYCODE_VOLUME_UP},
+ * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
+ * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
+ * @param stream stream type to adjust volume.
+ * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
+ */
+ @Override
+ public void dispatchVolumeKeyEvent(String packageName, String opPackageName,
+ boolean asSystemService, KeyEvent keyEvent, int stream, boolean musicOnly) {
+ if (keyEvent == null
+ || (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
+ && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
+ && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
+ Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
+ return;
+ }
+
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + ", pid=" + pid + ", uid="
+ + uid + ", asSystem=" + asSystemService + ", event=" + keyEvent);
+ }
+
+ try {
+ synchronized (mLock) {
+ if (isGlobalPriorityActiveLocked()
+ || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
+ dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
+ asSystemService, keyEvent, stream, musicOnly);
+ } else {
+ // TODO: Consider the case when both volume up and down keys are pressed
+ // at the same time.
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ if (keyEvent.getRepeatCount() == 0) {
+ // Keeps the copy of the KeyEvent because it can be reused.
+ mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
+ KeyEvent.obtain(keyEvent);
+ mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
+ mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(
+ MessageHandler.MSG_VOLUME_INITIAL_DOWN,
+ mCurrentFullUserRecord.mFullUserId, 0),
+ mLongPressTimeout);
+ }
+ if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
+ mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
+ if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
+ dispatchVolumeKeyLongPressLocked(
+ mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
+ // Mark that the key is already handled.
+ mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
+ }
+ dispatchVolumeKeyLongPressLocked(keyEvent);
+ }
+ } else { // if up
+ mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
+ if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
+ && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
+ .getDownTime() == keyEvent.getDownTime()) {
+ // Short-press. Should change volume.
+ dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
+ asSystemService,
+ mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
+ mCurrentFullUserRecord.mInitialDownVolumeStream,
+ mCurrentFullUserRecord.mInitialDownMusicOnly);
+ dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
+ asSystemService, keyEvent, stream, musicOnly);
+ } else {
+ dispatchVolumeKeyLongPressLocked(keyEvent);
+ }
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void dispatchVolumeKeyEventLocked(String packageName, String opPackageName, int pid,
+ int uid, boolean asSystemService, KeyEvent keyEvent, int stream,
+ boolean musicOnly) {
+ boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
+ boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
+ int direction = 0;
+ boolean isMute = false;
+ switch (keyEvent.getKeyCode()) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ direction = AudioManager.ADJUST_RAISE;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ direction = AudioManager.ADJUST_LOWER;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ isMute = true;
+ break;
+ }
+ if (down || up) {
+ int flags = AudioManager.FLAG_FROM_KEY;
+ if (musicOnly) {
+ // This flag is used when the screen is off to only affect active media.
+ flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
+ } else {
+ // These flags are consistent with the home screen
+ if (up) {
+ flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
+ } else {
+ flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
+ }
+ }
+ if (direction != 0) {
+ // If this is action up we want to send a beep for non-music events
+ if (up) {
+ direction = 0;
+ }
+ dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
+ asSystemService, stream, direction, flags);
+ } else if (isMute) {
+ if (down && keyEvent.getRepeatCount() == 0) {
+ dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
+ asSystemService, stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void dispatchAdjustVolume(String packageName, String opPackageName,
+ int suggestedStream, int delta, int flags) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid, false,
+ suggestedStream, delta, flags);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setRemoteVolumeController(IRemoteVolumeController rvc) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ enforceSystemUiPermission("listen for volume changes", pid, uid);
+ mRvc = rvc;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isGlobalPriorityActive() {
+ synchronized (mLock) {
+ return isGlobalPriorityActiveLocked();
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
+ pw.println();
+
+ synchronized (mLock) {
+ pw.println(mSessionsListeners.size() + " sessions listeners.");
+ pw.println("Global priority session is " + mGlobalPrioritySession);
+ if (mGlobalPrioritySession != null) {
+ mGlobalPrioritySession.dump(pw, " ");
+ }
+ pw.println("User Records:");
+ int count = mUserRecords.size();
+ for (int i = 0; i < count; i++) {
+ mUserRecords.valueAt(i).dumpLocked(pw, "");
+ }
+ mAudioPlayerStateMonitor.dump(mContext, pw, "");
+ }
+ }
+
+ /**
+ * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
+ * permission or an enabled notification listener)
+ *
+ * @param controllerPackageName package name of the controller app
+ * @param controllerPid pid of the controller app
+ * @param controllerUid uid of the controller app
+ */
+ @Override
+ public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
+ throws RemoteException {
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Don't perform sanity check between controllerPackageName and controllerUid.
+ // When an (activity|service) runs on the another apps process by specifying
+ // android:process in the AndroidManifest.xml, then PID and UID would have the
+ // running process' information instead of the (activity|service) that has created
+ // MediaController.
+ // Note that we can use Context#getOpPackageName() instead of
+ // Context#getPackageName() for getting package name that matches with the PID/UID,
+ // but it doesn't tell which package has created the MediaController, so useless.
+ return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
+ controllerPid, controllerUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // For MediaSession
+ private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
+ final int uid) {
+ String packageName = null;
+ if (componentName != null) {
+ // If they gave us a component name verify they own the
+ // package
+ packageName = componentName.getPackageName();
+ enforcePackageName(packageName, uid);
+ }
+ // Check that they can make calls on behalf of the user and
+ // get the final user id
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ true /* allowAll */, true /* requireFull */, "getSessions", packageName);
+ // Check if they have the permissions or their component is
+ // enabled for the user they're calling from.
+ enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
+ return resolvedUserId;
+ }
+
+ private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
+ int pid, int uid) throws RemoteException {
+ // Allow API calls from the System UI
+ if (isCurrentVolumeController(pid, uid)) {
+ return true;
+ }
+
+ // Check if it's system server or has MEDIA_CONTENT_CONTROL.
+ // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
+ // check here.
+ if (uid == Process.SYSTEM_UID || mContext.checkPermission(
+ android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ } else if (DEBUG) {
+ Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
+ }
+
+ // You may not access another user's content as an enabled listener.
+ final int userId = UserHandle.getUserId(uid);
+ if (resolvedUserId != userId) {
+ return false;
+ }
+
+ // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
+ // String pkgName) to notification team for optimization
+ final List<ComponentName> enabledNotificationListeners =
+ mNotificationManager.getEnabledNotificationListeners(userId);
+ if (enabledNotificationListeners != null) {
+ for (int i = 0; i < enabledNotificationListeners.size(); i++) {
+ if (TextUtils.equals(packageName,
+ enabledNotificationListeners.get(i).getPackageName())) {
+ return true;
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
+ + "notification listener");
+ }
+ return false;
+ }
+
+ private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
+ int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
+ MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
+ : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
+
+ boolean preferSuggestedStream = false;
+ if (isValidLocalStreamType(suggestedStream)
+ && AudioSystem.isStreamActive(suggestedStream, 0)) {
+ preferSuggestedStream = true;
+ }
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
+ + flags + ", suggestedStream=" + suggestedStream
+ + ", preferSuggestedStream=" + preferSuggestedStream);
+ }
+ if (session == null || preferSuggestedStream) {
+ if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
+ && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
+ if (DEBUG) {
+ Log.d(TAG, "No active session to adjust, skipping media only volume event");
+ }
+ return;
+ }
+
+ // Execute mAudioService.adjustSuggestedStreamVolume() on
+ // handler thread of MediaSessionService.
+ // This will release the MediaSessionService.mLock sooner and avoid
+ // a potential deadlock between MediaSessionService.mLock and
+ // ActivityManagerService lock.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ final String callingOpPackageName;
+ final int callingUid;
+ if (asSystemService) {
+ callingOpPackageName = mContext.getOpPackageName();
+ callingUid = Process.myUid();
+ } else {
+ callingOpPackageName = opPackageName;
+ callingUid = uid;
+ }
+ try {
+ mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream,
+ direction, flags, callingOpPackageName, callingUid);
+ } catch (SecurityException | IllegalArgumentException e) {
+ Log.e(TAG, "Cannot adjust volume: direction=" + direction
+ + ", suggestedStream=" + suggestedStream + ", flags=" + flags
+ + ", packageName=" + packageName + ", uid=" + uid
+ + ", asSystemService=" + asSystemService, e);
+ }
+ }
+ });
+ } else {
+ session.adjustVolume(packageName, opPackageName, pid, uid, null, asSystemService,
+ direction, flags, true);
+ }
+ }
+
+ private void handleVoiceKeyEventLocked(String packageName, int pid, int uid,
+ boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
+ int action = keyEvent.getAction();
+ boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
+ if (action == KeyEvent.ACTION_DOWN) {
+ if (keyEvent.getRepeatCount() == 0) {
+ mVoiceButtonDown = true;
+ mVoiceButtonHandled = false;
+ } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
+ mVoiceButtonHandled = true;
+ startVoiceInput(needWakeLock);
+ }
+ } else if (action == KeyEvent.ACTION_UP) {
+ if (mVoiceButtonDown) {
+ mVoiceButtonDown = false;
+ if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
+ // Resend the down then send this event through
+ KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
+ dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
+ downEvent, needWakeLock);
+ dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
+ keyEvent, needWakeLock);
+ }
+ }
+ }
+ }
+
+ private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
+ boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
+ MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
+ if (session != null) {
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Sending " + keyEvent + " to " + session);
+ }
+ if (needWakeLock) {
+ mKeyEventReceiver.aquireWakeLockLocked();
+ }
+ // If we don't need a wakelock use -1 as the id so we won't release it later.
+ session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
+ needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
+ mKeyEventReceiver);
+ if (mCurrentFullUserRecord.mCallback != null) {
+ try {
+ mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
+ keyEvent, new MediaSession.Token(session.getControllerBinder()));
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send callback", e);
+ }
+ }
+ } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
+ || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
+ if (needWakeLock) {
+ mKeyEventReceiver.aquireWakeLockLocked();
+ }
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ // TODO: Find a way to also send PID/UID in secure way.
+ String callerPackageName =
+ (asSystemService) ? mContext.getPackageName() : packageName;
+ mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
+ try {
+ if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
+ PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Sending " + keyEvent
+ + " to the last known PendingIntent " + receiver);
+ }
+ receiver.send(mContext,
+ needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
+ mediaButtonIntent, mKeyEventReceiver, mHandler);
+ if (mCurrentFullUserRecord.mCallback != null) {
+ ComponentName componentName = mCurrentFullUserRecord
+ .mLastMediaButtonReceiver.getIntent().getComponent();
+ if (componentName != null) {
+ mCurrentFullUserRecord.mCallback
+ .onMediaKeyEventDispatchedToMediaButtonReceiver(
+ keyEvent, componentName);
+ }
+ }
+ } else {
+ ComponentName receiver =
+ mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
+ int componentType = mCurrentFullUserRecord
+ .mRestoredMediaButtonReceiverComponentType;
+ UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord
+ .mRestoredMediaButtonReceiverUserId);
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
+ + receiver + ", type=" + componentType);
+ }
+ mediaButtonIntent.setComponent(receiver);
+ try {
+ switch (componentType) {
+ case FullUserRecord.COMPONENT_TYPE_ACTIVITY:
+ mContext.startActivityAsUser(mediaButtonIntent, userHandle);
+ break;
+ case FullUserRecord.COMPONENT_TYPE_SERVICE:
+ mContext.startForegroundServiceAsUser(mediaButtonIntent,
+ userHandle);
+ break;
+ default:
+ // Legacy behavior for other cases.
+ mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error sending media button to the restored intent "
+ + receiver + ", type=" + componentType, e);
+ }
+ if (mCurrentFullUserRecord.mCallback != null) {
+ mCurrentFullUserRecord.mCallback
+ .onMediaKeyEventDispatchedToMediaButtonReceiver(
+ keyEvent, receiver);
+ }
+ }
+ } catch (CanceledException e) {
+ Log.i(TAG, "Error sending key event to media button receiver "
+ + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send callback", e);
+ }
+ }
+ }
+
+ private void startVoiceInput(boolean needWakeLock) {
+ Intent voiceIntent = null;
+ // select which type of search to launch:
+ // - screen on and device unlocked: action is ACTION_WEB_SEARCH
+ // - device locked or screen off: action is
+ // ACTION_VOICE_SEARCH_HANDS_FREE
+ // with EXTRA_SECURE set to true if the device is securely locked
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+ if (!isLocked && pm.isScreenOn()) {
+ voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
+ Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
+ } else {
+ voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
+ isLocked && mKeyguardManager.isKeyguardSecure());
+ Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
+ }
+ // start the search activity
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ }
+ try {
+ if (voiceIntent != null) {
+ voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
+ mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "No activity for search: " + e);
+ } finally {
+ if (needWakeLock) {
+ mMediaEventWakeLock.release();
+ }
+ }
+ }
+
+ private boolean isVoiceKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+ || (!mHasFeatureLeanback && keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+ }
+
+ private boolean isUserSetupComplete() {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+ }
+
+ // we only handle public stream types, which are 0-5
+ private boolean isValidLocalStreamType(int streamType) {
+ return streamType >= AudioManager.STREAM_VOICE_CALL
+ && streamType <= AudioManager.STREAM_NOTIFICATION;
+ }
+
+ private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
+ private final String mPackageName;
+ private final int mPid;
+ private final int mUid;
+ private final boolean mAsSystemService;
+ private final KeyEvent mKeyEvent;
+ private final boolean mNeedWakeLock;
+ private boolean mHandled;
+
+ private MediaKeyListenerResultReceiver(String packageName, int pid, int uid,
+ boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
+ super(mHandler);
+ mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
+ mPackageName = packageName;
+ mPid = pid;
+ mUid = uid;
+ mAsSystemService = asSystemService;
+ mKeyEvent = keyEvent;
+ mNeedWakeLock = needWakeLock;
+ }
+
+ @Override
+ public void run() {
+ Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
+ dispatchMediaKeyEvent();
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
+ mHandled = true;
+ mHandler.removeCallbacks(this);
+ return;
+ }
+ dispatchMediaKeyEvent();
+ }
+
+ private void dispatchMediaKeyEvent() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+ mHandler.removeCallbacks(this);
+ synchronized (mLock) {
+ if (!isGlobalPriorityActiveLocked()
+ && isVoiceKey(mKeyEvent.getKeyCode())) {
+ handleVoiceKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
+ mKeyEvent, mNeedWakeLock);
+ } else {
+ dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
+ mKeyEvent, mNeedWakeLock);
+ }
+ }
+ }
+ }
+
+ private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
+
+ class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
+ PendingIntent.OnFinished {
+ private final Handler mHandler;
+ private int mRefCount = 0;
+ private int mLastTimeoutId = 0;
+
+ KeyEventWakeLockReceiver(Handler handler) {
+ super(handler);
+ mHandler = handler;
+ }
+
+ public void onTimeout() {
+ synchronized (mLock) {
+ if (mRefCount == 0) {
+ // We've already released it, so just return
+ return;
+ }
+ mLastTimeoutId++;
+ mRefCount = 0;
+ releaseWakeLockLocked();
+ }
+ }
+
+ public void aquireWakeLockLocked() {
+ if (mRefCount == 0) {
+ mMediaEventWakeLock.acquire();
+ }
+ mRefCount++;
+ mHandler.removeCallbacks(this);
+ mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
+
+ }
+
+ @Override
+ public void run() {
+ onTimeout();
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode < mLastTimeoutId) {
+ // Ignore results from calls that were before the last
+ // timeout, just in case.
+ return;
+ } else {
+ synchronized (mLock) {
+ if (mRefCount > 0) {
+ mRefCount--;
+ if (mRefCount == 0) {
+ releaseWakeLockLocked();
+ }
+ }
+ }
+ }
+ }
+
+ private void releaseWakeLockLocked() {
+ mMediaEventWakeLock.release();
+ mHandler.removeCallbacks(this);
+ }
+
+ @Override
+ public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+ String resultData, Bundle resultExtras) {
+ onReceiveResult(resultCode, null);
+ }
+ };
+
+ BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+ synchronized (mLock) {
+ if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
+ && mMediaEventWakeLock.isHeld()) {
+ mMediaEventWakeLock.release();
+ }
+ }
+ }
+ };
+ }
+
+ final class MessageHandler extends Handler {
+ private static final int MSG_SESSIONS_CHANGED = 1;
+ private static final int MSG_VOLUME_INITIAL_DOWN = 2;
+ private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SESSIONS_CHANGED:
+ pushSessionsChanged((int) msg.obj);
+ break;
+ case MSG_VOLUME_INITIAL_DOWN:
+ synchronized (mLock) {
+ FullUserRecord user = mUserRecords.get((int) msg.arg1);
+ if (user != null && user.mInitialDownVolumeKeyEvent != null) {
+ dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
+ // Mark that the key is already handled.
+ user.mInitialDownVolumeKeyEvent = null;
+ }
+ }
+ break;
+ }
+ }
+
+ public void postSessionsChanged(int userId) {
+ // Use object instead of the arguments when posting message to remove pending requests.
+ Integer userIdInteger = mIntegerCache.get(userId);
+ if (userIdInteger == null) {
+ userIdInteger = Integer.valueOf(userId);
+ mIntegerCache.put(userId, userIdInteger);
+ }
+ removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
+ obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
+ }
+ }
+
+ private class Controller2Callback extends MediaController2.ControllerCallback {
+ private final Session2Token mToken;
+
+ Controller2Callback(Session2Token token) {
+ mToken = token;
+ }
+
+ @Override
+ public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mToken.getUid());
+ mSession2TokensPerUser.get(userId).add(mToken);
+ pushSession2TokensChangedLocked(userId);
+ }
+ }
+
+ @Override
+ public void onDisconnected(MediaController2 controller) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mToken.getUid());
+ mSession2TokensPerUser.get(userId).remove(mToken);
+ pushSession2TokensChangedLocked(userId);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
index 6a5f563..64c451d 100644
--- a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -159,12 +159,13 @@
+ serviceInfo.packageName + "/" + serviceInfo.name);
return false;
}
- if (!hasCaptureVideoPermission(serviceInfo.packageName)) {
- // If the service does not have permission to capture video then it
- // isn't going to be terribly useful as a remote display, is it?
- // Kind of makes you wonder what it's doing there in the first place.
+ if (mPackageManager.checkPermission(Manifest.permission.REMOTE_DISPLAY_PROVIDER,
+ serviceInfo.packageName) != PackageManager.PERMISSION_GRANTED) {
+ // If the service does not have this permission then the system will not bind to it.
+ // This is to prevent non privileged apps declaring themselves as remote display
+ // providers just to be bound to by the system and keep their process alive.
Slog.w(TAG, "Ignoring remote display provider service because it does not "
- + "have the CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT "
+ + "have the REMOTE_DISPLAY_PROVIDER "
+ "permission: " + serviceInfo.packageName + "/" + serviceInfo.name);
return false;
}
@@ -172,18 +173,6 @@
return true;
}
- private boolean hasCaptureVideoPermission(String packageName) {
- if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT,
- packageName) == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
- if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT,
- packageName) == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
- return false;
- }
-
private int findProvider(String packageName, String className) {
int count = mProviders.size();
for (int i = 0; i < count; i++) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6932390..6baf12c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -23138,6 +23138,11 @@
}
@Override
+ public void setLocationExtraPackagesProvider(PackagesProvider provider) {
+ mDefaultPermissionPolicy.setLocationExtraPackagesProvider(provider);
+ }
+
+ @Override
public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
mDefaultPermissionPolicy.setVoiceInteractionPackagesProvider(provider);
}
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 68a755b..78fa82c 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -28,7 +28,6 @@
import android.util.Slog;
import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
@@ -53,21 +52,18 @@
private final IPackageManager mPackageManager;
private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
- private final Object mInstallLock;
- @GuardedBy("mInstallLock")
private final Installer mInstaller;
- public DexLogger(IPackageManager pms, Installer installer, Object installLock) {
- this(pms, installer, installLock, new PackageDynamicCodeLoading());
+ public DexLogger(IPackageManager pms, Installer installer) {
+ this(pms, installer, new PackageDynamicCodeLoading());
}
@VisibleForTesting
- DexLogger(IPackageManager pms, Installer installer, Object installLock,
+ DexLogger(IPackageManager pms, Installer installer,
PackageDynamicCodeLoading packageDynamicCodeLoading) {
mPackageManager = pms;
mPackageDynamicCodeLoading = packageDynamicCodeLoading;
mInstaller = installer;
- mInstallLock = installLock;
}
public Set<String> getAllPackagesWithDynamicCodeLoading() {
@@ -131,14 +127,16 @@
}
byte[] hash = null;
- synchronized (mInstallLock) {
- try {
- hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid,
- appInfo.volumeUuid, storageFlags);
- } catch (InstallerException e) {
- Slog.e(TAG, "Got InstallerException when hashing file " + filePath
- + ": " + e.getMessage());
- }
+ try {
+ // Note that we do not take the install lock here. Hashing should never interfere
+ // with app update/compilation/removal. We may get anomalous results if a file
+ // changes while we hash it, but that can happen anyway and is harmless for our
+ // purposes.
+ hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid,
+ appInfo.volumeUuid, storageFlags);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Got InstallerException when hashing file " + filePath
+ + ": " + e.getMessage());
}
String fileName = new File(filePath).getName();
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index e57d9d7..b546836 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -129,7 +129,7 @@
mPackageDexOptimizer = pdo;
mInstaller = installer;
mInstallLock = installLock;
- mDexLogger = new DexLogger(pms, installer, installLock);
+ mDexLogger = new DexLogger(pms, installer);
}
public DexLogger getDexLogger() {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 789664d..ceaf69d 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -225,6 +225,7 @@
private final Handler mHandler;
private PackagesProvider mLocationPackagesProvider;
+ private PackagesProvider mLocationExtraPackagesProvider;
private PackagesProvider mVoiceInteractionPackagesProvider;
private PackagesProvider mSmsAppPackagesProvider;
private PackagesProvider mDialerAppPackagesProvider;
@@ -270,6 +271,13 @@
}
}
+ /** Sets the provider for loction extra packages. */
+ public void setLocationExtraPackagesProvider(PackagesProvider provider) {
+ synchronized (mLock) {
+ mLocationExtraPackagesProvider = provider;
+ }
+ }
+
public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
synchronized (mLock) {
mVoiceInteractionPackagesProvider = provider;
@@ -403,6 +411,7 @@
Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
final PackagesProvider locationPackagesProvider;
+ final PackagesProvider locationExtraPackagesProvider;
final PackagesProvider voiceInteractionPackagesProvider;
final PackagesProvider smsAppPackagesProvider;
final PackagesProvider dialerAppPackagesProvider;
@@ -412,6 +421,7 @@
synchronized (mLock) {
locationPackagesProvider = mLocationPackagesProvider;
+ locationExtraPackagesProvider = mLocationExtraPackagesProvider;
voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
smsAppPackagesProvider = mSmsAppPackagesProvider;
dialerAppPackagesProvider = mDialerAppPackagesProvider;
@@ -424,6 +434,8 @@
? voiceInteractionPackagesProvider.getPackages(userId) : null;
String[] locationPackageNames = (locationPackagesProvider != null)
? locationPackagesProvider.getPackages(userId) : null;
+ String[] locationExtraPackageNames = (locationExtraPackagesProvider != null)
+ ? locationExtraPackagesProvider.getPackages(userId) : null;
String[] smsAppPackageNames = (smsAppPackagesProvider != null)
? smsAppPackagesProvider.getPackages(userId) : null;
String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
@@ -638,6 +650,12 @@
LOCATION_PERMISSIONS, ACTIVITY_RECOGNITION_PERMISSIONS);
}
}
+ if (locationExtraPackageNames != null) {
+ // Also grant location permission to location extra packages.
+ for (String packageName : locationExtraPackageNames) {
+ grantPermissionsToSystemPackage(packageName, userId, LOCATION_PERMISSIONS);
+ }
+ }
// Music
Intent musicIntent = new Intent(Intent.ACTION_VIEW)
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e82e748..2cd4921 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -210,6 +210,7 @@
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionManagerInternal;
+import android.sysprop.DisplayProperties;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.text.format.Time;
@@ -245,7 +246,6 @@
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -261,6 +261,7 @@
import com.android.server.am.PendingIntentController;
import com.android.server.am.PendingIntentRecord;
import com.android.server.am.UserState;
+import com.android.server.appop.AppOpsService;
import com.android.server.firewall.IntentFirewall;
import com.android.server.pm.UserManagerService;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -694,7 +695,7 @@
final boolean isPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
// Transfer any global setting for forcing RTL layout, into a System Property
- SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+ DisplayProperties.debug_force_rtl(forceRtl);
final Configuration configuration = new Configuration();
Settings.System.getConfiguration(resolver, configuration);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 740d472..45bb94b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1160,12 +1160,14 @@
@Override
boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
ConfigurationContainer requestingContainer) {
+ final int previousRotation = mRotation;
final Configuration config = updateOrientationFromAppTokens(
getRequestedOverrideConfiguration(), freezeDisplayToken, false);
- // If display rotation class tells us that it doesn't consider app requested orientation,
- // this display won't rotate just because of an app changes its requested orientation. Thus
- // it indicates that this display chooses not to handle this request.
- final boolean handled = getDisplayRotation().respectAppRequestedOrientation();
+ // This event is considered handled iff a configuration propagation is triggered, because
+ // that's the only place lower level containers check if they need to do something to this
+ // request. The only guaranteed signal is that the display is rotated to a different
+ // orientation (i.e. rotating 180 degrees doesn't count).
+ final boolean handled = (mRotation - previousRotation) % 2 != 0;
if (config == null) {
return handled;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index bcc7be4..7aabc15 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -329,15 +329,6 @@
return mFixedToUserRotation;
}
- /**
- * Returns {@code true} if this display rotation takes app requested orientation into
- * consideration; {@code false} otherwise. For the time being the only case where this is {@code
- * false} is when {@link #isFixedToUserRotation()} is {@code true}.
- */
- boolean respectAppRequestedOrientation() {
- return !mFixedToUserRotation;
- }
-
public int getLandscapeRotation() {
return mLandscapeRotation;
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 5f56fe5..177f244 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -462,22 +462,19 @@
mOccluded = false;
mDismissingKeyguardActivity = null;
- // Only the top activity of the focused stack on each display may control it's
- // occluded state.
- final ActivityStack focusedStack = display.getFocusedStack();
- if (focusedStack != null) {
- final ActivityRecord topDismissing =
- focusedStack.getTopDismissingKeyguardActivity();
- mOccluded = focusedStack.topActivityOccludesKeyguard() || (topDismissing != null
- && focusedStack.topRunningActivityLocked() == topDismissing
- && controller.canShowWhileOccluded(
+ final ActivityStack stack = getStackForControllingOccluding(display);
+ if (stack != null) {
+ final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
+ mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null
+ && stack.topRunningActivityLocked() == topDismissing
+ && controller.canShowWhileOccluded(
true /* dismissKeyguard */,
false /* showWhenLocked */));
- if (focusedStack.getTopDismissingKeyguardActivity() != null) {
- mDismissingKeyguardActivity = focusedStack.getTopDismissingKeyguardActivity();
+ if (stack.getTopDismissingKeyguardActivity() != null) {
+ mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
}
- mOccluded |= controller.mWindowManager.isShowingDream();
}
+ mOccluded |= controller.mWindowManager.isShowingDream();
// TODO(b/113840485): Handle app transition for individual display, and apply occluded
// state change to secondary displays.
@@ -492,6 +489,23 @@
}
}
+ /**
+ * Gets the stack used to check the occluded state.
+ * <p>
+ * Only the top non-pinned activity of the focusable stack on each display can control its
+ * occlusion state.
+ */
+ private ActivityStack getStackForControllingOccluding(ActivityDisplay display) {
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ if (stack != null && stack.isFocusableAndVisible()
+ && !stack.inPinnedWindowingMode()) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
void dumpStatus(PrintWriter pw, String prefix) {
final StringBuilder sb = new StringBuilder();
sb.append(prefix);
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 4a553cf..e944858 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -1278,28 +1278,28 @@
}
/**
- * Checks if the root activity requires a particular orientation (either by override or
+ * Checks if the top activity requires a particular orientation (either by override or
* activityInfo) and returns that. Otherwise, this returns ORIENTATION_UNDEFINED.
*/
- private int getRootActivityRequestedOrientation() {
- ActivityRecord root = getRootActivity();
+ private int getTopActivityRequestedOrientation() {
+ ActivityRecord top = getTopActivity();
if (getRequestedOverrideConfiguration().orientation != ORIENTATION_UNDEFINED
- || root == null) {
+ || top == null) {
return getRequestedOverrideConfiguration().orientation;
}
- int rootScreenOrientation = root.getOrientation();
- if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+ int screenOrientation = top.getOrientation();
+ if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
// NOSENSOR means the display's "natural" orientation, so return that.
ActivityDisplay display = mStack != null ? mStack.getDisplay() : null;
if (display != null && display.mDisplayContent != null) {
return mStack.getDisplay().mDisplayContent.getNaturalOrientation();
}
- } else if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
+ } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
// LOCKED means the activity's orientation remains unchanged, so return existing value.
- return root.getConfiguration().orientation;
- } else if (ActivityInfo.isFixedOrientationLandscape(rootScreenOrientation)) {
+ return top.getConfiguration().orientation;
+ } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)) {
return ORIENTATION_LANDSCAPE;
- } else if (ActivityInfo.isFixedOrientationPortrait(rootScreenOrientation)) {
+ } else if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)) {
return ORIENTATION_PORTRAIT;
}
return ORIENTATION_UNDEFINED;
@@ -2196,9 +2196,9 @@
// In FULLSCREEN mode, always start with empty bounds to indicate "fill parent"
outOverrideBounds.setEmpty();
- // If the task or its root activity require a different orientation, make it fit the
+ // If the task or its top activity requires a different orientation, make it fit the
// available bounds by scaling down its bounds.
- int forcedOrientation = getRootActivityRequestedOrientation();
+ int forcedOrientation = getTopActivityRequestedOrientation();
if (forcedOrientation != ORIENTATION_UNDEFINED
&& forcedOrientation != newParentConfig.orientation) {
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index c8c5e8f..fb00aeb 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -107,6 +107,7 @@
"android.hardware.gnss@1.0",
"android.hardware.gnss@1.1",
"android.hardware.gnss@2.0",
+ "android.hardware.gnss.visibility_control@1.0",
"android.hardware.input.classifier@1.0",
"android.hardware.ir@1.0",
"android.hardware.light@2.0",
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index bce944d..dcb2ff5 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -26,6 +26,7 @@
#include <android/hardware/gnss/1.1/IGnssMeasurement.h>
#include <android/hardware/gnss/2.0/IGnssMeasurement.h>
#include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
+#include <android/hardware/gnss/visibility_control/1.0/IGnssVisibilityControl.h>
#include <nativehelper/JNIHelp.h>
#include "jni.h"
#include "hardware_legacy/power.h"
@@ -88,6 +89,8 @@
static jmethodID method_correctionPlaneLngDeg;
static jmethodID method_correctionPlaneAltDeg;
static jmethodID method_correctionPlaneAzimDeg;
+static jmethodID method_reportNfwNotification;
+static jmethodID method_isInEmergencySession;
/*
* Save a pointer to JavaVm to attach/detach threads executing
@@ -152,6 +155,9 @@
using IMeasurementCorrections =
android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections;
+using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControl;
+using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControlCallback;
+
struct GnssDeathRecipient : virtual public hidl_death_recipient
{
// hidl_death_recipient interface
@@ -190,7 +196,7 @@
// This boolean is needed to ensure that Gnsss Measurement Corrections related method are only
// initalized when needed which will be few devices initially
bool firstGnssMeasurementCorrectionInjected = false;
-
+sp<IGnssVisibilityControl> gnssVisibilityControlIface = nullptr;
#define WAKE_LOCK_NAME "GPS"
@@ -1090,6 +1096,54 @@
}
/*
+ * GnssVisibilityControlCallback implements callback methods of IGnssVisibilityControlCallback.hal.
+ */
+struct GnssVisibilityControlCallback : public IGnssVisibilityControlCallback {
+ Return<void> nfwNotifyCb(const IGnssVisibilityControlCallback::NfwNotification& notification)
+ override;
+ Return<bool> isInEmergencySession() override;
+};
+
+Return<void> GnssVisibilityControlCallback::nfwNotifyCb(
+ const IGnssVisibilityControlCallback::NfwNotification& notification) {
+ JNIEnv* env = getJniEnv();
+ jstring proxyAppPackageName = env->NewStringUTF(notification.proxyAppPackageName.c_str());
+ jstring otherProtocolStackName = env->NewStringUTF(notification.otherProtocolStackName.c_str());
+ jstring requestorId = env->NewStringUTF(notification.requestorId.c_str());
+
+ if (proxyAppPackageName && otherProtocolStackName && requestorId) {
+ env->CallVoidMethod(mCallbacksObj, method_reportNfwNotification, proxyAppPackageName,
+ notification.protocolStack, otherProtocolStackName,
+ notification.requestor, requestorId,
+ notification.inEmergencyMode, notification.isCachedLocation);
+ } else {
+ ALOGE("%s: OOM Error\n", __func__);
+ }
+
+ if (requestorId) {
+ env->DeleteLocalRef(requestorId);
+ }
+
+ if (otherProtocolStackName) {
+ env->DeleteLocalRef(otherProtocolStackName);
+ }
+
+ if (proxyAppPackageName) {
+ env->DeleteLocalRef(proxyAppPackageName);
+ }
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return Void();
+}
+
+Return<bool> GnssVisibilityControlCallback::isInEmergencySession() {
+ JNIEnv* env = getJniEnv();
+ auto result = env->CallBooleanMethod(mCallbacksObj, method_isInEmergencySession);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return result;
+}
+
+/*
* AGnssCallback_V1_0 implements callback methods required by the IAGnssCallback 1.0 interface.
*/
struct AGnssCallback_V1_0 : public IAGnssCallback_V1_0 {
@@ -1323,6 +1377,9 @@
"reportLocationBatch",
"([Landroid/location/Location;)V");
method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
+ method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification",
+ "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V");
+ method_isInEmergencySession = env->GetMethodID(clazz, "isInEmergencySession", "()Z");
/*
* Save a pointer to JVM.
@@ -1398,12 +1455,6 @@
if (gnssHal_V2_0 != nullptr) {
// TODO(b/119638366): getExtensionGnssMeasurement_1_1 from gnssHal_V2_0
auto gnssMeasurement = gnssHal_V2_0->getExtensionGnssMeasurement_2_0();
- auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections();
- if (!gnssCorrections.isOk()) {
- ALOGD("Unable to get a handle to GnssMeasurementCorrections interface");
- } else {
- gnssCorrectionsIface = gnssCorrections;
- }
if (!gnssMeasurement.isOk()) {
ALOGD("Unable to get a handle to GnssMeasurement_V2_0");
} else {
@@ -1411,6 +1462,12 @@
gnssMeasurementIface_V1_1 = gnssMeasurementIface_V2_0;
gnssMeasurementIface = gnssMeasurementIface_V2_0;
}
+ auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections();
+ if (!gnssCorrections.isOk()) {
+ ALOGD("Unable to get a handle to GnssMeasurementCorrections interface");
+ } else {
+ gnssCorrectionsIface = gnssCorrections;
+ }
} else if (gnssHal_V1_1 != nullptr) {
auto gnssMeasurement = gnssHal_V1_1->getExtensionGnssMeasurement_1_1();
if (!gnssMeasurement.isOk()) {
@@ -1481,6 +1538,15 @@
} else {
gnssBatchingIface = gnssBatching;
}
+
+ if (gnssHal_V2_0 != nullptr) {
+ auto gnssVisibilityControl = gnssHal_V2_0->getExtensionVisibilityControl();
+ if (!gnssVisibilityControl.isOk()) {
+ ALOGD("Unable to get a handle to GnssVisibilityControl interface");
+ } else {
+ gnssVisibilityControlIface = gnssVisibilityControl;
+ }
+ }
}
static jboolean android_location_GnssLocationProvider_is_supported(
@@ -1582,7 +1648,13 @@
if (agnssRilIface != nullptr) {
agnssRilIface->setCallback(aGnssRilCbIface);
} else {
- ALOGI("Unable to Initialize AGnss Ril interface\n");
+ ALOGI("Unable to initialize AGnss Ril interface\n");
+ }
+
+ if (gnssVisibilityControlIface != nullptr) {
+ sp<IGnssVisibilityControlCallback> gnssVisibilityControlCbIface =
+ new GnssVisibilityControlCallback();
+ gnssVisibilityControlIface->setCallback(gnssVisibilityControlCbIface);
}
return JNI_TRUE;
@@ -1984,6 +2056,11 @@
return result;
}
+static jboolean android_location_GnssLocationProvider_is_gnss_visibility_control_supported(
+ JNIEnv* /* env */, jclass /* clazz */) {
+ return (gnssVisibilityControlIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+}
+
static void android_location_GnssNetworkConnectivityHandler_update_network_state(JNIEnv* env,
jobject /* obj */,
jboolean connected,
@@ -2567,6 +2644,29 @@
return gnssBatchingIface->stop();
}
+static jboolean android_location_GnssVisibilityControl_enable_nfw_location_access(
+ JNIEnv* env, jobject, jobjectArray proxyApps) {
+ if (gnssVisibilityControlIface == nullptr) {
+ ALOGI("No GNSS Visibility Control interface available");
+ return JNI_FALSE;
+ }
+
+ const jsize length = env->GetArrayLength(proxyApps);
+ hidl_vec<hidl_string> hidlProxyApps(length);
+ for (int i = 0; i < length; ++i) {
+ jstring proxyApp = (jstring) (env->GetObjectArrayElement(proxyApps, i));
+ ScopedJniString jniProxyApp(env, proxyApp);
+ hidlProxyApps[i] = jniProxyApp;
+ }
+
+ auto result = gnssVisibilityControlIface->enableNfwLocationAccess(hidlProxyApps);
+ if (result.isOk()) {
+ return result;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"class_init_native", "()V", reinterpret_cast<void *>(
@@ -2617,6 +2717,8 @@
{"native_get_internal_state",
"()Ljava/lang/String;",
reinterpret_cast<void *>(android_location_GnssLocationProvider_get_internal_state)},
+ {"native_is_gnss_visibility_control_supported", "()Z", reinterpret_cast<void *>(
+ android_location_GnssLocationProvider_is_gnss_visibility_control_supported)},
};
static const JNINativeMethod sMethodsBatching[] = {
@@ -2746,6 +2848,14 @@
reinterpret_cast<void *>(android_location_GnssConfiguration_set_es_extension_sec)},
};
+static const JNINativeMethod sVisibilityControlMethods[] = {
+ /* name, signature, funcPtr */
+ {"native_enable_nfw_location_access",
+ "([Ljava/lang/String;)Z",
+ reinterpret_cast<void *>(
+ android_location_GnssVisibilityControl_enable_nfw_location_access)},
+};
+
int register_android_server_location_GnssLocationProvider(JNIEnv* env) {
jniRegisterNativeMethods(
env,
@@ -2777,6 +2887,11 @@
"com/android/server/location/GnssConfiguration",
sConfigurationMethods,
NELEM(sConfigurationMethods));
+ jniRegisterNativeMethods(
+ env,
+ "com/android/server/location/GnssVisibilityControl",
+ sVisibilityControlMethods,
+ NELEM(sVisibilityControlMethods));
return jniRegisterNativeMethods(
env,
"com/android/server/location/GnssLocationProvider",
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
new file mode 100644
index 0000000..eaab650
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Encapsulating class for using the SQLite database backing the memory store.
+ *
+ * This class groups together the contracts and the SQLite helper used to
+ * use the database.
+ *
+ * @hide
+ */
+public class IpMemoryStoreDatabase {
+ /**
+ * Contract class for the Network Attributes table.
+ */
+ public static class NetworkAttributesContract {
+ public static final String TABLENAME = "NetworkAttributes";
+
+ public static final String COLNAME_L2KEY = "l2Key";
+ public static final String COLTYPE_L2KEY = "TEXT NOT NULL";
+
+ public static final String COLNAME_EXPIRYDATE = "expiryDate";
+ // Milliseconds since the Epoch, in true Java style
+ public static final String COLTYPE_EXPIRYDATE = "BIGINT";
+
+ public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address";
+ public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER";
+
+ // Please note that the group hint is only a *hint*, hence its name. The client can offer
+ // this information to nudge the grouping in the decision it thinks is right, but it can't
+ // decide for the memory store what is the same L3 network.
+ public static final String COLNAME_GROUPHINT = "groupHint";
+ public static final String COLTYPE_GROUPHINT = "TEXT";
+
+ public static final String COLNAME_DNSADDRESSES = "dnsAddresses";
+ // Stored in marshalled form as is
+ public static final String COLTYPE_DNSADDRESSES = "BLOB";
+
+ public static final String COLNAME_MTU = "mtu";
+ public static final String COLTYPE_MTU = "INTEGER";
+
+ public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
+ + TABLENAME + " ("
+ + COLNAME_L2KEY + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
+ + COLNAME_EXPIRYDATE + " " + COLTYPE_EXPIRYDATE + ", "
+ + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", "
+ + COLNAME_GROUPHINT + " " + COLTYPE_GROUPHINT + ", "
+ + COLNAME_DNSADDRESSES + " " + COLTYPE_DNSADDRESSES + ", "
+ + COLNAME_MTU + " " + COLTYPE_MTU + ")";
+ public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
+ }
+
+ /**
+ * Contract class for the Private Data table.
+ */
+ public static class PrivateDataContract {
+ public static final String TABLENAME = "PrivateData";
+
+ public static final String COLNAME_L2KEY = "l2Key";
+ public static final String COLTYPE_L2KEY = "TEXT NOT NULL";
+
+ public static final String COLNAME_CLIENT = "client";
+ public static final String COLTYPE_CLIENT = "TEXT NOT NULL";
+
+ public static final String COLNAME_DATANAME = "dataName";
+ public static final String COLTYPE_DATANAME = "TEXT NOT NULL";
+
+ public static final String COLNAME_DATA = "data";
+ public static final String COLTYPE_DATA = "BLOB NOT NULL";
+
+ public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
+ + TABLENAME + " ("
+ + COLNAME_L2KEY + " " + COLTYPE_L2KEY + ", "
+ + COLNAME_CLIENT + " " + COLTYPE_CLIENT + ", "
+ + COLNAME_DATANAME + " " + COLTYPE_DATANAME + ", "
+ + COLNAME_DATA + " " + COLTYPE_DATA + ", "
+ + "PRIMARY KEY ("
+ + COLNAME_L2KEY + ", "
+ + COLNAME_CLIENT + ", "
+ + COLNAME_DATANAME + "))";
+ public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
+ }
+
+ // To save memory when the DB is not used, close it after 30s of inactivity. This is
+ // determined manually based on what feels right.
+ private static final long IDLE_CONNECTION_TIMEOUT_MS = 30_000;
+
+ /** The SQLite DB helper */
+ public static class DbHelper extends SQLiteOpenHelper {
+ // Update this whenever changing the schema.
+ private static final int SCHEMA_VERSION = 1;
+ private static final String DATABASE_FILENAME = "IpMemoryStore.db";
+
+ public DbHelper(@NonNull final Context context) {
+ super(context, DATABASE_FILENAME, null, SCHEMA_VERSION);
+ setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+ }
+
+ /** Called when the database is created */
+ public void onCreate(@NonNull final SQLiteDatabase db) {
+ db.execSQL(NetworkAttributesContract.CREATE_TABLE);
+ db.execSQL(PrivateDataContract.CREATE_TABLE);
+ }
+
+ /** Called when the database is upgraded */
+ public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
+ final int newVersion) {
+ // No upgrade supported yet.
+ db.execSQL(NetworkAttributesContract.DROP_TABLE);
+ db.execSQL(PrivateDataContract.DROP_TABLE);
+ onCreate(db);
+ }
+
+ /** Called when the database is downgraded */
+ public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
+ final int newVersion) {
+ // Downgrades always nuke all data and recreate an empty table.
+ db.execSQL(NetworkAttributesContract.DROP_TABLE);
+ db.execSQL(PrivateDataContract.DROP_TABLE);
+ onCreate(db);
+ }
+ }
+}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index c9759bf..55a72190 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
import android.net.IIpMemoryStore;
import android.net.ipmemorystore.Blob;
import android.net.ipmemorystore.IOnBlobRetrievedListener;
@@ -27,6 +29,10 @@
import android.net.ipmemorystore.IOnSameNetworkResponseListener;
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.util.Log;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* Implementation for the IP memory store.
@@ -37,10 +43,75 @@
* @hide
*/
public class IpMemoryStoreService extends IIpMemoryStore.Stub {
- final Context mContext;
+ private static final String TAG = IpMemoryStoreService.class.getSimpleName();
+ private static final int MAX_CONCURRENT_THREADS = 4;
+ @NonNull
+ final Context mContext;
+ @Nullable
+ final SQLiteDatabase mDb;
+ @NonNull
+ final ExecutorService mExecutor;
+
+ /**
+ * Construct an IpMemoryStoreService object.
+ * This constructor will block on disk access to open the database.
+ * @param context the context to access storage with.
+ */
public IpMemoryStoreService(@NonNull final Context context) {
+ // Note that constructing the service will access the disk and block
+ // for some time, but it should make no difference to the clients. Because
+ // the interface is one-way, clients fire and forget requests, and the callback
+ // will get called eventually in any case, and the framework will wait for the
+ // service to be created to deliver subsequent requests.
+ // Avoiding this would mean the mDb member can't be final, which means the service would
+ // have to test for nullity, care for failure, and allow for a wait at every single access,
+ // which would make the code a lot more complex and require all methods to possibly block.
mContext = context;
+ SQLiteDatabase db;
+ final IpMemoryStoreDatabase.DbHelper helper = new IpMemoryStoreDatabase.DbHelper(context);
+ try {
+ db = helper.getWritableDatabase();
+ if (null == db) Log.e(TAG, "Unexpected null return of getWriteableDatabase");
+ } catch (final SQLException e) {
+ Log.e(TAG, "Can't open the Ip Memory Store database", e);
+ db = null;
+ } catch (final Exception e) {
+ Log.wtf(TAG, "Impossible exception Ip Memory Store database", e);
+ db = null;
+ }
+ mDb = db;
+ // The work-stealing thread pool executor will spawn threads as needed up to
+ // the max only when there is no free thread available. This generally behaves
+ // exactly like one would expect it intuitively :
+ // - When work arrives, it will spawn a new thread iff there are no available threads
+ // - When there is no work to do it will shutdown threads after a while (the while
+ // being equal to 2 seconds (not configurable) when max threads are spun up and
+ // twice as much for every one less thread)
+ // - When all threads are busy the work is enqueued and waits for any worker
+ // to become available.
+ // Because the stealing pool is made for very heavily parallel execution of
+ // small tasks that spawn others, it creates a queue per thread that in this
+ // case is overhead. However, the three behaviors above make it a superior
+ // choice to cached or fixedThreadPoolExecutor, neither of which can actually
+ // enqueue a task waiting for a thread to be free. This can probably be solved
+ // with judicious subclassing of ThreadPoolExecutor, but that's a lot of dangerous
+ // complexity for little benefit in this case.
+ mExecutor = Executors.newWorkStealingPool(MAX_CONCURRENT_THREADS);
+ }
+
+ /**
+ * Shutdown the memory store service, cancelling running tasks and dropping queued tasks.
+ *
+ * This is provided to give a way to clean up, and is meant to be available in case of an
+ * emergency shutdown.
+ */
+ public void shutdown() {
+ // By contrast with ExecutorService#shutdown, ExecutorService#shutdownNow tries
+ // to cancel the existing tasks, and does not wait for completion. It does not
+ // guarantee the threads can be terminated in any given amount of time.
+ mExecutor.shutdownNow();
+ if (mDb != null) mDb.close();
}
/**
@@ -61,7 +132,7 @@
public void storeNetworkAttributes(@NonNull final String l2Key,
@NonNull final NetworkAttributesParcelable attributes,
@Nullable final IOnStatusListener listener) {
- // TODO : implement this
+ // TODO : implement this.
}
/**
@@ -79,7 +150,7 @@
public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
@NonNull final String name, @NonNull final Blob data,
@Nullable final IOnStatusListener listener) {
- // TODO : implement this
+ // TODO : implement this.
}
/**
@@ -99,7 +170,7 @@
@Override
public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
@NonNull final IOnL2KeyResponseListener listener) {
- // TODO : implement this
+ // TODO : implement this.
}
/**
@@ -114,7 +185,7 @@
@Override
public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
@NonNull final IOnSameNetworkResponseListener listener) {
- // TODO : implement this
+ // TODO : implement this.
}
/**
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java
new file mode 100644
index 0000000..aa45400
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A class containing the logic around the relevance value for
+ * IP Memory Store.
+ *
+ * @hide
+ */
+public class RelevanceUtils {
+ /**
+ * The relevance is a decaying value that gets lower and lower until it
+ * reaches 0 after some time passes. It follows an exponential decay law,
+ * dropping slowly at first then faster and faster, because a network is
+ * likely to be visited again if it was visited not long ago, and the longer
+ * it hasn't been visited the more likely it is that it won't be visited
+ * again. For example, a network visited on holiday should stay fresh for
+ * the duration of the holiday and persist for a while, but after the venue
+ * hasn't been visited for a while it should quickly be discarded. What
+ * should accelerate forgetting the network is extended periods without
+ * visits, so that occasional venues get discarded but regular visits keep
+ * the network relevant, even if the visits are infrequent.
+ *
+ * This function must be stable by iteration, meaning that adjusting the same value
+ * for different dates iteratively multiple times should give the same result.
+ * Formally, if f is the decay function that associates a relevance x at a date d1
+ * to the value at ulterior date d3, then for any date d2 between d1 and d3 :
+ * f(x, d3 - d1) = f(f(x, d3 - d2), d2 - d1). Intuitively, this property simply
+ * means it should be the same to compute and store back the value after two months,
+ * or to do it once after one month, store it back, and do it again after another
+ * months has passed.
+ * The pair of the relevance and date define the entire curve, so any pair
+ * of values on the curve will define the same curve. Setting one of them to a
+ * constant, so as not to have to store it, means the other one will always suffice
+ * to describe the curve. For example, only storing the date for a known, constant
+ * value of the relevance is an efficient way of remembering this information (and
+ * to compare relevances together, as f is monotonically decreasing).
+ *
+ *** Choosing the function :
+ * Functions of the kind described above are standard exponential decay functions
+ * like the ones that govern atomic decay where the value at any given date can be
+ * computed uniformly from the value at a previous date and the time elapsed since
+ * that date. It is simple to picture this kind of function as one where after a
+ * given period of time called the half-life, the relevance value will have been
+ * halved. Decay of this kind is expressed in function of the previous value by
+ * functions like
+ * f(x, t) = x * F ^ (t / L)
+ * ...where x is the value, t is the elapsed time, L is the half-life (or more
+ * generally the F-th-life) and F the decay factor (typically 0.5, hence why L is
+ * usually called the half-life). The ^ symbol here is used for exponentiation.
+ * Or, starting at a given M for t = 0 :
+ * f(t) = M * F ^ (t / L)
+ *
+ * Because a line in the store needs to become irrelevant at some point but
+ * this class of functions never go to 0, a minimum cutoff has to be chosen to
+ * represent irrelevance. The simpler way of doing this is to simply add this
+ * minimum cutoff to the computation before and removing it after.
+ * Thus the function becomes :
+ * f(x, t) = ((x + K) * F ^ (t / L)) - K
+ * ...where K is the minimum cutoff, L the half-life, and F the factor between
+ * the original x and x after its half-life. Strictly speaking using the word
+ * "half-life" implies that F = 0.5, but the relation works for any value of F.
+ *
+ * It is easy enough to check that this function satisfies the stability
+ * relation that was given above for any value of F, L and K, which become
+ * parameters that can be defined at will.
+ *
+ * relevance
+ * 1.0 |
+ * |\
+ * | \
+ * | \ (this graph rendered with L = 75 days and K = 1/40)
+ * 0.75| ',
+ * | \
+ * | '.
+ * | \.
+ * | \
+ * 0.5 | '\
+ * | ''.
+ * | ''.
+ * | ''.
+ * 0.25| '''..
+ * | '''..
+ * | ''''....
+ * | '''''..........
+ * 0 +-------------------------------------------------------''''''''''----
+ * 0 50 100 150 200 250 300 350 400 days
+ *
+ *** Choosing the parameters
+ * The maximum M is an arbitrary parameter that simply scales the curve.
+ * The tradeoff for M is pretty simple : if the relevance is going to be an
+ * integer, the bigger M is the more precision there is in the relevance.
+ * However, values of M that are easy for humans to read are preferable to
+ * help debugging, and a suitably low value may be enough to ensure there
+ * won't be integer overflows in intermediate computations.
+ * A value of 1_000_000 probably is plenty for precision, while still in the
+ * low range of what ints can represent.
+ *
+ * F and L are parameters to be chosen arbitrarily and have an impact on how
+ * fast the relevance will be decaying at first, keeping in mind that
+ * the 400 days value and the cap stay the same. In simpler words, F and L
+ * define the steepness of the curve.
+ * To keep things simple (and familiar) F is arbitrarily chosen to be 0.5, and
+ * L is set to 200 days visually to achieve the desired effect. Refer to the
+ * illustration above to get a feel of how that feels.
+ *
+ * Moreover, the memory store works on an assumption that the relevance should
+ * be capped, and that an entry with capped relevance should decay in 400 days.
+ * This is on premises that the networks a device will need to remember the
+ * longest should be networks visited about once a year.
+ * For this reason, the relevance is at the maximum M 400 days before expiry :
+ * f(M, 400 days) = 0
+ * From replacing this with the value of the function, K can then be derived
+ * from the values of M, F and L :
+ * (M + K) * F ^ (t / L) - K = 0
+ * K = M * F ^ (400 days / L) / (1 - F ^ (400 days / L))
+ * Replacing with actual values this gives :
+ * K = 1_000_000 * 0.5 ^ (400 / 200) / (1 - 0.5 ^ (400 / 200))
+ * = 1_000_000 / 3 ≈ 333_333.3
+ * This ensures the function has the desired profile, the desired value at
+ * cap, and the desired value at expiry.
+ *
+ *** Useful relations
+ * Let's define the expiry time for any given relevance x as the interval of
+ * time such as :
+ * f(x, expiry) = 0
+ * which can be rewritten
+ * ((x + K) * F ^ (expiry / L)) = K
+ * ...giving an expression of the expiry in function of the relevance x as
+ * expiry = L * logF(K / (x + K))
+ * Conversely the relevance x can be expressed in function of the expiry as
+ * x = K / F ^ (expiry / L) - K
+ * These relations are useful in utility functions.
+ *
+ *** Bumping things up
+ * The last issue therefore is to decide how to bump up the relevance. The
+ * simple approach is to simply lift up the curve a little bit by a constant
+ * normalized amount, delaying the time of expiry. For example increasing
+ * the relevance by an amount I gives :
+ * x2 = x1 + I
+ * x2 and x1 correspond to two different expiry times expiry2 and expiry1,
+ * and replacing x1 and x2 in the relation above with their expression in
+ * function of the expiry comes :
+ * K / F ^ (expiry2 / L) - K = K / F ^ (expiry1 / L) - K + I
+ * which resolves to :
+ * expiry2 = L * logF(K / (I + K / F ^ (expiry1 / L)))
+ *
+ * In this implementation, the bump is defined as 1/25th of the cap for
+ * the relevance. This means a network will be remembered for the maximum
+ * period of 400 days if connected 25 times in succession not accounting
+ * for decay. Of course decay actually happens so it will take more than 25
+ * connections for any given network to actually reach the cap, but because
+ * decay is slow at first, it is a good estimate of how fast cap happens.
+ *
+ * Specifically, it gives the following four results :
+ * - A network that a device connects to once hits irrelevance about 32.7 days after
+ * it was first registered if never connected again.
+ * - A network that a device connects to once a day at a fixed hour will hit the cap
+ * on the 27th connection.
+ * - A network that a device connects to once a week at a fixed hour will hit the cap
+ * on the 57th connection.
+ * - A network that a device connects to every day for 7 straight days then never again
+ * expires 144 days after the last connection.
+ * These metrics tend to match pretty well the requirements.
+ */
+
+ // TODO : make these constants configurable at runtime. Don't forget to build it so that
+ // changes will wipe the database, migrate the values, or otherwise make sure the relevance
+ // values are still meaningful.
+
+ // How long, in milliseconds, is a capped relevance valid for, or in other
+ // words how many milliseconds after its relevance was set to RELEVANCE_CAP does
+ // any given line expire. 400 days.
+ @VisibleForTesting
+ public static final long CAPPED_RELEVANCE_LIFETIME_MS = 400L * 24 * 60 * 60 * 1000;
+
+ // The constant that represents a normalized 1.0 value for the relevance. In other words,
+ // the cap for the relevance. This is referred to as M in the explanation above.
+ @VisibleForTesting
+ public static final int CAPPED_RELEVANCE = 1_000_000;
+
+ // The decay factor. After a half-life, the relevance will have decayed by this value.
+ // This is referred to as F in the explanation above.
+ private static final double DECAY_FACTOR = 0.5;
+
+ // The half-life. After this time, the relevance will have decayed by a factor DECAY_FACTOR.
+ // This is referred to as L in the explanation above.
+ private static final long HALF_LIFE_MS = 200L * 24 * 60 * 60 * 1000;
+
+ // The value of the frame change. This is referred to as K in the explanation above.
+ private static final double IRRELEVANCE_FLOOR =
+ CAPPED_RELEVANCE * powF((double) CAPPED_RELEVANCE_LIFETIME_MS / HALF_LIFE_MS)
+ / (1 - powF((double) CAPPED_RELEVANCE_LIFETIME_MS / HALF_LIFE_MS));
+
+ // How much to bump the relevance by every time a line is written to.
+ @VisibleForTesting
+ public static final int RELEVANCE_BUMP = CAPPED_RELEVANCE / 25;
+
+ // Java doesn't include a function for the logarithm in an arbitrary base, so implement it
+ private static final double LOG_DECAY_FACTOR = Math.log(DECAY_FACTOR);
+ private static double logF(final double value) {
+ return Math.log(value) / LOG_DECAY_FACTOR;
+ }
+
+ // Utility function to get a power of the decay factor, to simplify the code.
+ private static double powF(final double value) {
+ return Math.pow(DECAY_FACTOR, value);
+ }
+
+ /**
+ * Compute the value of the relevance now given an expiry date.
+ *
+ * @param expiry the date at which the column in the database expires.
+ * @return the adjusted value of the relevance for this moment in time.
+ */
+ public static int computeRelevanceForNow(final long expiry) {
+ return computeRelevanceForTargetDate(expiry, System.currentTimeMillis());
+ }
+
+ /**
+ * Compute the value of the relevance at a given date from an expiry date.
+ *
+ * Because relevance decays with time, a relevance in the past corresponds to
+ * a different relevance later.
+ *
+ * Relevance is always a positive value. 0 means not relevant at all.
+ *
+ * See the explanation at the top of this file to get the justification for this
+ * computation.
+ *
+ * @param expiry the date at which the column in the database expires.
+ * @param target the target date to adjust the relevance to.
+ * @return the adjusted value of the relevance for the target moment.
+ */
+ public static int computeRelevanceForTargetDate(final long expiry, final long target) {
+ final long delay = expiry - target;
+ if (delay >= CAPPED_RELEVANCE_LIFETIME_MS) return CAPPED_RELEVANCE;
+ if (delay <= 0) return 0;
+ return (int) (IRRELEVANCE_FLOOR / powF((float) delay / HALF_LIFE_MS) - IRRELEVANCE_FLOOR);
+ }
+
+ /**
+ * Compute the expiry duration adjusted up for a new fresh write.
+ *
+ * Every time data is written to the memory store for a given line, the
+ * relevance is bumped up by a certain amount, which will boost the priority
+ * of this line for computation of group attributes, and delay (possibly
+ * indefinitely, if the line is accessed regularly) forgetting the data stored
+ * in that line.
+ * As opposed to bumpExpiryDate, this function uses a duration from now to expiry.
+ *
+ * See the explanation at the top of this file for a justification of this computation.
+ *
+ * @param oldExpiryDuration the old expiry duration in milliseconds from now.
+ * @return the expiry duration representing a bumped up relevance value.
+ */
+ public static long bumpExpiryDuration(final long oldExpiryDuration) {
+ // L * logF(K / (I + K / F ^ (expiry1 / L))), as documented above
+ final double divisionFactor = powF(((double) oldExpiryDuration) / HALF_LIFE_MS);
+ final double oldRelevance = IRRELEVANCE_FLOOR / divisionFactor;
+ final long newDuration =
+ (long) (HALF_LIFE_MS * logF(IRRELEVANCE_FLOOR / (RELEVANCE_BUMP + oldRelevance)));
+ return Math.min(newDuration, CAPPED_RELEVANCE_LIFETIME_MS);
+ }
+
+ /**
+ * Compute the new expiry date adjusted up for a new fresh write.
+ *
+ * Every time data is written to the memory store for a given line, the
+ * relevance is bumped up by a certain amount, which will boost the priority
+ * of this line for computation of group attributes, and delay (possibly
+ * indefinitely, if the line is accessed regularly) forgetting the data stored
+ * in that line.
+ * As opposed to bumpExpiryDuration, this function takes the old timestamp and returns the
+ * new timestamp.
+ *
+ * {@see bumpExpiryDuration}, and keep in mind that the bump depends on when this is called,
+ * because the relevance decays exponentially, therefore bumping up a high relevance (for a
+ * date far in the future) is less potent than bumping up a low relevance (for a date in
+ * a close future).
+ *
+ * @param oldExpiryDate the old date of expiration.
+ * @return the new expiration date after the relevance bump.
+ */
+ public static long bumpExpiryDate(final long oldExpiryDate) {
+ final long now = System.currentTimeMillis();
+ final long newDuration = bumpExpiryDuration(oldExpiryDate - now);
+ return now + newDuration;
+ }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
index 7559560..9a6e003 100644
--- a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
@@ -58,7 +58,9 @@
import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.transport.TransportClientManager;
import com.android.server.backup.transport.TransportNotRegisteredException;
+import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,6 +68,7 @@
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowPackageManager;
import java.util.ArrayList;
@@ -75,6 +78,7 @@
import java.util.stream.Stream;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class})
@Presubmit
public class TransportManagerTest {
private static final String PACKAGE_A = "some.package.a";
@@ -102,6 +106,12 @@
mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz");
}
+ /** Reset shadow state. */
+ @After
+ public void tearDown() throws Exception {
+ ShadowApplicationPackageManager.reset();
+ }
+
@Test
public void testRegisterTransports() throws Exception {
setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
@@ -666,7 +676,8 @@
packageInfo.packageName = packageName;
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.privateFlags = flags;
- mShadowPackageManager.addPackage(packageInfo);
+ mShadowPackageManager.installPackage(packageInfo);
+ ShadowApplicationPackageManager.addInstalledPackage(packageName, packageInfo);
}
private TransportManager createTransportManagerWithRegisteredTransports(
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 3b7fa3d..427aed7 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -514,10 +514,14 @@
private void setUpForUpdateTransportAttributes() throws Exception {
mTransportComponent = mTransport.getTransportComponent();
String transportPackage = mTransportComponent.getPackageName();
+ PackageInfo packageInfo = getPackageInfo(transportPackage);
ShadowPackageManager shadowPackageManager = shadowOf(mContext.getPackageManager());
- shadowPackageManager.addPackage(transportPackage);
+ shadowPackageManager.installPackage(packageInfo);
shadowPackageManager.setPackagesForUid(PACKAGE_UID, transportPackage);
+ // Set up for user invocations on ApplicationPackageManager.
+ ShadowApplicationPackageManager.addInstalledPackage(transportPackage, packageInfo);
+ ShadowApplicationPackageManager.setPackageUid(transportPackage, PACKAGE_UID);
mTransportUid = mContext.getPackageManager().getPackageUid(transportPackage, 0);
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
index dc32209..ab121ed 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
@@ -38,6 +38,7 @@
extends org.robolectric.shadows.ShadowApplicationPackageManager {
private static final Map<String, PackageInfo> sPackageInfos = new ArrayMap<>();
private static final List<PackageInfo> sInstalledPackages = new ArrayList<>();
+ private static final Map<String, Integer> sPackageUids = new ArrayMap<>();
/**
* Registers the package {@code packageName} to be returned when invoking {@link
@@ -49,6 +50,14 @@
sInstalledPackages.add(packageInfo);
}
+ /**
+ * Sets the package uid {@code packageUid} for the package {@code packageName} to be returned
+ * when invoking {@link ApplicationPackageManager#getPackageUidAsUser(String, int, int)}.
+ */
+ public static void setPackageUid(String packageName, int packageUid) {
+ sPackageUids.put(packageName, packageUid);
+ }
+
@Override
protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
throws NameNotFoundException {
@@ -63,6 +72,15 @@
return sInstalledPackages;
}
+ @Override
+ protected int getPackageUidAsUser(String packageName, int flags, int userId)
+ throws NameNotFoundException {
+ if (!sPackageUids.containsKey(packageName)) {
+ throw new NameNotFoundException(packageName);
+ }
+ return sPackageUids.get(packageName);
+ }
+
/** Clear package state. */
@Resetter
public static void reset() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index f1cd0cd..57ee6dc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -33,6 +33,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -43,7 +44,12 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
+import android.app.AppGlobals;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
import android.app.job.JobInfo;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
@@ -56,13 +62,16 @@
import android.os.BatteryManagerInternal;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.SparseBooleanArray;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.job.JobStore;
import com.android.server.job.controllers.QuotaController.ExecutionStats;
import com.android.server.job.controllers.QuotaController.TimingSession;
@@ -96,9 +105,13 @@
private BroadcastReceiver mChargingReceiver;
private Constants mConstants;
private QuotaController mQuotaController;
+ private int mSourceUid;
+ private IUidObserver mUidObserver;
private MockitoSession mMockingSession;
@Mock
+ private ActivityManagerInternal mActivityMangerInternal;
+ @Mock
private AlarmManager mAlarmManager;
@Mock
private Context mContext;
@@ -107,6 +120,8 @@
@Mock
private UsageStatsManagerInternal mUsageStatsManager;
+ private JobStore mJobStore;
+
@Before
public void setUp() {
mMockingSession = mockitoSession()
@@ -123,8 +138,17 @@
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
// Called in QuotaController constructor.
+ IActivityManager activityManager = ActivityManager.getService();
+ spyOn(activityManager);
+ try {
+ doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
+ } catch (RemoteException e) {
+ fail("registerUidObserver threw exception: " + e.getMessage());
+ }
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+ doReturn(mActivityMangerInternal)
+ .when(() -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mock(BatteryManagerInternal.class))
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
doReturn(mUsageStatsManager)
@@ -132,6 +156,9 @@
// Used in JobStatus.
doReturn(mock(PackageManagerInternal.class))
.when(() -> LocalServices.getService(PackageManagerInternal.class));
+ // Used in QuotaController.Handler.
+ mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
+ when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
// Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
// in the past, and QuotaController sometimes floors values at 0, so if the test time
@@ -150,10 +177,23 @@
// Capture the listeners.
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor<IUidObserver> uidObserverCaptor =
+ ArgumentCaptor.forClass(IUidObserver.class);
mQuotaController = new QuotaController(mJobSchedulerService);
verify(mContext).registerReceiver(receiverCaptor.capture(), any());
mChargingReceiver = receiverCaptor.getValue();
+ try {
+ verify(activityManager).registerUidObserver(
+ uidObserverCaptor.capture(),
+ eq(ActivityManager.UID_OBSERVER_PROCSTATE),
+ eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE),
+ any());
+ mUidObserver = uidObserverCaptor.getValue();
+ mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
}
@After
@@ -182,6 +222,25 @@
mChargingReceiver.onReceive(mContext, intent);
}
+ private void setProcessState(int procState) {
+ try {
+ doReturn(procState).when(mActivityMangerInternal).getUidProcessState(mSourceUid);
+ SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
+ spyOn(foregroundUids);
+ mUidObserver.onUidStateChanged(mSourceUid, procState, 0);
+ if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1))
+ .put(eq(mSourceUid), eq(true));
+ assertTrue(foregroundUids.get(mSourceUid));
+ } else {
+ verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1)).delete(eq(mSourceUid));
+ assertFalse(foregroundUids.get(mSourceUid));
+ }
+ } catch (RemoteException e) {
+ fail("registerUidObserver threw exception: " + e.getMessage());
+ }
+ }
+
private void setStandbyBucket(int bucketIndex) {
int bucket;
switch (bucketIndex) {
@@ -204,9 +263,18 @@
anyLong())).thenReturn(bucket);
}
- private void setStandbyBucket(int bucketIndex, JobStatus job) {
+ private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
setStandbyBucket(bucketIndex);
- job.setStandbyBucket(bucketIndex);
+ for (JobStatus job : jobs) {
+ job.setStandbyBucket(bucketIndex);
+ }
+ }
+
+ private void trackJobs(JobStatus... jobs) {
+ for (JobStatus job : jobs) {
+ mJobStore.add(job);
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ }
}
private JobStatus createJobStatus(String testTag, int jobId) {
@@ -214,8 +282,11 @@
new ComponentName(mContext, "TestQuotaJobService"))
.setMinimumLatency(Math.abs(jobId) + 1)
.build();
- return JobStatus.createFromJobInfo(
+ JobStatus js = JobStatus.createFromJobInfo(
jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
+ js.setStandbyBucket(FREQUENT_INDEX);
+ return js;
}
private TimingSession createTimingSession(long start, long duration, int count) {
@@ -709,6 +780,7 @@
verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
mQuotaController.prepareForExecutionLocked(jobStatus);
advanceElapsedClock(5 * MINUTE_IN_MILLIS);
@@ -1339,19 +1411,23 @@
setDischarging();
JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
- jobStatus.uidActive = true;
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
mQuotaController.prepareForExecutionLocked(jobStatus);
advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ // Change to a state that should still be considered foreground.
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
/**
- * Tests that Timers properly track overlapping foreground and background jobs.
+ * Tests that Timers properly track sessions when switching between foreground and background
+ * states.
*/
@Test
public void testTimerTracking_ForegroundAndBackground() {
@@ -1360,7 +1436,6 @@
JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
- jobFg3.uidActive = true;
mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
@@ -1368,6 +1443,7 @@
List<TimingSession> expected = new ArrayList<>();
// UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
long start = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.prepareForExecutionLocked(jobBg1);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
@@ -1379,48 +1455,223 @@
// Bg job starts while inactive, spans an entire active session, and ends after the
// active session.
- // Fg job starts after the bg job and ends before the bg job.
- // Entire bg job duration should be counted since it started before active session. However,
- // count should only be 1 since Timer shouldn't count fg jobs.
+ // App switching to foreground state then fg job starts.
+ // App remains in foreground state after coming to foreground, so there should only be one
+ // session.
start = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
mQuotaController.prepareForExecutionLocked(jobBg2);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
mQuotaController.prepareForExecutionLocked(jobFg3);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
- expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1));
assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
advanceElapsedClock(SECOND_IN_MILLIS);
// Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
// "inactive" and then bg job 2 starts. Then fg job ends.
- // This should result in two TimingSessions with a count of one each.
+ // This should result in two TimingSessions:
+ // * The first should have a count of 1
+ // * The second should have a count of 2 since it will include both jobs
start = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
+ setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
mQuotaController.prepareForExecutionLocked(jobBg1);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
mQuotaController.prepareForExecutionLocked(jobFg3);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
- expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
mQuotaController.prepareForExecutionLocked(jobBg2);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
- expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
+ expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
/**
+ * Tests that Timers properly track overlapping top and background jobs.
+ */
+ @Test
+ public void testTimerTracking_TopAndNonTop() {
+ setDischarging();
+
+ JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1);
+ JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2);
+ JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3);
+ JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ List<TimingSession> expected = new ArrayList<>();
+
+ // UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ // Bg job starts while inactive, spans an entire active session, and ends after the
+ // active session.
+ // App switching to top state then fg job starts.
+ // App remains in top state after coming to top, so there should only be one
+ // session.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
+ // foreground_service and a new job starts. Shortly after, uid goes
+ // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
+ // This should result in two TimingSessions:
+ // * The first should have a count of 1
+ // * The second should have a count of 2, which accounts for the bg2 and fg, but not top
+ // jobs.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+ setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mQuotaController.prepareForExecutionLocked(jobFg1);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+ expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ /**
+ * Tests that TOP jobs aren't stopped when an app runs out of quota.
+ */
+ @Test
+ public void testTracking_OutOfQuota_ForegroundAndBackground() {
+ setDischarging();
+
+ JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
+ JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
+ trackJobs(jobBg, jobTop);
+ setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
+ // Now the package only has 20 seconds to run.
+ final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+ 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+
+ // UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ mQuotaController.prepareForExecutionLocked(jobBg);
+ advanceElapsedClock(remainingTimeMs / 2);
+ // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+ // should continue to have remainingTimeMs / 2 time remaining.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ advanceElapsedClock(remainingTimeMs);
+
+ // Wait for some extra time to allow for job processing.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged();
+ assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg));
+ assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop));
+ // Go to a background state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ advanceElapsedClock(remainingTimeMs / 2 + 1);
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged();
+ // Top job should still be allowed to run.
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // New jobs to run.
+ JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
+ JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
+ JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
+ setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged();
+ trackJobs(jobFg, jobTop);
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // App still in foreground so everything should be in quota.
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged();
+ // App is now in background and out of quota. Fg should now change to out of quota since it
+ // wasn't started. Top should remain in quota since it started when the app was in TOP.
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ trackJobs(jobBg2);
+ assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ }
+
+ /**
* Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
* its quota.
*/
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 823b7a5..3ebc6ad 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -544,6 +544,18 @@
switchUser(-1, UserHandle.of(startUser), false);
}
+ public void testSwitchUserByHandle_ThrowsException() {
+ synchronized (mUserSwitchLock) {
+ try {
+ ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ am.switchUser(null);
+ fail("Expected IllegalArgumentException on passing in a null UserHandle.");
+ } catch (IllegalArgumentException expected) {
+ // Do nothing - exception is expected.
+ }
+ }
+ }
+
@MediumTest
public void testConcurrentUserCreate() throws Exception {
int userCount = mUserManager.getUserCount();
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
index 3b6b48b..f817e8e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
@@ -77,7 +77,6 @@
@Mock IPackageManager mPM;
@Mock Installer mInstaller;
- private final Object mInstallLock = new Object();
private PackageDynamicCodeLoading mPackageDynamicCodeLoading;
private DexLogger mDexLogger;
@@ -103,7 +102,7 @@
};
// For test purposes capture log messages as well as sending to the event log.
- mDexLogger = new DexLogger(mPM, mInstaller, mInstallLock, mPackageDynamicCodeLoading) {
+ mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) {
@Override
void writeDclEvent(int uid, String message) {
super.writeDclEvent(uid, message);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8f9d2ba..d1609c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -41,9 +41,11 @@
import android.app.ActivityOptions;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PauseActivityItem;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
import android.util.MutableBoolean;
import androidx.test.filters.MediumTest;
@@ -251,4 +253,72 @@
assertEquals(prevSeq + 1, appWindowTokenRequestedOrientation.seq);
verify(mActivity.mAppWindowToken).onMergedOverrideConfigurationChanged();
}
+
+ @Test
+ public void testSetsRelaunchReason_NotDragResizing() {
+ mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
+
+ mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
+ mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+ mActivity.getConfiguration()));
+
+ mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION;
+ final Configuration newConfig = new Configuration(mTask.getConfiguration());
+ newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+ ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+ mTask.onRequestedOverrideConfigurationChanged(newConfig);
+
+ mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+
+ mActivity.ensureActivityConfiguration(0, false, false);
+
+ assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE,
+ mActivity.mRelaunchReason);
+ }
+
+ @Test
+ public void testSetsRelaunchReason_DragResizing() {
+ mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
+
+ mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
+ mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+ mActivity.getConfiguration()));
+
+ mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION;
+ final Configuration newConfig = new Configuration(mTask.getConfiguration());
+ newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+ ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+ mTask.onRequestedOverrideConfigurationChanged(newConfig);
+
+ doReturn(true).when(mTask.getTask()).isDragResizing();
+
+ mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+
+ mActivity.ensureActivityConfiguration(0, false, false);
+
+ assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE,
+ mActivity.mRelaunchReason);
+ }
+
+ @Test
+ public void testSetsRelaunchReason_NonResizeConfigChanges() {
+ mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
+
+ mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
+ mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+ mActivity.getConfiguration()));
+
+ mActivity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
+ final Configuration newConfig = new Configuration(mTask.getConfiguration());
+ newConfig.fontScale = 5;
+ mTask.onRequestedOverrideConfigurationChanged(newConfig);
+
+ mActivity.mRelaunchReason =
+ ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+
+ mActivity.ensureActivityConfiguration(0, false, false);
+
+ assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_NONE,
+ mActivity.mRelaunchReason);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index b2a2869..9d5f687 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -36,6 +36,8 @@
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
+import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
+import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.google.common.truth.Truth.assertThat;
@@ -687,6 +689,62 @@
}
@Test
+ public void testHandleAppDied_RelaunchesAfterCrashDuringWindowingModeResize() {
+ final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+ activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+ activity.launchCount = 1;
+ activity.haveState = false;
+
+ mStack.handleAppDiedLocked(activity.app);
+
+ assertEquals(1, mTask.mActivities.size());
+ assertEquals(1, mStack.getAllTasks().size());
+ }
+
+ @Test
+ public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringWindowingModeResize() {
+ final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+ activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+ activity.launchCount = 3;
+ activity.haveState = false;
+
+ mStack.handleAppDiedLocked(activity.app);
+
+ assertThat(mTask.mActivities).isEmpty();
+ assertThat(mStack.getAllTasks()).isEmpty();
+ }
+
+ @Test
+ public void testHandleAppDied_RelaunchesAfterCrashDuringFreeResize() {
+ final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+ activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
+ activity.launchCount = 1;
+ activity.haveState = false;
+
+ mStack.handleAppDiedLocked(activity.app);
+
+ assertEquals(1, mTask.mActivities.size());
+ assertEquals(1, mStack.getAllTasks().size());
+ }
+
+ @Test
+ public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringFreeResize() {
+ final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+ activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
+ activity.launchCount = 3;
+ activity.haveState = false;
+
+ mStack.handleAppDiedLocked(activity.app);
+
+ assertThat(mTask.mActivities).isEmpty();
+ assertThat(mStack.getAllTasks()).isEmpty();
+ }
+
+ @Test
public void testFinishCurrentActivity() {
// Create 2 activities on a new display.
final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index fa42289..51bebbb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,7 +39,9 @@
import androidx.test.filters.SmallTest;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
/**
@@ -52,15 +54,34 @@
@Presubmit
public class AppTransitionTests extends WindowTestsBase {
+ private static RootWindowContainer sOriginalRootWindowContainer;
+
private DisplayContent mDc;
+ @BeforeClass
+ public static void setUpRootWindowContainerMock() {
+ final WindowManagerService wm = WmServiceUtils.getWindowManagerService();
+ // For unit test, we don't need to test performSurfacePlacement to prevent some abnormal
+ // interaction with surfaceflinger native side.
+ sOriginalRootWindowContainer = wm.mRoot;
+ // Creating spied mock of RootWindowContainer shouldn't be done in @Before, since it will
+ // create unnecessary nested spied objects chain, because WindowManagerService object under
+ // test is a single instance shared among all tests that extend WindowTestsBase class.
+ // Instead it should be done once before running all tests in this test class.
+ wm.mRoot = spy(wm.mRoot);
+ doNothing().when(wm.mRoot).performSurfacePlacement(anyBoolean());
+ }
+
+ @AfterClass
+ public static void tearDownRootWindowContainerMock() {
+ final WindowManagerService wm = WmServiceUtils.getWindowManagerService();
+ wm.mRoot = sOriginalRootWindowContainer;
+ sOriginalRootWindowContainer = null;
+ }
+
@Before
public void setUp() throws Exception {
mDc = mWm.getDefaultDisplayContentLocked();
- // For unit test, we don't need to test performSurfacePlacement to prevent some
- // abnormal interaction with surfaceflinger native side.
- mWm.mRoot = spy(mWm.mRoot);
- doNothing().when(mWm.mRoot).performSurfacePlacement(anyBoolean());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 3826fac..f399463 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -59,6 +59,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.view.DisplayCutout;
+import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.Surface;
@@ -584,15 +585,17 @@
@Test
public void testOnDescendantOrientationRequestChanged() {
- final DisplayContent dc = createNewDisplay();
+ final DisplayInfo info = new DisplayInfo();
+ info.logicalWidth = 1080;
+ info.logicalHeight = 1920;
+ info.logicalDensityDpi = 240;
+ final DisplayContent dc = createNewDisplay(info);
+ dc.configureDisplayPolicy();
mWm.mAtmService.mRootActivityContainer = mock(RootActivityContainer.class);
- final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
- ? SCREEN_ORIENTATION_PORTRAIT
- : SCREEN_ORIENTATION_LANDSCAPE;
final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
window.getTask().mTaskRecord = mock(TaskRecord.class, ExtendedMockito.RETURNS_DEEP_STUBS);
- window.mAppToken.setOrientation(newOrientation);
+ window.mAppToken.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
ActivityRecord activityRecord = mock(ActivityRecord.class);
@@ -603,21 +606,22 @@
verify(mWm.mAtmService).updateDisplayOverrideConfigurationLocked(captor.capture(),
same(activityRecord), anyBoolean(), eq(dc.getDisplayId()));
final Configuration newDisplayConfig = captor.getValue();
- assertEquals(Configuration.ORIENTATION_PORTRAIT, newDisplayConfig.orientation);
+ assertEquals(Configuration.ORIENTATION_LANDSCAPE, newDisplayConfig.orientation);
}
@Test
public void testOnDescendantOrientationRequestChanged_FrozenToUserRotation() {
- final DisplayContent dc = createNewDisplay();
+ final DisplayInfo info = new DisplayInfo();
+ info.logicalWidth = 1080;
+ info.logicalHeight = 1920;
+ info.logicalDensityDpi = 240;
+ final DisplayContent dc = createNewDisplay(info);
dc.getDisplayRotation().setFixedToUserRotation(true);
mWm.mAtmService.mRootActivityContainer = mock(RootActivityContainer.class);
- final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
- ? SCREEN_ORIENTATION_PORTRAIT
- : SCREEN_ORIENTATION_LANDSCAPE;
final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
window.getTask().mTaskRecord = mock(TaskRecord.class, ExtendedMockito.RETURNS_DEEP_STUBS);
- window.mAppToken.setOrientation(newOrientation);
+ window.mAppToken.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
ActivityRecord activityRecord = mock(ActivityRecord.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 198e7ce..d05711e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -33,7 +33,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.ContentResolver;
@@ -612,23 +611,6 @@
// ========================
// Non-rotation API Tests
// ========================
- @Test
- public void testRespectsAppRequestedOrientationByDefault() throws Exception {
- mBuilder.build();
-
- assertTrue("Display rotation should respect app requested orientation by"
- + " default.", mTarget.respectAppRequestedOrientation());
- }
-
- @Test
- public void testNotRespectAppRequestedOrientation_FixedToUserRotation() throws Exception {
- mBuilder.build();
- mTarget.setFixedToUserRotation(true);
-
- assertFalse("Display rotation shouldn't respect app requested orientation if"
- + " fixed to user rotation.", mTarget.respectAppRequestedOrientation());
- }
-
/**
* Call {@link DisplayRotation#configure(int, int, int, int)} to configure {@link #mTarget}
* according to given parameters.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 8a98cbe..1c33dfb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -25,6 +25,13 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.hamcrest.Matchers.not;
@@ -46,6 +53,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.Xml;
import android.view.DisplayInfo;
+import android.view.Surface;
import androidx.test.filters.MediumTest;
@@ -217,12 +225,17 @@
info.logicalHeight = fullScreenBounds.height();
ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null);
+ // Override display orientation. Normally this is available via DisplayContent, but DC
+ // is mocked-out.
+ display.getRequestedOverrideConfiguration().orientation =
+ Configuration.ORIENTATION_LANDSCAPE;
+ display.onRequestedOverrideConfigurationChanged(
+ display.getRequestedOverrideConfiguration());
ActivityStack stack = new StackBuilder(mRootActivityContainer)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
TaskRecord task = stack.getChildAt(0);
- ActivityRecord root = task.getRootActivity();
- ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
- assertEquals(root, task.getRootActivity());
+ ActivityRecord root = task.getTopActivity();
+ assertEquals(root, task.getTopActivity());
assertEquals(fullScreenBounds, task.getBounds());
@@ -233,16 +246,22 @@
assertTrue(task.getBounds().width() < task.getBounds().height());
assertEquals(fullScreenBounds.height(), task.getBounds().height());
- // Setting non-root app has no effect
- setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
- assertTrue(task.getBounds().width() < task.getBounds().height());
+ // Top activity gets used
+ ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
+ assertEquals(top, task.getTopActivity());
+ setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE);
+ assertTrue(task.getBounds().width() > task.getBounds().height());
+ assertEquals(task.getBounds().width(), fullScreenBounds.width());
// Setting app to unspecified restores
- setActivityRequestedOrientation(root, SCREEN_ORIENTATION_UNSPECIFIED);
+ setActivityRequestedOrientation(top, SCREEN_ORIENTATION_UNSPECIFIED);
assertEquals(fullScreenBounds, task.getBounds());
// Setting app to fixed landscape and changing display
- setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
+ setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE);
+ // simulate display orientation changing (normally done via DisplayContent)
+ display.getRequestedOverrideConfiguration().orientation =
+ Configuration.ORIENTATION_PORTRAIT;
display.setBounds(fullScreenBoundsPort);
assertTrue(task.getBounds().width() > task.getBounds().height());
assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
@@ -265,6 +284,53 @@
assertEquals(freeformBounds, task.getBounds());
}
+ @Test
+ public void testUpdatesForcedOrientationInBackground() {
+ final DisplayInfo info = new DisplayInfo();
+ info.logicalWidth = 1920;
+ info.logicalHeight = 1080;
+ final ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
+ doCallRealMethod().when(display.mDisplayContent).setDisplayRotation(any());
+ display.mDisplayContent.setDisplayRotation(mock(DisplayRotation.class));
+ doCallRealMethod().when(display.mDisplayContent).onDescendantOrientationChanged(any(),
+ any());
+ doCallRealMethod().when(display.mDisplayContent).setRotation(anyInt());
+ doAnswer(invocation -> {
+ display.mDisplayContent.setRotation(Surface.ROTATION_0);
+ return null;
+ }).when(display.mDisplayContent).updateOrientationFromAppTokens(any(), any(), anyBoolean());
+
+ final ActivityStack stack = new StackBuilder(mRootActivityContainer)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+ final TaskRecord task = stack.getChildAt(0);
+ final ActivityRecord activity = task.getRootActivity();
+
+ // Wire up app window token and task.
+ doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt(), any(), any());
+ doCallRealMethod().when(activity.mAppWindowToken).onDescendantOrientationChanged(any(),
+ any());
+ doReturn(task.mTask).when(activity.mAppWindowToken).getParent();
+
+ // Wire up task and stack.
+ task.mTask.mTaskRecord = task;
+ doCallRealMethod().when(task.mTask).onDescendantOrientationChanged(any(), any());
+ doReturn(stack.getWindowContainerController().mContainer).when(task.mTask).getParent();
+
+ // Wire up stack and display content.
+ doCallRealMethod().when(stack.mWindowContainerController.mContainer)
+ .onDescendantOrientationChanged(any(), any());
+ doReturn(display.mDisplayContent).when(stack.mWindowContainerController.mContainer)
+ .getParent();
+
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ assertTrue("Bounds of the task should be pillarboxed.",
+ task.getBounds().width() < task.getBounds().height());
+
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ assertTrue("Bounds of the task should be fullscreen.",
+ task.getBounds().equals(new Rect(0, 0, 1920, 1080)));
+ }
+
/** Ensures that the alias intent won't have target component resolved. */
@Test
public void testTaskIntentActivityAlias() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index bbf3d45..613c4ff 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -371,14 +371,15 @@
}
private boolean shouldEnableService(Context context) {
- // VoiceInteractionService should not be enabled on any low RAM devices
- // or devices that have not declared the recognition feature, unless the
- // device's configuration has explicitly set the config flag for a fixed
+ // VoiceInteractionService should not be enabled on devices that have not declared the
+ // recognition feature (including low-ram devices where notLowRam="true" takes effect),
+ // unless the device's configuration has explicitly set the config flag for a fixed
// voice interaction service.
- return (!ActivityManager.isLowRamDeviceStatic()
- && context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_VOICE_RECOGNIZERS)) ||
- getForceVoiceInteractionServicePackage(context.getResources()) != null;
+ if (getForceVoiceInteractionServicePackage(context.getResources()) != null) {
+ return true;
+ }
+ return context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS);
}
private String getForceVoiceInteractionServicePackage(Resources res) {
diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java
index fe07370..4da79b3 100644
--- a/telephony/java/android/telephony/AvailableNetworkInfo.java
+++ b/telephony/java/android/telephony/AvailableNetworkInfo.java
@@ -110,6 +110,7 @@
private AvailableNetworkInfo(Parcel in) {
mSubId = in.readInt();
mPriority = in.readInt();
+ mMccMncs = new ArrayList<>();
in.readStringList(mMccMncs);
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index babeb7b..3311218 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9963,7 +9963,7 @@
boolean ret = false;
try {
IOns iOpportunisticNetworkService = getIOns();
- if (iOpportunisticNetworkService != null) {
+ if (iOpportunisticNetworkService != null && availableNetworks != null) {
ret = iOpportunisticNetworkService.updateAvailableNetworks(availableNetworks,
pkgForDebug);
}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 96f7a1b..3408291 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -92,6 +92,7 @@
public static final int EVENT_DATA_RECONNECT = BASE + 47;
public static final int EVENT_ROAMING_SETTING_CHANGE = BASE + 48;
public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
+ public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 50;
/***** Constants *****/
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 2a648bd..8523554 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -501,4 +501,18 @@
*/
public static final String ACTION_LINE1_NUMBER_ERROR_DETECTED =
"com.android.internal.telephony.ACTION_LINE1_NUMBER_ERROR_DETECTED";
+
+ /**
+ * Broadcast action to notify radio bug.
+ *
+ * Requires the READ_PRIVILEGED_PHONE_STATE permission.
+ *
+ * @hide
+ */
+ public static final String ACTION_REPORT_RADIO_BUG =
+ "com.android.internal.telephony.ACTION_REPORT_RADIO_BUG";
+
+ // ACTION_REPORT_RADIO_BUG extra keys
+ public static final String EXTRA_SLOT_ID = "slotId";
+ public static final String EXTRA_RADIO_BUG_TYPE = "radioBugType";
}
diff --git a/test-base/api/current.txt b/test-base/api/current.txt
index 7ebd6aa..91fcca5 100644
--- a/test-base/api/current.txt
+++ b/test-base/api/current.txt
@@ -48,6 +48,9 @@
method public abstract void startTiming(boolean);
}
+ public abstract deprecated class RepetitiveTest implements java.lang.annotation.Annotation {
+ }
+
public abstract deprecated class UiThreadTest implements java.lang.annotation.Annotation {
}
diff --git a/test-base/src/android/test/RepetitiveTest.java b/test-base/src/android/test/RepetitiveTest.java
index 6a7130e..13e89d2 100644
--- a/test-base/src/android/test/RepetitiveTest.java
+++ b/test-base/src/android/test/RepetitiveTest.java
@@ -26,8 +26,10 @@
* When the annotation is present, the test method is executed the number of times specified by
* numIterations and defaults to 1.
*
- * {@hide} Not needed for public API.
+ * @deprecated New tests should be written using the
+ * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
*/
+@Deprecated
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepetitiveTest {
@@ -37,4 +39,4 @@
* @return The total number of iterations, the default is 1.
*/
int numIterations() default 1;
-}
\ No newline at end of file
+}
diff --git a/test-legacy/Android.bp b/test-legacy/Android.bp
index 833c714..a69f422 100644
--- a/test-legacy/Android.bp
+++ b/test-legacy/Android.bp
@@ -25,7 +25,7 @@
static_libs: [
"android.test.base-minus-junit",
"android.test.runner-minus-junit",
- "android.test.mock.impl",
+ "android.test.mock_static",
],
no_framework_libs: true,
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index e1d6e01..43b765d 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -30,3 +30,19 @@
srcs_lib_whitelist_pkgs: ["android"],
compile_dex: true,
}
+
+// Build the android.test.mock_static library
+// ==========================================
+// This is only intended for inclusion in the legacy-android-test.
+// Must not be used elewhere.
+java_library_static {
+ name: "android.test.mock_static",
+
+ java_version: "1.8",
+ srcs: ["src/**/*.java"],
+
+ no_framework_libs: true,
+ libs: [
+ "framework",
+ ],
+}
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index 932fee0..299fbef 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -849,6 +849,18 @@
assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
}
+ private void assertParcelingIsLossless(LinkProperties source) {
+ Parcel p = Parcel.obtain();
+ source.writeToParcel(p, /* flags */ 0);
+ p.setDataPosition(0);
+ final byte[] marshalled = p.marshall();
+ p = Parcel.obtain();
+ p.unmarshall(marshalled, 0, marshalled.length);
+ p.setDataPosition(0);
+ LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
+ assertEquals(source, dest);
+ }
+
@Test
public void testLinkPropertiesParcelable() throws Exception {
LinkProperties source = new LinkProperties();
@@ -870,15 +882,12 @@
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
- Parcel p = Parcel.obtain();
- source.writeToParcel(p, /* flags */ 0);
- p.setDataPosition(0);
- final byte[] marshalled = p.marshall();
- p = Parcel.obtain();
- p.unmarshall(marshalled, 0, marshalled.length);
- p.setDataPosition(0);
- LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
+ assertParcelingIsLossless(source);
+ }
- assertEquals(source, dest);
+ @Test
+ public void testParcelUninitialized() throws Exception {
+ LinkProperties empty = new LinkProperties();
+ assertParcelingIsLossless(empty);
}
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bf39644..2a92a7d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4683,7 +4683,7 @@
mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(true);
networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
- verify(mNetworkManagementService, times(1)).startClatd(MOBILE_IFNAME);
+ verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME);
Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent);
// Clat iface up, expect stack link updated.
@@ -4710,7 +4710,7 @@
mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle();
networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
- verify(mNetworkManagementService, times(1)).stopClatd(MOBILE_IFNAME);
+ verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
// Clat iface removed, expect linkproperties revert to original one
clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME);
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 4c52d81..9578ded 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -32,11 +32,13 @@
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.INetd;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkMisc;
import android.net.NetworkStack;
+import android.os.INetworkManagementService;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.format.DateUtils;
@@ -66,6 +68,8 @@
LingerMonitor mMonitor;
@Mock ConnectivityService mConnService;
+ @Mock INetd mNetd;
+ @Mock INetworkManagementService mNMS;
@Mock Context mCtx;
@Mock NetworkMisc mMisc;
@Mock NetworkNotificationManager mNotifier;
@@ -352,7 +356,7 @@
caps.addCapability(0);
caps.addTransportType(transport);
NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
- caps, 50, mCtx, null, mMisc, mConnService);
+ caps, 50, mCtx, null, mMisc, mConnService, mNetd, mNMS);
nai.everValidated = true;
return nai;
}
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index bf42412..07b1d05 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -17,9 +17,7 @@
package com.android.server.connectivity;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -27,6 +25,7 @@
import static org.mockito.Mockito.when;
import android.net.ConnectivityManager;
+import android.net.INetd;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -57,6 +56,7 @@
@Mock ConnectivityService mConnectivity;
@Mock NetworkMisc mMisc;
+ @Mock INetd mNetd;
@Mock INetworkManagementService mNms;
@Mock InterfaceConfiguration mConfig;
@Mock NetworkAgentInfo mNai;
@@ -65,7 +65,7 @@
Handler mHandler;
Nat464Xlat makeNat464Xlat() {
- return new Nat464Xlat(mNms, mNai);
+ return new Nat464Xlat(mNai, mNetd, mNms);
}
@Before
@@ -129,7 +129,7 @@
nat.start();
verify(mNms).registerObserver(eq(nat));
- verify(mNms).startClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStart(eq(BASE_IFACE));
// Stacked interface up notification arrives.
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -144,7 +144,7 @@
// ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
nat.stop();
- verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStop(eq(BASE_IFACE));
// Stacked interface removed notification arrives.
nat.interfaceRemoved(STACKED_IFACE);
@@ -156,7 +156,7 @@
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
assertIdle(nat);
- verifyNoMoreInteractions(mNms, mConnectivity);
+ verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
}
@Test
@@ -168,7 +168,7 @@
nat.start();
verify(mNms).registerObserver(eq(nat));
- verify(mNms).startClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStart(eq(BASE_IFACE));
// Stacked interface up notification arrives.
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -185,7 +185,7 @@
mLooper.dispatchNext();
verify(mNms).unregisterObserver(eq(nat));
- verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStop(eq(BASE_IFACE));
verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
assertTrue(c.getValue().getStackedLinks().isEmpty());
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
@@ -194,7 +194,7 @@
// ConnectivityService stops clat: no-op.
nat.stop();
- verifyNoMoreInteractions(mNms, mConnectivity);
+ verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
}
@Test
@@ -205,13 +205,13 @@
nat.start();
verify(mNms).registerObserver(eq(nat));
- verify(mNms).startClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStart(eq(BASE_IFACE));
// ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
nat.stop();
verify(mNms).unregisterObserver(eq(nat));
- verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStop(eq(BASE_IFACE));
assertIdle(nat);
// In-flight interface up notification arrives: no-op
@@ -225,7 +225,7 @@
assertIdle(nat);
- verifyNoMoreInteractions(mNms, mConnectivity);
+ verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
}
@Test
@@ -236,16 +236,16 @@
nat.start();
verify(mNms).registerObserver(eq(nat));
- verify(mNms).startClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStart(eq(BASE_IFACE));
// ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
nat.stop();
verify(mNms).unregisterObserver(eq(nat));
- verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStop(eq(BASE_IFACE));
assertIdle(nat);
- verifyNoMoreInteractions(mNms, mConnectivity);
+ verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
}
static void assertIdle(Nat464Xlat nat) {
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index 859a54d..e63c3b0 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -16,6 +16,9 @@
package com.android.server.net.ipmemorystore;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
import android.content.Context;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -26,6 +29,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
+
/** Unit tests for {@link IpMemoryStoreServiceTest}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -36,6 +41,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString());
}
@Test
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java b/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java
new file mode 100644
index 0000000..8d367e2
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import static com.android.server.net.ipmemorystore.RelevanceUtils.CAPPED_RELEVANCE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RelevanceUtils}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RelevanceUtilsTests {
+ @Test
+ public void testComputeRelevanceForTargetDate() {
+ final long dayInMillis = 24L * 60 * 60 * 1000;
+ final long base = 1_000_000L; // any given point in time
+ // Relevance when the network expires in 1000 years must be capped
+ assertEquals(CAPPED_RELEVANCE, RelevanceUtils.computeRelevanceForTargetDate(
+ base + 1000L * dayInMillis, base));
+ // Relevance when expiry is before the date must be 0
+ assertEquals(0, RelevanceUtils.computeRelevanceForTargetDate(base - 1, base));
+ // Make sure the relevance for a given target date is higher if the expiry is further
+ // in the future
+ assertTrue(RelevanceUtils.computeRelevanceForTargetDate(base + 100 * dayInMillis, base)
+ < RelevanceUtils.computeRelevanceForTargetDate(base + 150 * dayInMillis, base));
+
+ // Make sure the relevance falls slower as the expiry is closing in. This is to ensure
+ // the decay is indeed logarithmic.
+ final int relevanceAtExpiry = RelevanceUtils.computeRelevanceForTargetDate(base, base);
+ final int relevance50DaysBeforeExpiry =
+ RelevanceUtils.computeRelevanceForTargetDate(base + 50 * dayInMillis, base);
+ final int relevance100DaysBeforeExpiry =
+ RelevanceUtils.computeRelevanceForTargetDate(base + 100 * dayInMillis, base);
+ final int relevance150DaysBeforeExpiry =
+ RelevanceUtils.computeRelevanceForTargetDate(base + 150 * dayInMillis, base);
+ assertEquals(0, relevanceAtExpiry);
+ assertTrue(relevance50DaysBeforeExpiry - relevanceAtExpiry
+ < relevance100DaysBeforeExpiry - relevance50DaysBeforeExpiry);
+ assertTrue(relevance100DaysBeforeExpiry - relevance50DaysBeforeExpiry
+ < relevance150DaysBeforeExpiry - relevance100DaysBeforeExpiry);
+ }
+
+ @Test
+ public void testIncreaseRelevance() {
+ long expiry = System.currentTimeMillis();
+
+ final long firstBump = RelevanceUtils.bumpExpiryDate(expiry);
+ // Though a few milliseconds might have elapsed, the first bump should push the duration
+ // to days in the future, so unless this test takes literal days between these two lines,
+ // this should always pass.
+ assertTrue(firstBump > expiry);
+
+ expiry = 0;
+ long lastDifference = Long.MAX_VALUE;
+ // The relevance should be capped in at most this many steps. Otherwise, fail.
+ final int steps = 1000;
+ for (int i = 0; i < steps; ++i) {
+ final long newExpiry = RelevanceUtils.bumpExpiryDuration(expiry);
+ if (newExpiry == expiry) {
+ // The relevance should be capped. Make sure it is, then exit without failure.
+ assertEquals(newExpiry, RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS);
+ return;
+ }
+ // Make sure the new expiry is further in the future than last time.
+ assertTrue(newExpiry > expiry);
+ // Also check that it was not bumped as much as the last bump, because the
+ // decay must be exponential.
+ assertTrue(newExpiry - expiry < lastDifference);
+ lastDifference = newExpiry - expiry;
+ expiry = newExpiry;
+ }
+ fail("Relevance failed to go to the maximum value after " + steps + " bumps");
+ }
+
+ @Test
+ public void testContinuity() {
+ final long expiry = System.currentTimeMillis();
+
+ // Relevance at expiry and after expiry should be the cap.
+ final int relevanceBeforeMaxLifetime = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+ expiry - (RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS + 1_000_000));
+ assertEquals(relevanceBeforeMaxLifetime, CAPPED_RELEVANCE);
+ final int relevanceForMaxLifetime = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+ expiry - RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS);
+ assertEquals(relevanceForMaxLifetime, CAPPED_RELEVANCE);
+
+ // If the max relevance is reached at the cap lifetime, one millisecond less than this
+ // should be very close. Strictly speaking this is a bit brittle, but it should be
+ // good enough for the purposes of the memory store.
+ final int relevanceForOneMillisecLessThanCap = RelevanceUtils.computeRelevanceForTargetDate(
+ expiry, expiry - RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS + 1);
+ assertTrue(relevanceForOneMillisecLessThanCap <= CAPPED_RELEVANCE);
+ assertTrue(relevanceForOneMillisecLessThanCap >= CAPPED_RELEVANCE - 10);
+
+ // Likewise the relevance one millisecond before expiry should be very close to 0. It's
+ // fine if it rounds down to 0.
+ final int relevanceOneMillisecBeforeExpiry = RelevanceUtils.computeRelevanceForTargetDate(
+ expiry, expiry - 1);
+ assertTrue(relevanceOneMillisecBeforeExpiry <= 10);
+ assertTrue(relevanceOneMillisecBeforeExpiry >= 0);
+
+ final int relevanceAtExpiry = RelevanceUtils.computeRelevanceForTargetDate(expiry, expiry);
+ assertEquals(relevanceAtExpiry, 0);
+ final int relevanceAfterExpiry = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+ expiry + 1_000_000);
+ assertEquals(relevanceAfterExpiry, 0);
+ }
+
+ // testIncreaseRelevance makes sure bumping the expiry continuously always yields a
+ // monotonically increasing date as a side effect, but this tests that the relevance (as
+ // opposed to the expiry date) increases monotonically with increasing periods.
+ @Test
+ public void testMonotonicity() {
+ // Hopefully the relevance is granular enough to give a different value for every one
+ // of this number of steps.
+ final int steps = 40;
+ final long expiry = System.currentTimeMillis();
+
+ int lastRelevance = -1;
+ for (int i = 0; i < steps; ++i) {
+ final long date = expiry - i * (RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS / steps);
+ final int relevance = RelevanceUtils.computeRelevanceForTargetDate(expiry, date);
+ assertTrue(relevance > lastRelevance);
+ lastRelevance = relevance;
+ }
+ }
+}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 85bf6f2..5812ec4 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -213,6 +213,27 @@
return true;
}
+static bool AddDeprecatedUsesFeatures(xml::Element* el, SourcePathDiagnostics* diag) {
+ if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
+ if (attr->value.empty()) {
+ return true;
+ }
+
+ // Add "android.hardware.fingerprint" when "android.hardware.biometric.fingerprint" is found,
+ // since the former is deprecated in Q and the latter is not present pre-Q. (see b/115639644)
+ if (attr->value == "android.hardware.biometrics.fingerprint") {
+ auto element = el->CloneElement([&](const xml::Element& el, xml::Element* out_el) {
+ xml::Attribute* cloned_attr = out_el->FindOrCreateAttribute(xml::kSchemaAndroid, "name");
+ cloned_attr->value = "android.hardware.fingerprint";
+ });
+
+ el->parent->AppendChild(std::move(element));
+ }
+ }
+
+ return true;
+}
+
bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
IDiagnostics* diag) {
// First verify some options.
@@ -247,6 +268,7 @@
// Common <uses-feature> actions.
xml::XmlNodeAction uses_feature_action;
uses_feature_action.Action(VerifyUsesFeature);
+ uses_feature_action.Action(AddDeprecatedUsesFeatures);
// Common component actions.
xml::XmlNodeAction component_action;
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index adea627..fcc9f55 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -832,4 +832,36 @@
EXPECT_THAT(Verify(input), NotNull());
}
+TEST_F(ManifestFixerTest, UsesFeatureAddDeprecated) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-feature android:name="android.hardware.biometrics.fingerprint" />
+ <feature-group>
+ <uses-feature android:name="android.hardware.biometrics.fingerprint" />
+ </feature-group>
+ </manifest>)";
+
+ std::unique_ptr<xml::XmlResource> manifest = Verify(input);
+ ASSERT_THAT(manifest, NotNull());
+ EXPECT_THAT(manifest->root->FindChildWithAttribute("", "uses-feature",
+ xml::kSchemaAndroid, "name",
+ "android.hardware.biometrics.fingerprint"),
+ Ne(nullptr));
+ EXPECT_THAT(manifest->root->FindChildWithAttribute("", "uses-feature",
+ xml::kSchemaAndroid, "name",
+ "android.hardware.fingerprint"),
+ Ne(nullptr));
+
+ xml::Element* feature_group = manifest->root->FindChild("", "feature-group");
+ ASSERT_THAT(feature_group, Ne(nullptr));
+
+ EXPECT_THAT(feature_group->FindChildWithAttribute("", "uses-feature", xml::kSchemaAndroid, "name",
+ "android.hardware.biometrics.fingerprint"),
+ Ne(nullptr));
+ EXPECT_THAT(feature_group->FindChildWithAttribute("", "uses-feature", xml::kSchemaAndroid, "name",
+ "android.hardware.fingerprint"),
+ Ne(nullptr));
+}
+
} // namespace aapt
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 85871fe..e019f28 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -145,11 +145,23 @@
*/
public static final int SUITE_B_192 = 10;
+ /**
+ * WPA pre-shared key with stronger SHA256-based algorithms.
+ * @hide
+ */
+ public static final int WPA_PSK_SHA256 = 11;
+
+ /**
+ * WPA using EAP authentication with stronger SHA256-based algorithms.
+ * @hide
+ */
+ public static final int WPA_EAP_SHA256 = 12;
+
public static final String varName = "key_mgmt";
public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP",
"IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP",
- "SAE", "OWE", "SUITE_B_192"};
+ "SAE", "OWE", "SUITE_B_192", "WPA_PSK_SHA256", "WPA_EAP_SHA256" };
}
/**